Skip to main content

oxicrypt_module/
lib.rs

1//! FIPS 140-3 Level 1 module boundary.
2//!
3//! This crate defines the **cryptographic module boundary** per
4//! FIPS 140-3 Section 7.2 and the service-gating plumbing every
5//! other crate in the workspace depends on. No algorithm is
6//! permitted to produce output until the power-up self-tests have
7//! run and the module has entered the `Operational` state; on any
8//! self-test failure the module latches into a terminal `Error`
9//! state and rejects every subsequent call for the remainder of
10//! the process lifetime.
11//!
12//! # State machine (FIPS 140-3 §7.2)
13//!
14//! ```text
15//!    ┌──────────┐  initialize() ┌───────────┐  all KATs pass  ┌──────────────┐
16//!    │ PowerOff │──────────────▶│ SelfTest  │────────────────▶│ Operational  │
17//!    └──────────┘               └───────────┘                 └──────────────┘
18//!                                     │                              │
19//!                                 KAT failure                conditional self-test
20//!                                     │                            failure
21//!                                     ▼                              │
22//!                                ┌─────────┐◀───────────────────────┘
23//!                                │  Error  │
24//!                                └─────────┘
25//! ```
26//!
27//! # What this crate ships
28//!
29//! - [`State`] and the transition machine (`PowerOff →
30//!   SelfTest → Operational | Error`).
31//! - [`initialize_with_tests`] — the canonical one-shot entry
32//!   point that the top-level caller uses to run every approved
33//!   algorithm's power-up KAT before opening the service
34//!   interface. [`initialize`] is a thin wrapper for the empty
35//!   registry case (used only by this crate's own unit tests).
36//! - [`require_operational`] — the gate that every approved
37//!   service in every other crate calls on entry. Returns
38//!   [`Error::NotOperational`] (with the observed state) on any
39//!   pre-`Operational` or post-`Error` call.
40//! - [`enter_error_state`] — irreversible transition into the
41//!   terminal error state, called by conditional self-tests
42//!   (pairwise consistency, DRBG health) when they detect a
43//!   failure.
44//! - [`KatEntry`] and the [`SelfTest`] trait — the registry
45//!   shape that algorithm crates use to expose their power-up
46//!   KATs.
47//! - [`AlgorithmProfile`] — runtime selection of an algorithm
48//!   restriction policy (Unrestricted, CNSA 2.0, CNSA 1.0).
49//!   Set once at initialization via
50//!   [`initialize_with_profile`].
51//! - [`Service`] — per-algorithm-and-parameter enumeration of
52//!   every approved service in the module. Used with
53//!   [`require_allowed`] to enforce profile restrictions.
54//!
55//! The test registry is **not** assembled by linker-section
56//! tricks: callers pass an explicit `&[KatEntry]` slice. That
57//! means the full set of power-up tests is visible in source at
58//! every call site, which makes it straightforward to audit the
59//! module's power-up inventory against the Security Policy.
60//!
61//! # Algorithm profiles
62//!
63//! A single binary serves general FIPS 140-3 consumers
64//! and CNSA-restricted deployments via a runtime
65//! [`AlgorithmProfile`] selection. The operator passes the
66//! desired profile to [`initialize_with_profile`]; subsequent
67//! calls to [`require_allowed`] enforce it. Services not
68//! permitted by the active profile return
69//! [`Error::AlgorithmRestricted`].
70//!
71//! Three profiles are defined:
72//!
73//! - **Unrestricted** — all FIPS-approved algorithms are
74//!   available. This is the default, matching the behavior of
75//!   [`initialize_with_tests`].
76//! - **CNSA 2.0** (CNSSP 15) — only quantum-resistant
77//!   algorithms: AES-256, SHA-384/512, ML-KEM-1024, ML-DSA-87,
78//!   LMS and XMSS for software and firmware signing, plus
79//!   SHA3-384/512 and 256-bit SP 800-185 variants. Every
80//!   approved SP 800-208 LMS parameter set is permitted;
81//!   CNSSP-15 Annex B places no subset restriction on LMS.
82//!   No SLH-DSA parameter set is permitted — CNSSP-15 lists
83//!   none.
84//! - **CNSA 1.0** — classical algorithms for the transition
85//!   period: AES-256, SHA-384, ECDSA/ECDH P-384, RSA >= 3072,
86//!   DH >= 3072.
87//!
88//! Profiles nest: CNSA 2.0 is the most restrictive, CNSA 1.0
89//! is intermediate, Unrestricted allows everything. KATs still
90//! run all algorithms regardless of profile — the profile
91//! only restricts post-initialization service access.
92//!
93//! # Sensitive security parameters (SSPs)
94//!
95//! This crate holds **no SSPs** of its own. It only gates
96//! access to other crates that do. Secret material lives in the
97//! algorithm crates that own it (e.g. `oxicrypt-rsa`'s
98//! `RsaPrivateKey2048`, `oxicrypt-drbg`'s DRBG states). The error
99//! latch here does not perform SSP zeroization on its own — each
100//! owning crate is responsible for ensuring its SSPs are
101//! dropped when the process restarts following a transition into
102//! the `Error` state.
103//!
104//! # FIPS 140-3 / SP 800-140B mapping
105//!
106//! | SP 800-140B / IG clause | Implementation |
107//! |-------------------------|----------------|
108//! | §7.10 power-up self-tests | [`initialize_with_tests`] runs every registered [`KatEntry`] sequentially; the first failure latches [`State::Error`]. |
109//! | §7.10 conditional self-tests | Algorithm crates call [`enter_error_state`] on detecting a pairwise-consistency or DRBG-health failure. |
110//! | IG 9.5.A approved-mode indicator | [`is_operational`] / [`state`] — queryable at runtime. |
111//! | IG 10.3.A software integrity | Delegated to `oxicrypt-integrity` (separate crate), wired in as the first KAT by the top-level caller. |
112//!
113//! # Thread safety
114//!
115//! State is stored in a single `AtomicU8` and is safe to read
116//! from any thread. [`initialize_with_tests`] uses a
117//! compare-and-swap to guarantee the self-test phase runs
118//! exactly once per process lifetime even under concurrent
119//! first-calls; racing losers spin on the `SelfTest` state byte
120//! so they do not observe the transient phase and then receive
121//! [`Error::AlreadyInitialized`].
122
123#![no_std]
124#![forbid(unsafe_code)]
125
126use core::fmt;
127use core::sync::atomic::{AtomicU8, Ordering};
128
129/// Module lifecycle state per FIPS 140-3 Section 7.2.
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131#[repr(u8)]
132pub enum State {
133    /// Module has not yet been initialized. No services are available.
134    PowerOff = 0,
135    /// Power-up self-tests are currently executing. No services are
136    /// available. This state is transient.
137    SelfTest = 1,
138    /// All power-up self-tests have passed. Approved services are
139    /// available.
140    Operational = 2,
141    /// A self-test has failed (either a power-up KAT or a conditional
142    /// test). This state is **terminal** within a process: no further
143    /// services are offered and all cached secret material must be
144    /// treated as zeroized. The only recovery is to restart the
145    /// containing process.
146    Error = 3,
147}
148
149impl State {
150    const fn from_u8(raw: u8) -> Self {
151        match raw {
152            0 => Self::PowerOff,
153            1 => Self::SelfTest,
154            2 => Self::Operational,
155            // Any unknown discriminant collapses to `Error` so that a
156            // corrupted state byte can never be interpreted as
157            // `Operational`. This is defence-in-depth: `AtomicU8`
158            // should only ever hold values written through this
159            // module.
160            _ => Self::Error,
161        }
162    }
163}
164
165impl fmt::Display for State {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        let s = match self {
168            Self::PowerOff => "PowerOff",
169            Self::SelfTest => "SelfTest",
170            Self::Operational => "Operational",
171            Self::Error => "Error",
172        };
173        f.write_str(s)
174    }
175}
176
177/// Global module state. Written only by [`initialize`] and
178/// [`enter_error_state`]; read by [`state`] and [`require_operational`].
179static STATE: AtomicU8 = AtomicU8::new(State::PowerOff as u8);
180
181/// Returns the current module state.
182pub fn state() -> State {
183    State::from_u8(STATE.load(Ordering::Acquire))
184}
185
186/// Returns `true` iff the module has passed its power-up self-tests and is
187/// currently in the `Operational` state.
188pub fn is_operational() -> bool {
189    state() == State::Operational
190}
191
192/// Guard used at the entry point of every approved service. Returns
193/// `Ok(())` only when the module is `Operational`; otherwise returns a
194/// [`Error::NotOperational`] describing the current state so callers
195/// cannot accidentally ignore it.
196pub fn require_operational() -> Result<(), Error> {
197    let current = state();
198    if current == State::Operational {
199        Ok(())
200    } else {
201        Err(Error::NotOperational { current })
202    }
203}
204
205/// Errors surfaced by the module boundary.
206///
207/// Every variant is constructed with a `&'static str` description so
208/// that formatting is allocation-free; this lets the module report
209/// failures even from `no_std` contexts and from within the self-test
210/// runner itself.
211#[derive(Debug, Clone, Copy, PartialEq, Eq)]
212pub enum Error {
213    /// A service was invoked while the module was not in the
214    /// `Operational` state.
215    NotOperational {
216        /// The state the module was actually in when the call arrived.
217        current: State,
218    },
219    /// A power-up self-test failed. The module is now in the `Error`
220    /// state.
221    SelfTestFailed {
222        /// Name of the failing test, supplied by its
223        /// [`SelfTest`] implementation.
224        test: &'static str,
225    },
226    /// A conditional self-test (e.g. DRBG CRNGT, pairwise consistency)
227    /// failed during operation. The module is now in the `Error` state.
228    ConditionalTestFailed {
229        /// Short description of the failing check.
230        reason: &'static str,
231    },
232    /// [`initialize`] was called while the module was already past
233    /// `PowerOff`. This is not itself a FIPS violation — it just means
234    /// a second caller raced with the first. The first call's outcome
235    /// is authoritative; the race loser should read [`state`] to find
236    /// out.
237    AlreadyInitialized,
238    /// A cryptographic service was called with input bytes that do not
239    /// encode a valid domain element (for example, a scalar that is
240    /// zero or not less than the group order, or a point that is not
241    /// a canonical encoding on the curve). This is distinct from a
242    /// self-test failure: the module is still operational; the caller
243    /// supplied out-of-range bytes. Algorithm primitives should return
244    /// this rather than silently substituting a default.
245    InvalidInput,
246    /// A service was invoked that is not permitted under the active
247    /// [`AlgorithmProfile`]. The module is still operational — the
248    /// algorithm exists but is restricted by policy. Switch to a
249    /// less restrictive profile (which requires a process restart)
250    /// or use an alternative algorithm that is permitted.
251    AlgorithmRestricted {
252        /// The specific service that was blocked.
253        service: Service,
254    },
255    /// A service was invoked for an algorithm that exists in the
256    /// [`Service`] enum but whose implementation has not yet been
257    /// completed. Stub crates return this. The algorithm is
258    /// recognized by the profile system and its gate will pass if
259    /// the profile permits it, but the actual cryptographic
260    /// operation is not yet available.
261    NotImplemented,
262}
263
264impl fmt::Display for Error {
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        match self {
267            Self::NotOperational { current } => {
268                write!(f, "FIPS module not operational (state = {current})")
269            }
270            Self::SelfTestFailed { test } => {
271                write!(f, "FIPS power-up self-test failed: {test}")
272            }
273            Self::ConditionalTestFailed { reason } => {
274                write!(f, "FIPS conditional self-test failed: {reason}")
275            }
276            Self::AlreadyInitialized => f.write_str("FIPS module already initialized"),
277            Self::InvalidInput => f.write_str("invalid input to FIPS service"),
278            Self::AlgorithmRestricted { service } => {
279                write!(
280                    f,
281                    "algorithm {service} is not permitted under the {} profile",
282                    active_profile()
283                )
284            }
285            Self::NotImplemented => f.write_str("algorithm not yet implemented"),
286        }
287    }
288}
289
290/// Marker returned by a failing [`SelfTest::run`].
291///
292/// Self-tests are allowed to describe *what* failed, but they are not
293/// allowed to exfiltrate the intermediate values that triggered the
294/// failure — including them in an error payload would potentially leak
295/// information about internal state. The runner treats the mere
296/// presence of this value as "test failed" and converts it into
297/// [`Error::SelfTestFailed`] using the test's own [`SelfTest::NAME`].
298#[derive(Debug, Clone, Copy, PartialEq, Eq)]
299pub struct SelfTestFailure;
300
301impl fmt::Display for SelfTestFailure {
302    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303        f.write_str("self-test failure")
304    }
305}
306
307/// A single power-up self-test.
308///
309/// Each approved algorithm crate will implement this trait for at least
310/// one test vector, per SP 800-140B. The [`initialize`] routine runs
311/// every registered test sequentially before the module can leave the
312/// `SelfTest` state.
313///
314/// Implementations **must** be deterministic, **must not** allocate,
315/// and **must not** read from any input the caller controls: the
316/// whole point of a KAT is that its inputs and expected outputs are
317/// baked into the module binary.
318pub trait SelfTest {
319    /// Stable, human-readable identifier for this test. Appears in
320    /// [`Error::SelfTestFailed`] and in audit logs.
321    const NAME: &'static str;
322
323    /// Runs the test. Returns `Ok(())` on success, `Err(SelfTestFailure)`
324    /// on any mismatch. The runner converts the `Err` into
325    /// [`Error::SelfTestFailed`] using [`Self::NAME`].
326    fn run() -> Result<(), SelfTestFailure>;
327}
328
329/// A power-up KAT registered with the module.
330///
331/// Algorithm crates expose one or more `KatEntry` values that the
332/// top-level caller passes into [`initialize_with_tests`] at startup.
333/// The runner executes every entry sequentially before the module
334/// leaves the `SelfTest` state; a single failure latches the module
335/// into `Error` for the remainder of the process lifetime, per
336/// FIPS 140-3 Section 7.10 and SP 800-140B.
337///
338/// The function pointer signature takes no inputs on purpose: a KAT
339/// must run against compile-time-baked vectors only, never against
340/// caller-supplied data.
341#[derive(Debug, Clone, Copy)]
342pub struct KatEntry {
343    /// Stable identifier for the test. Surfaces in
344    /// [`Error::SelfTestFailed`] and audit logs. Must match the
345    /// [`SelfTest::NAME`] used for the underlying vector.
346    pub name: &'static str,
347    /// The test routine itself.
348    pub run: fn() -> Result<(), SelfTestFailure>,
349}
350
351/// Initializes the module with an empty KAT registry.
352///
353/// Convenience wrapper around [`initialize_with_tests`] for contexts
354/// that want to exercise only the state machine (notably
355/// `fips-module`'s own unit tests). Production callers of this crate
356/// must use [`initialize_with_tests`] with the full approved-service
357/// KAT set — a FIPS module that ships no self-tests is not compliant
358/// with SP 800-140B regardless of whether the state machine runs.
359pub fn initialize() -> Result<(), Error> {
360    initialize_with_tests(&[])
361}
362
363/// Initializes the module and runs every supplied power-up KAT.
364///
365/// This is the canonical entry point. It transitions
366/// `PowerOff -> SelfTest`, executes every entry in `tests` in order,
367/// and then transitions to `Operational` on success or to the
368/// terminal `Error` state on the first failure. The returned
369/// [`Error::SelfTestFailed`] carries the name of the failing test.
370///
371/// Concurrent calls are safe: exactly one caller wins the
372/// `PowerOff -> SelfTest` CAS; losers receive
373/// [`Error::AlreadyInitialized`] and should consult [`state`] to
374/// discover whether the winning call succeeded.
375///
376/// Note: the slice is passed **by reference**, not built up by magic.
377/// This repository deliberately avoids linker-section registration
378/// tricks: the full set of power-up tests is visible in source at
379/// every call site, which makes it straightforward to audit the
380/// module's test inventory against the Security Policy.
381pub fn initialize_with_tests(tests: &[KatEntry]) -> Result<(), Error> {
382    // Try to claim the SelfTest phase. Only the first caller in the
383    // process lifetime succeeds.
384    let cas = STATE.compare_exchange(
385        State::PowerOff as u8,
386        State::SelfTest as u8,
387        Ordering::AcqRel,
388        Ordering::Acquire,
389    );
390    if cas.is_err() {
391        // Another thread is running (or has run) the self-tests. If
392        // it is still inside `SelfTest`, spin until it publishes a
393        // terminal state so racing callers don't observe the
394        // transient phase and trip `require_operational`.
395        while STATE.load(Ordering::Acquire) == State::SelfTest as u8 {
396            core::hint::spin_loop();
397        }
398        return Err(Error::AlreadyInitialized);
399    }
400
401    for entry in tests {
402        let result = (entry.run)();
403        if result.is_err() {
404            // Latch the module into Error before returning. Any
405            // subsequent service call will be rejected by
406            // require_operational().
407            STATE.store(State::Error as u8, Ordering::Release);
408            return Err(Error::SelfTestFailed { test: entry.name });
409        }
410    }
411
412    STATE.store(State::Operational as u8, Ordering::Release);
413    Ok(())
414}
415
416/// Forces the module into the terminal `Error` state.
417///
418/// Called by conditional self-tests (DRBG CRNGT, pairwise consistency
419/// on keygen, etc.) when they detect a failure. Once invoked the
420/// module will reject every subsequent [`require_operational`] call
421/// for the remainder of the process lifetime.
422///
423/// This function is intentionally irreversible: FIPS 140-3 requires
424/// that a module which has entered an error condition stay there
425/// until it is re-initialized, and our "re-initialization" is a
426/// process restart.
427pub fn enter_error_state(_reason: &'static str) {
428    STATE.store(State::Error as u8, Ordering::Release);
429}
430
431// =========================================================================
432// Algorithm profile gating (CNSA 2.0 / CNSA 1.0)
433// =========================================================================
434
435/// Algorithm restriction profile selected at module initialization.
436///
437/// A single binary can serve general FIPS 140-3 consumers
438/// (`Unrestricted`) and CNSA-restricted deployments (`Cnsa2`,
439/// `Cnsa1`) by choosing the appropriate profile at init time.
440///
441/// The profile is immutable once set: it is stored alongside the
442/// module `State` before the first KAT runs and cannot be changed
443/// without restarting the process (which resets the state machine).
444#[derive(Debug, Clone, Copy, PartialEq, Eq)]
445#[repr(u8)]
446pub enum AlgorithmProfile {
447    /// All FIPS-approved algorithms are available. This is the
448    /// default and preserves backward compatibility with callers
449    /// that use [`initialize_with_tests`].
450    Unrestricted = 0,
451    /// CNSA 2.0 (CNSSP 15): quantum-resistant algorithms only.
452    /// AES-256, SHA-384/512, ML-KEM-1024, ML-DSA-87, LMS, XMSS.
453    /// SHA3-384/512 and 256-bit SP 800-185 variants are also
454    /// allowed. All other algorithms return
455    /// [`Error::AlgorithmRestricted`].
456    Cnsa2 = 1,
457    /// CNSA 1.0: classical algorithms for the transition period.
458    /// AES-256, SHA-384, ECDSA/ECDH P-384, RSA >= 3072, DH >= 3072.
459    Cnsa1 = 2,
460}
461
462impl AlgorithmProfile {
463    const fn from_u8(raw: u8) -> Self {
464        match raw {
465            0 => Self::Unrestricted,
466            2 => Self::Cnsa1,
467            // Unknown discriminant (including 1 = Cnsa2) defaults to
468            // the most restrictive profile as defence-in-depth.
469            _ => Self::Cnsa2,
470        }
471    }
472}
473
474impl fmt::Display for AlgorithmProfile {
475    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
476        let s = match self {
477            Self::Unrestricted => "Unrestricted",
478            Self::Cnsa2 => "CNSA 2.0",
479            Self::Cnsa1 => "CNSA 1.0",
480        };
481        f.write_str(s)
482    }
483}
484
485/// Global algorithm profile. Written once by [`initialize_with_profile`];
486/// read by [`require_allowed`] and [`active_profile`].
487static PROFILE: AtomicU8 = AtomicU8::new(AlgorithmProfile::Unrestricted as u8);
488
489/// Returns the active algorithm profile.
490pub fn active_profile() -> AlgorithmProfile {
491    AlgorithmProfile::from_u8(PROFILE.load(Ordering::Acquire))
492}
493
494/// Initializes the module with a specific algorithm profile and runs
495/// every supplied power-up KAT.
496///
497/// This is the profile-aware entry point. It stores the selected
498/// profile before running KATs, then transitions to `Operational` on
499/// success. KATs always run all algorithms regardless of the profile —
500/// the profile only restricts post-initialization service access via
501/// [`require_allowed`].
502///
503/// See [`initialize_with_tests`] for the backward-compatible wrapper
504/// that uses [`AlgorithmProfile::Unrestricted`].
505pub fn initialize_with_profile(tests: &[KatEntry], profile: AlgorithmProfile) -> Result<(), Error> {
506    // Store the profile before running KATs. This is safe even if
507    // initialization fails: a failed init latches State::Error, so
508    // no service call can reach require_allowed() anyway.
509    PROFILE.store(profile as u8, Ordering::Release);
510    initialize_with_tests(tests)
511}
512
513/// Enumeration of every approved service in the module, at the
514/// algorithm-and-parameter level.
515///
516/// Each variant represents a specific algorithm instantiation (e.g.
517/// `Aes128Ecb` not just `Aes`). This granularity is necessary
518/// because CNSA restrictions operate at the key-size and hash-size
519/// level.
520///
521/// Algorithm crates pass the appropriate `Service` variant to
522/// [`require_allowed`] at their public entry points.
523#[derive(Debug, Clone, Copy, PartialEq, Eq)]
524#[repr(u16)]
525#[allow(missing_docs)]
526pub enum Service {
527    // ----- oxicrypt-sha: FIPS 180-4 / FIPS 202 -----
528    Sha1 = 0,
529    Sha224 = 1,
530    Sha256 = 2,
531    Sha384 = 3,
532    Sha512 = 4,
533    Sha512_224 = 5,
534    Sha512_256 = 6,
535
536    Sha3_224 = 10,
537    Sha3_256 = 11,
538    Sha3_384 = 12,
539    Sha3_512 = 13,
540
541    // ----- oxicrypt-xof: FIPS 202 / SP 800-185 -----
542    Shake128 = 20,
543    Shake256 = 21,
544    CShake128 = 22,
545    CShake256 = 23,
546    Kmac128 = 24,
547    Kmac256 = 25,
548    KmacXof128 = 26,
549    KmacXof256 = 27,
550    TupleHash128 = 28,
551    TupleHash256 = 29,
552    TupleHashXof128 = 30,
553    TupleHashXof256 = 31,
554    ParallelHash128 = 32,
555    ParallelHash256 = 33,
556    ParallelHashXof128 = 34,
557    ParallelHashXof256 = 35,
558
559    // ----- oxicrypt-hmac: FIPS 198-1 -----
560    HmacSha1 = 40,
561    HmacSha224 = 41,
562    HmacSha256 = 42,
563    HmacSha384 = 43,
564    HmacSha512 = 44,
565    HmacSha512_224 = 45,
566    HmacSha512_256 = 46,
567    HmacSha3_224 = 47,
568    HmacSha3_256 = 48,
569    HmacSha3_384 = 49,
570    HmacSha3_512 = 50,
571
572    // ----- oxicrypt-cmac: SP 800-38B -----
573    CmacAes128 = 60,
574    CmacAes192 = 61,
575    CmacAes256 = 62,
576
577    // ----- oxicrypt-aes: FIPS 197 / SP 800-38A/D/C/F/Fp -----
578    Aes128Ecb = 70,
579    Aes128Cbc = 71,
580    Aes128Ctr = 72,
581    Aes128Gcm = 73,
582    Aes128Ccm = 74,
583    Aes128Kw = 75,
584    Aes128Kwp = 76,
585    Aes192Ecb = 80,
586    Aes192Cbc = 81,
587    Aes192Ctr = 82,
588    Aes192Gcm = 83,
589    Aes192Ccm = 84,
590    Aes192Kw = 85,
591    Aes192Kwp = 86,
592    Aes256Ecb = 90,
593    Aes256Cbc = 91,
594    Aes256Ctr = 92,
595    Aes256Gcm = 93,
596    Aes256Ccm = 94,
597    Aes256Kw = 95,
598    Aes256Kwp = 96,
599
600    // Key-construction gates — checked when building an AES key
601    // object before the caller selects a mode.  All per-mode
602    // variants of the same key size share identical profile
603    // permissions, so a single gate at key construction is
604    // sufficient.
605    /// AES-128 key construction (gates all AES-128 mode services).
606    Aes128 = 97,
607    /// AES-192 key construction (gates all AES-192 mode services).
608    Aes192 = 98,
609    /// AES-256 key construction (gates all AES-256 mode services).
610    Aes256 = 99,
611
612    // ----- oxicrypt-drbg: SP 800-90A -----
613    CtrDrbgAes128 = 100,
614    CtrDrbgAes192 = 101,
615    CtrDrbgAes256 = 102,
616    HashDrbgSha256 = 103,
617    HashDrbgSha384 = 104,
618    HashDrbgSha512 = 105,
619    HmacDrbgSha256 = 106,
620    HmacDrbgSha384 = 107,
621    HmacDrbgSha512 = 108,
622
623    // ----- oxicrypt-kdf: SP 800-56C / SP 800-108 / SP 800-132 -----
624    HkdfSha1 = 120,
625    HkdfSha256 = 121,
626    HkdfSha384 = 122,
627    HkdfSha512 = 123,
628    KbkdfHmacSha256 = 130,
629    KbkdfHmacSha384 = 131,
630    KbkdfHmacSha512 = 132,
631    KbkdfCmacAes128 = 133,
632    KbkdfCmacAes192 = 134,
633    KbkdfCmacAes256 = 135,
634    Pbkdf2HmacSha1 = 140,
635    Pbkdf2HmacSha224 = 141,
636    Pbkdf2HmacSha256 = 142,
637    Pbkdf2HmacSha384 = 143,
638    Pbkdf2HmacSha512 = 144,
639
640    // ----- oxicrypt-rsa: FIPS 186-5 / SP 800-56Br2 -----
641    RsaKeygen2048 = 150,
642    RsaPkcs1v15Sign2048 = 151,
643    RsaPssSign2048 = 152,
644    RsaOaep2048 = 153,
645    RsaPkcs1v15Verify2048 = 154,
646    RsaPssVerify2048 = 155,
647    RsaKeygen3072 = 160,
648    RsaPkcs1v15Sign3072 = 161,
649    RsaPssSign3072 = 162,
650    RsaOaep3072 = 163,
651    RsaPkcs1v15Verify3072 = 164,
652    RsaPssVerify3072 = 165,
653    RsaKeygen4096 = 170,
654    RsaPkcs1v15Sign4096 = 171,
655    RsaPssSign4096 = 172,
656    RsaOaep4096 = 173,
657    RsaPkcs1v15Verify4096 = 174,
658    RsaPssVerify4096 = 175,
659
660    // ----- oxicrypt-ecdsa: FIPS 186-5 -----
661    EcdsaP256Sign = 200,
662    EcdsaP256Verify = 201,
663    EcdsaP256Keygen = 202,
664    EcdsaP384Sign = 210,
665    EcdsaP384Verify = 211,
666    EcdsaP384Keygen = 212,
667
668    // ----- oxicrypt-ecdh: SP 800-56Ar3 -----
669    EcdhP256 = 220,
670    EcdhP384 = 221,
671
672    // ----- oxicrypt-eddsa: FIPS 186-5 / RFC 8032 -----
673    Ed25519Sign = 230,
674    Ed25519Verify = 231,
675    Ed25519Keygen = 232,
676
677    // ----- oxicrypt-tls-kdf: SP 800-135r1 + RFC 8446 §7.1 -----
678    Tls12Kdf = 240,
679    Tls13Kdf = 241,
680
681    // ----- oxicrypt-ml-kem: FIPS 203 -----
682    MlKem1024Encaps = 300,
683    MlKem1024Decaps = 301,
684    MlKem1024Keygen = 302,
685    MlKem512Encaps = 303,
686    MlKem512Decaps = 304,
687    MlKem512Keygen = 305,
688    MlKem768Encaps = 306,
689    MlKem768Decaps = 307,
690    MlKem768Keygen = 308,
691
692    // ----- oxicrypt-ml-dsa: FIPS 204 -----
693    MlDsa87Sign = 310,
694    MlDsa87Verify = 311,
695    MlDsa87Keygen = 312,
696    MlDsa44Sign = 313,
697    MlDsa44Verify = 314,
698    MlDsa44Keygen = 315,
699    MlDsa65Sign = 316,
700    MlDsa65Verify = 317,
701    MlDsa65Keygen = 318,
702
703    // ----- oxicrypt-slh-dsa: FIPS 205 (12 parameter sets × 3 ops = 36 variants) -----
704    // SHA-2 256s claims 320-322 for historical reasons, then the rest of the
705    // SHA-2 family (128s/f, 192s/f, 256f), then the SHAKE family. No SLH-DSA
706    // parameter set is permitted under either CNSA profile.
707    SlhDsaSha2256sKeygen = 320,
708    SlhDsaSha2256sSign = 321,
709    SlhDsaSha2256sVerify = 322,
710    SlhDsaSha2128sKeygen = 323,
711    SlhDsaSha2128sSign = 324,
712    SlhDsaSha2128sVerify = 325,
713    SlhDsaSha2128fKeygen = 326,
714    SlhDsaSha2128fSign = 327,
715    SlhDsaSha2128fVerify = 328,
716    SlhDsaSha2192sKeygen = 329,
717    SlhDsaSha2192sSign = 330,
718    SlhDsaSha2192sVerify = 331,
719    SlhDsaSha2192fKeygen = 332,
720    SlhDsaSha2192fSign = 333,
721    SlhDsaSha2192fVerify = 334,
722    SlhDsaSha2256fKeygen = 335,
723    SlhDsaSha2256fSign = 336,
724    SlhDsaSha2256fVerify = 337,
725    SlhDsaShake128sKeygen = 338,
726    SlhDsaShake128sSign = 339,
727    SlhDsaShake128sVerify = 340,
728    SlhDsaShake128fKeygen = 341,
729    SlhDsaShake128fSign = 342,
730    SlhDsaShake128fVerify = 343,
731    SlhDsaShake192sKeygen = 344,
732    SlhDsaShake192sSign = 345,
733    SlhDsaShake192sVerify = 346,
734    SlhDsaShake192fKeygen = 347,
735    SlhDsaShake192fSign = 348,
736    SlhDsaShake192fVerify = 349,
737    SlhDsaShake256sKeygen = 350,
738    SlhDsaShake256sSign = 351,
739    SlhDsaShake256sVerify = 352,
740    SlhDsaShake256fKeygen = 353,
741    SlhDsaShake256fSign = 354,
742    SlhDsaShake256fVerify = 355,
743
744    // ----- oxicrypt-lms: SP 800-208 (RFC 8554 / RFC 8708) -----
745    // 160 per-pair entries (80 pairs × Sign + Verify), discriminants 500-659.
746    // Layout: 500-539 SHA-256/M=32, 540-579 SHA-256/M=24,
747    // 580-619 SHAKE/M=32, 620-659 SHAKE/M=24.
748    // Within each family: (H ascending, W ascending), Sign/Verify alternating.
749    // The block is contiguous and holds nothing but LMS, which is what lets
750    // `is_lms_service` range-test it; `lms_discriminant_block_is_contiguous_and_pure`
751    // pins that. All 80 pairs are permitted under every profile.
752
753    // SHA-256 / N=32 family (RFC 8554 §A.1+§A.2)
754    LmsSha256M32H5W1Sign = 500,
755    LmsSha256M32H5W1Verify = 501,
756    LmsSha256M32H5W2Sign = 502,
757    LmsSha256M32H5W2Verify = 503,
758    LmsSha256M32H5W4Sign = 504,
759    LmsSha256M32H5W4Verify = 505,
760    LmsSha256M32H5W8Sign = 506,
761    LmsSha256M32H5W8Verify = 507,
762    LmsSha256M32H10W1Sign = 508,
763    LmsSha256M32H10W1Verify = 509,
764    LmsSha256M32H10W2Sign = 510,
765    LmsSha256M32H10W2Verify = 511,
766    LmsSha256M32H10W4Sign = 512,
767    LmsSha256M32H10W4Verify = 513,
768    LmsSha256M32H10W8Sign = 514,
769    LmsSha256M32H10W8Verify = 515,
770    LmsSha256M32H15W1Sign = 516,
771    LmsSha256M32H15W1Verify = 517,
772    LmsSha256M32H15W2Sign = 518,
773    LmsSha256M32H15W2Verify = 519,
774    LmsSha256M32H15W4Sign = 520,
775    LmsSha256M32H15W4Verify = 521,
776    LmsSha256M32H15W8Sign = 522,
777    LmsSha256M32H15W8Verify = 523,
778    LmsSha256M32H20W1Sign = 524,
779    LmsSha256M32H20W1Verify = 525,
780    LmsSha256M32H20W2Sign = 526,
781    LmsSha256M32H20W2Verify = 527,
782    LmsSha256M32H20W4Sign = 528,
783    LmsSha256M32H20W4Verify = 529,
784    LmsSha256M32H20W8Sign = 530,
785    LmsSha256M32H20W8Verify = 531,
786    LmsSha256M32H25W1Sign = 532,
787    LmsSha256M32H25W1Verify = 533,
788    LmsSha256M32H25W2Sign = 534,
789    LmsSha256M32H25W2Verify = 535,
790    LmsSha256M32H25W4Sign = 536,
791    LmsSha256M32H25W4Verify = 537,
792    LmsSha256M32H25W8Sign = 538,
793    LmsSha256M32H25W8Verify = 539,
794
795    // SHA-256 / N=24 family (RFC 8708 §4.1)
796    LmsSha256M24H5W1Sign = 540,
797    LmsSha256M24H5W1Verify = 541,
798    LmsSha256M24H5W2Sign = 542,
799    LmsSha256M24H5W2Verify = 543,
800    LmsSha256M24H5W4Sign = 544,
801    LmsSha256M24H5W4Verify = 545,
802    LmsSha256M24H5W8Sign = 546,
803    LmsSha256M24H5W8Verify = 547,
804    LmsSha256M24H10W1Sign = 548,
805    LmsSha256M24H10W1Verify = 549,
806    LmsSha256M24H10W2Sign = 550,
807    LmsSha256M24H10W2Verify = 551,
808    LmsSha256M24H10W4Sign = 552,
809    LmsSha256M24H10W4Verify = 553,
810    LmsSha256M24H10W8Sign = 554,
811    LmsSha256M24H10W8Verify = 555,
812    LmsSha256M24H15W1Sign = 556,
813    LmsSha256M24H15W1Verify = 557,
814    LmsSha256M24H15W2Sign = 558,
815    LmsSha256M24H15W2Verify = 559,
816    LmsSha256M24H15W4Sign = 560,
817    LmsSha256M24H15W4Verify = 561,
818    LmsSha256M24H15W8Sign = 562,
819    LmsSha256M24H15W8Verify = 563,
820    LmsSha256M24H20W1Sign = 564,
821    LmsSha256M24H20W1Verify = 565,
822    LmsSha256M24H20W2Sign = 566,
823    LmsSha256M24H20W2Verify = 567,
824    LmsSha256M24H20W4Sign = 568,
825    LmsSha256M24H20W4Verify = 569,
826    LmsSha256M24H20W8Sign = 570,
827    LmsSha256M24H20W8Verify = 571,
828    LmsSha256M24H25W1Sign = 572,
829    LmsSha256M24H25W1Verify = 573,
830    LmsSha256M24H25W2Sign = 574,
831    LmsSha256M24H25W2Verify = 575,
832    LmsSha256M24H25W4Sign = 576,
833    LmsSha256M24H25W4Verify = 577,
834    LmsSha256M24H25W8Sign = 578,
835    LmsSha256M24H25W8Verify = 579,
836
837    // SHAKE-256 / N=32 family (RFC 8708 §3.1)
838    LmsShakeM32H5W1Sign = 580,
839    LmsShakeM32H5W1Verify = 581,
840    LmsShakeM32H5W2Sign = 582,
841    LmsShakeM32H5W2Verify = 583,
842    LmsShakeM32H5W4Sign = 584,
843    LmsShakeM32H5W4Verify = 585,
844    LmsShakeM32H5W8Sign = 586,
845    LmsShakeM32H5W8Verify = 587,
846    LmsShakeM32H10W1Sign = 588,
847    LmsShakeM32H10W1Verify = 589,
848    LmsShakeM32H10W2Sign = 590,
849    LmsShakeM32H10W2Verify = 591,
850    LmsShakeM32H10W4Sign = 592,
851    LmsShakeM32H10W4Verify = 593,
852    LmsShakeM32H10W8Sign = 594,
853    LmsShakeM32H10W8Verify = 595,
854    LmsShakeM32H15W1Sign = 596,
855    LmsShakeM32H15W1Verify = 597,
856    LmsShakeM32H15W2Sign = 598,
857    LmsShakeM32H15W2Verify = 599,
858    LmsShakeM32H15W4Sign = 600,
859    LmsShakeM32H15W4Verify = 601,
860    LmsShakeM32H15W8Sign = 602,
861    LmsShakeM32H15W8Verify = 603,
862    LmsShakeM32H20W1Sign = 604,
863    LmsShakeM32H20W1Verify = 605,
864    LmsShakeM32H20W2Sign = 606,
865    LmsShakeM32H20W2Verify = 607,
866    LmsShakeM32H20W4Sign = 608,
867    LmsShakeM32H20W4Verify = 609,
868    LmsShakeM32H20W8Sign = 610,
869    LmsShakeM32H20W8Verify = 611,
870    LmsShakeM32H25W1Sign = 612,
871    LmsShakeM32H25W1Verify = 613,
872    LmsShakeM32H25W2Sign = 614,
873    LmsShakeM32H25W2Verify = 615,
874    LmsShakeM32H25W4Sign = 616,
875    LmsShakeM32H25W4Verify = 617,
876    LmsShakeM32H25W8Sign = 618,
877    LmsShakeM32H25W8Verify = 619,
878
879    // SHAKE-256 / N=24 family (RFC 8708 §4.2)
880    LmsShakeM24H5W1Sign = 620,
881    LmsShakeM24H5W1Verify = 621,
882    LmsShakeM24H5W2Sign = 622,
883    LmsShakeM24H5W2Verify = 623,
884    LmsShakeM24H5W4Sign = 624,
885    LmsShakeM24H5W4Verify = 625,
886    LmsShakeM24H5W8Sign = 626,
887    LmsShakeM24H5W8Verify = 627,
888    LmsShakeM24H10W1Sign = 628,
889    LmsShakeM24H10W1Verify = 629,
890    LmsShakeM24H10W2Sign = 630,
891    LmsShakeM24H10W2Verify = 631,
892    LmsShakeM24H10W4Sign = 632,
893    LmsShakeM24H10W4Verify = 633,
894    LmsShakeM24H10W8Sign = 634,
895    LmsShakeM24H10W8Verify = 635,
896    LmsShakeM24H15W1Sign = 636,
897    LmsShakeM24H15W1Verify = 637,
898    LmsShakeM24H15W2Sign = 638,
899    LmsShakeM24H15W2Verify = 639,
900    LmsShakeM24H15W4Sign = 640,
901    LmsShakeM24H15W4Verify = 641,
902    LmsShakeM24H15W8Sign = 642,
903    LmsShakeM24H15W8Verify = 643,
904    LmsShakeM24H20W1Sign = 644,
905    LmsShakeM24H20W1Verify = 645,
906    LmsShakeM24H20W2Sign = 646,
907    LmsShakeM24H20W2Verify = 647,
908    LmsShakeM24H20W4Sign = 648,
909    LmsShakeM24H20W4Verify = 649,
910    LmsShakeM24H20W8Sign = 650,
911    LmsShakeM24H20W8Verify = 651,
912    LmsShakeM24H25W1Sign = 652,
913    LmsShakeM24H25W1Verify = 653,
914    LmsShakeM24H25W2Sign = 654,
915    LmsShakeM24H25W2Verify = 655,
916    LmsShakeM24H25W4Sign = 656,
917    LmsShakeM24H25W4Verify = 657,
918    LmsShakeM24H25W8Sign = 658,
919    LmsShakeM24H25W8Verify = 659,
920
921    // ----- oxicrypt-xmss: SP 800-208 (stub) -----
922    // Renumbered 340/341 → 370/371 in Batch 4 to make room for the SLH-DSA block.
923    XmssSign = 370,
924    XmssVerify = 371,
925
926    // ----- oxicrypt-dh: RFC 3526 (stub) -----
927    // Renumbered 350 → 380 in Batch 4 to make room for the SLH-DSA block.
928    Dh3072 = 380,
929}
930
931impl fmt::Display for Service {
932    // The match arm count is proportional to the number of approved
933    // services in the module; there is no natural factoring that
934    // would reduce it.
935    #[allow(clippy::too_many_lines)]
936    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
937        let name = match self {
938            Self::Sha1 => "SHA-1",
939            Self::Sha224 => "SHA-224",
940            Self::Sha256 => "SHA-256",
941            Self::Sha384 => "SHA-384",
942            Self::Sha512 => "SHA-512",
943            Self::Sha512_224 => "SHA-512/224",
944            Self::Sha512_256 => "SHA-512/256",
945            Self::Sha3_224 => "SHA3-224",
946            Self::Sha3_256 => "SHA3-256",
947            Self::Sha3_384 => "SHA3-384",
948            Self::Sha3_512 => "SHA3-512",
949            Self::Shake128 => "SHAKE128",
950            Self::Shake256 => "SHAKE256",
951            Self::CShake128 => "cSHAKE128",
952            Self::CShake256 => "cSHAKE256",
953            Self::Kmac128 => "KMAC128",
954            Self::Kmac256 => "KMAC256",
955            Self::KmacXof128 => "KMACXOF128",
956            Self::KmacXof256 => "KMACXOF256",
957            Self::TupleHash128 => "TupleHash128",
958            Self::TupleHash256 => "TupleHash256",
959            Self::TupleHashXof128 => "TupleHashXOF128",
960            Self::TupleHashXof256 => "TupleHashXOF256",
961            Self::ParallelHash128 => "ParallelHash128",
962            Self::ParallelHash256 => "ParallelHash256",
963            Self::ParallelHashXof128 => "ParallelHashXOF128",
964            Self::ParallelHashXof256 => "ParallelHashXOF256",
965            Self::HmacSha1 => "HMAC-SHA-1",
966            Self::HmacSha224 => "HMAC-SHA-224",
967            Self::HmacSha256 => "HMAC-SHA-256",
968            Self::HmacSha384 => "HMAC-SHA-384",
969            Self::HmacSha512 => "HMAC-SHA-512",
970            Self::HmacSha512_224 => "HMAC-SHA-512/224",
971            Self::HmacSha512_256 => "HMAC-SHA-512/256",
972            Self::HmacSha3_224 => "HMAC-SHA3-224",
973            Self::HmacSha3_256 => "HMAC-SHA3-256",
974            Self::HmacSha3_384 => "HMAC-SHA3-384",
975            Self::HmacSha3_512 => "HMAC-SHA3-512",
976            Self::CmacAes128 => "CMAC-AES-128",
977            Self::CmacAes192 => "CMAC-AES-192",
978            Self::CmacAes256 => "CMAC-AES-256",
979            Self::Aes128Ecb => "AES-128-ECB",
980            Self::Aes128Cbc => "AES-128-CBC",
981            Self::Aes128Ctr => "AES-128-CTR",
982            Self::Aes128Gcm => "AES-128-GCM",
983            Self::Aes128Ccm => "AES-128-CCM",
984            Self::Aes128Kw => "AES-128-KW",
985            Self::Aes128Kwp => "AES-128-KWP",
986            Self::Aes192Ecb => "AES-192-ECB",
987            Self::Aes192Cbc => "AES-192-CBC",
988            Self::Aes192Ctr => "AES-192-CTR",
989            Self::Aes192Gcm => "AES-192-GCM",
990            Self::Aes192Ccm => "AES-192-CCM",
991            Self::Aes192Kw => "AES-192-KW",
992            Self::Aes192Kwp => "AES-192-KWP",
993            Self::Aes256Ecb => "AES-256-ECB",
994            Self::Aes256Cbc => "AES-256-CBC",
995            Self::Aes256Ctr => "AES-256-CTR",
996            Self::Aes256Gcm => "AES-256-GCM",
997            Self::Aes256Ccm => "AES-256-CCM",
998            Self::Aes256Kw => "AES-256-KW",
999            Self::Aes256Kwp => "AES-256-KWP",
1000            Self::Aes128 => "AES-128",
1001            Self::Aes192 => "AES-192",
1002            Self::Aes256 => "AES-256",
1003            Self::CtrDrbgAes128 => "CTR_DRBG-AES-128",
1004            Self::CtrDrbgAes192 => "CTR_DRBG-AES-192",
1005            Self::CtrDrbgAes256 => "CTR_DRBG-AES-256",
1006            Self::HashDrbgSha256 => "Hash_DRBG-SHA-256",
1007            Self::HashDrbgSha384 => "Hash_DRBG-SHA-384",
1008            Self::HashDrbgSha512 => "Hash_DRBG-SHA-512",
1009            Self::HmacDrbgSha256 => "HMAC_DRBG-SHA-256",
1010            Self::HmacDrbgSha384 => "HMAC_DRBG-SHA-384",
1011            Self::HmacDrbgSha512 => "HMAC_DRBG-SHA-512",
1012            Self::HkdfSha1 => "HKDF-SHA-1",
1013            Self::HkdfSha256 => "HKDF-SHA-256",
1014            Self::HkdfSha384 => "HKDF-SHA-384",
1015            Self::HkdfSha512 => "HKDF-SHA-512",
1016            Self::KbkdfHmacSha256 => "KBKDF-HMAC-SHA-256",
1017            Self::KbkdfHmacSha384 => "KBKDF-HMAC-SHA-384",
1018            Self::KbkdfHmacSha512 => "KBKDF-HMAC-SHA-512",
1019            Self::KbkdfCmacAes128 => "KBKDF-CMAC-AES-128",
1020            Self::KbkdfCmacAes192 => "KBKDF-CMAC-AES-192",
1021            Self::KbkdfCmacAes256 => "KBKDF-CMAC-AES-256",
1022            Self::Pbkdf2HmacSha1 => "PBKDF2-HMAC-SHA-1",
1023            Self::Pbkdf2HmacSha224 => "PBKDF2-HMAC-SHA-224",
1024            Self::Pbkdf2HmacSha256 => "PBKDF2-HMAC-SHA-256",
1025            Self::Pbkdf2HmacSha384 => "PBKDF2-HMAC-SHA-384",
1026            Self::Pbkdf2HmacSha512 => "PBKDF2-HMAC-SHA-512",
1027            Self::RsaKeygen2048 => "RSA-2048 keygen",
1028            Self::RsaPkcs1v15Sign2048 => "RSA-PKCS1v1.5-Sign-2048",
1029            Self::RsaPssSign2048 => "RSA-PSS-Sign-2048",
1030            Self::RsaOaep2048 => "RSA-OAEP-2048",
1031            Self::RsaPkcs1v15Verify2048 => "RSA-PKCS1v1.5-Verify-2048",
1032            Self::RsaPssVerify2048 => "RSA-PSS-Verify-2048",
1033            Self::RsaKeygen3072 => "RSA-3072 keygen",
1034            Self::RsaPkcs1v15Sign3072 => "RSA-PKCS1v1.5-Sign-3072",
1035            Self::RsaPssSign3072 => "RSA-PSS-Sign-3072",
1036            Self::RsaOaep3072 => "RSA-OAEP-3072",
1037            Self::RsaPkcs1v15Verify3072 => "RSA-PKCS1v1.5-Verify-3072",
1038            Self::RsaPssVerify3072 => "RSA-PSS-Verify-3072",
1039            Self::RsaKeygen4096 => "RSA-4096 keygen",
1040            Self::RsaPkcs1v15Sign4096 => "RSA-PKCS1v1.5-Sign-4096",
1041            Self::RsaPssSign4096 => "RSA-PSS-Sign-4096",
1042            Self::RsaOaep4096 => "RSA-OAEP-4096",
1043            Self::RsaPkcs1v15Verify4096 => "RSA-PKCS1v1.5-Verify-4096",
1044            Self::RsaPssVerify4096 => "RSA-PSS-Verify-4096",
1045            Self::EcdsaP256Sign => "ECDSA-P256 sign",
1046            Self::EcdsaP256Verify => "ECDSA-P256 verify",
1047            Self::EcdsaP256Keygen => "ECDSA-P256 keygen",
1048            Self::EcdsaP384Sign => "ECDSA-P384 sign",
1049            Self::EcdsaP384Verify => "ECDSA-P384 verify",
1050            Self::EcdsaP384Keygen => "ECDSA-P384 keygen",
1051            Self::EcdhP256 => "ECDH-P256",
1052            Self::EcdhP384 => "ECDH-P384",
1053            Self::Ed25519Sign => "Ed25519 sign",
1054            Self::Ed25519Verify => "Ed25519 verify",
1055            Self::Ed25519Keygen => "Ed25519 keygen",
1056            Self::Tls12Kdf => "TLS 1.2 KDF",
1057            Self::Tls13Kdf => "TLS 1.3 KDF",
1058            Self::MlKem1024Encaps => "ML-KEM-1024 encaps",
1059            Self::MlKem1024Decaps => "ML-KEM-1024 decaps",
1060            Self::MlKem1024Keygen => "ML-KEM-1024 keygen",
1061            Self::MlKem512Encaps => "ML-KEM-512 encaps",
1062            Self::MlKem512Decaps => "ML-KEM-512 decaps",
1063            Self::MlKem512Keygen => "ML-KEM-512 keygen",
1064            Self::MlKem768Encaps => "ML-KEM-768 encaps",
1065            Self::MlKem768Decaps => "ML-KEM-768 decaps",
1066            Self::MlKem768Keygen => "ML-KEM-768 keygen",
1067            Self::MlDsa87Sign => "ML-DSA-87 sign",
1068            Self::MlDsa87Verify => "ML-DSA-87 verify",
1069            Self::MlDsa87Keygen => "ML-DSA-87 keygen",
1070            Self::MlDsa44Sign => "ML-DSA-44 sign",
1071            Self::MlDsa44Verify => "ML-DSA-44 verify",
1072            Self::MlDsa44Keygen => "ML-DSA-44 keygen",
1073            Self::MlDsa65Sign => "ML-DSA-65 sign",
1074            Self::MlDsa65Verify => "ML-DSA-65 verify",
1075            Self::MlDsa65Keygen => "ML-DSA-65 keygen",
1076            Self::SlhDsaSha2256sKeygen => "SLH-DSA-SHA2-256s keygen",
1077            Self::SlhDsaSha2256sSign => "SLH-DSA-SHA2-256s sign",
1078            Self::SlhDsaSha2256sVerify => "SLH-DSA-SHA2-256s verify",
1079            Self::SlhDsaSha2128sKeygen => "SLH-DSA-SHA2-128s keygen",
1080            Self::SlhDsaSha2128sSign => "SLH-DSA-SHA2-128s sign",
1081            Self::SlhDsaSha2128sVerify => "SLH-DSA-SHA2-128s verify",
1082            Self::SlhDsaSha2128fKeygen => "SLH-DSA-SHA2-128f keygen",
1083            Self::SlhDsaSha2128fSign => "SLH-DSA-SHA2-128f sign",
1084            Self::SlhDsaSha2128fVerify => "SLH-DSA-SHA2-128f verify",
1085            Self::SlhDsaSha2192sKeygen => "SLH-DSA-SHA2-192s keygen",
1086            Self::SlhDsaSha2192sSign => "SLH-DSA-SHA2-192s sign",
1087            Self::SlhDsaSha2192sVerify => "SLH-DSA-SHA2-192s verify",
1088            Self::SlhDsaSha2192fKeygen => "SLH-DSA-SHA2-192f keygen",
1089            Self::SlhDsaSha2192fSign => "SLH-DSA-SHA2-192f sign",
1090            Self::SlhDsaSha2192fVerify => "SLH-DSA-SHA2-192f verify",
1091            Self::SlhDsaSha2256fKeygen => "SLH-DSA-SHA2-256f keygen",
1092            Self::SlhDsaSha2256fSign => "SLH-DSA-SHA2-256f sign",
1093            Self::SlhDsaSha2256fVerify => "SLH-DSA-SHA2-256f verify",
1094            Self::SlhDsaShake128sKeygen => "SLH-DSA-SHAKE-128s keygen",
1095            Self::SlhDsaShake128sSign => "SLH-DSA-SHAKE-128s sign",
1096            Self::SlhDsaShake128sVerify => "SLH-DSA-SHAKE-128s verify",
1097            Self::SlhDsaShake128fKeygen => "SLH-DSA-SHAKE-128f keygen",
1098            Self::SlhDsaShake128fSign => "SLH-DSA-SHAKE-128f sign",
1099            Self::SlhDsaShake128fVerify => "SLH-DSA-SHAKE-128f verify",
1100            Self::SlhDsaShake192sKeygen => "SLH-DSA-SHAKE-192s keygen",
1101            Self::SlhDsaShake192sSign => "SLH-DSA-SHAKE-192s sign",
1102            Self::SlhDsaShake192sVerify => "SLH-DSA-SHAKE-192s verify",
1103            Self::SlhDsaShake192fKeygen => "SLH-DSA-SHAKE-192f keygen",
1104            Self::SlhDsaShake192fSign => "SLH-DSA-SHAKE-192f sign",
1105            Self::SlhDsaShake192fVerify => "SLH-DSA-SHAKE-192f verify",
1106            Self::SlhDsaShake256sKeygen => "SLH-DSA-SHAKE-256s keygen",
1107            Self::SlhDsaShake256sSign => "SLH-DSA-SHAKE-256s sign",
1108            Self::SlhDsaShake256sVerify => "SLH-DSA-SHAKE-256s verify",
1109            Self::SlhDsaShake256fKeygen => "SLH-DSA-SHAKE-256f keygen",
1110            Self::SlhDsaShake256fSign => "SLH-DSA-SHAKE-256f sign",
1111            Self::SlhDsaShake256fVerify => "SLH-DSA-SHAKE-256f verify",
1112            Self::LmsSha256M32H5W1Sign => "LMS SHA-256 M=32 H=5 W=1 sign",
1113            Self::LmsSha256M32H5W1Verify => "LMS SHA-256 M=32 H=5 W=1 verify",
1114            Self::LmsSha256M32H5W2Sign => "LMS SHA-256 M=32 H=5 W=2 sign",
1115            Self::LmsSha256M32H5W2Verify => "LMS SHA-256 M=32 H=5 W=2 verify",
1116            Self::LmsSha256M32H5W4Sign => "LMS SHA-256 M=32 H=5 W=4 sign",
1117            Self::LmsSha256M32H5W4Verify => "LMS SHA-256 M=32 H=5 W=4 verify",
1118            Self::LmsSha256M32H5W8Sign => "LMS SHA-256 M=32 H=5 W=8 sign",
1119            Self::LmsSha256M32H5W8Verify => "LMS SHA-256 M=32 H=5 W=8 verify",
1120            Self::LmsSha256M32H10W1Sign => "LMS SHA-256 M=32 H=10 W=1 sign",
1121            Self::LmsSha256M32H10W1Verify => "LMS SHA-256 M=32 H=10 W=1 verify",
1122            Self::LmsSha256M32H10W2Sign => "LMS SHA-256 M=32 H=10 W=2 sign",
1123            Self::LmsSha256M32H10W2Verify => "LMS SHA-256 M=32 H=10 W=2 verify",
1124            Self::LmsSha256M32H10W4Sign => "LMS SHA-256 M=32 H=10 W=4 sign",
1125            Self::LmsSha256M32H10W4Verify => "LMS SHA-256 M=32 H=10 W=4 verify",
1126            Self::LmsSha256M32H10W8Sign => "LMS SHA-256 M=32 H=10 W=8 sign",
1127            Self::LmsSha256M32H10W8Verify => "LMS SHA-256 M=32 H=10 W=8 verify",
1128            Self::LmsSha256M32H15W1Sign => "LMS SHA-256 M=32 H=15 W=1 sign",
1129            Self::LmsSha256M32H15W1Verify => "LMS SHA-256 M=32 H=15 W=1 verify",
1130            Self::LmsSha256M32H15W2Sign => "LMS SHA-256 M=32 H=15 W=2 sign",
1131            Self::LmsSha256M32H15W2Verify => "LMS SHA-256 M=32 H=15 W=2 verify",
1132            Self::LmsSha256M32H15W4Sign => "LMS SHA-256 M=32 H=15 W=4 sign",
1133            Self::LmsSha256M32H15W4Verify => "LMS SHA-256 M=32 H=15 W=4 verify",
1134            Self::LmsSha256M32H15W8Sign => "LMS SHA-256 M=32 H=15 W=8 sign",
1135            Self::LmsSha256M32H15W8Verify => "LMS SHA-256 M=32 H=15 W=8 verify",
1136            Self::LmsSha256M32H20W1Sign => "LMS SHA-256 M=32 H=20 W=1 sign",
1137            Self::LmsSha256M32H20W1Verify => "LMS SHA-256 M=32 H=20 W=1 verify",
1138            Self::LmsSha256M32H20W2Sign => "LMS SHA-256 M=32 H=20 W=2 sign",
1139            Self::LmsSha256M32H20W2Verify => "LMS SHA-256 M=32 H=20 W=2 verify",
1140            Self::LmsSha256M32H20W4Sign => "LMS SHA-256 M=32 H=20 W=4 sign",
1141            Self::LmsSha256M32H20W4Verify => "LMS SHA-256 M=32 H=20 W=4 verify",
1142            Self::LmsSha256M32H20W8Sign => "LMS SHA-256 M=32 H=20 W=8 sign",
1143            Self::LmsSha256M32H20W8Verify => "LMS SHA-256 M=32 H=20 W=8 verify",
1144            Self::LmsSha256M32H25W1Sign => "LMS SHA-256 M=32 H=25 W=1 sign",
1145            Self::LmsSha256M32H25W1Verify => "LMS SHA-256 M=32 H=25 W=1 verify",
1146            Self::LmsSha256M32H25W2Sign => "LMS SHA-256 M=32 H=25 W=2 sign",
1147            Self::LmsSha256M32H25W2Verify => "LMS SHA-256 M=32 H=25 W=2 verify",
1148            Self::LmsSha256M32H25W4Sign => "LMS SHA-256 M=32 H=25 W=4 sign",
1149            Self::LmsSha256M32H25W4Verify => "LMS SHA-256 M=32 H=25 W=4 verify",
1150            Self::LmsSha256M32H25W8Sign => "LMS SHA-256 M=32 H=25 W=8 sign",
1151            Self::LmsSha256M32H25W8Verify => "LMS SHA-256 M=32 H=25 W=8 verify",
1152            Self::LmsSha256M24H5W1Sign => "LMS SHA-256 M=24 H=5 W=1 sign",
1153            Self::LmsSha256M24H5W1Verify => "LMS SHA-256 M=24 H=5 W=1 verify",
1154            Self::LmsSha256M24H5W2Sign => "LMS SHA-256 M=24 H=5 W=2 sign",
1155            Self::LmsSha256M24H5W2Verify => "LMS SHA-256 M=24 H=5 W=2 verify",
1156            Self::LmsSha256M24H5W4Sign => "LMS SHA-256 M=24 H=5 W=4 sign",
1157            Self::LmsSha256M24H5W4Verify => "LMS SHA-256 M=24 H=5 W=4 verify",
1158            Self::LmsSha256M24H5W8Sign => "LMS SHA-256 M=24 H=5 W=8 sign",
1159            Self::LmsSha256M24H5W8Verify => "LMS SHA-256 M=24 H=5 W=8 verify",
1160            Self::LmsSha256M24H10W1Sign => "LMS SHA-256 M=24 H=10 W=1 sign",
1161            Self::LmsSha256M24H10W1Verify => "LMS SHA-256 M=24 H=10 W=1 verify",
1162            Self::LmsSha256M24H10W2Sign => "LMS SHA-256 M=24 H=10 W=2 sign",
1163            Self::LmsSha256M24H10W2Verify => "LMS SHA-256 M=24 H=10 W=2 verify",
1164            Self::LmsSha256M24H10W4Sign => "LMS SHA-256 M=24 H=10 W=4 sign",
1165            Self::LmsSha256M24H10W4Verify => "LMS SHA-256 M=24 H=10 W=4 verify",
1166            Self::LmsSha256M24H10W8Sign => "LMS SHA-256 M=24 H=10 W=8 sign",
1167            Self::LmsSha256M24H10W8Verify => "LMS SHA-256 M=24 H=10 W=8 verify",
1168            Self::LmsSha256M24H15W1Sign => "LMS SHA-256 M=24 H=15 W=1 sign",
1169            Self::LmsSha256M24H15W1Verify => "LMS SHA-256 M=24 H=15 W=1 verify",
1170            Self::LmsSha256M24H15W2Sign => "LMS SHA-256 M=24 H=15 W=2 sign",
1171            Self::LmsSha256M24H15W2Verify => "LMS SHA-256 M=24 H=15 W=2 verify",
1172            Self::LmsSha256M24H15W4Sign => "LMS SHA-256 M=24 H=15 W=4 sign",
1173            Self::LmsSha256M24H15W4Verify => "LMS SHA-256 M=24 H=15 W=4 verify",
1174            Self::LmsSha256M24H15W8Sign => "LMS SHA-256 M=24 H=15 W=8 sign",
1175            Self::LmsSha256M24H15W8Verify => "LMS SHA-256 M=24 H=15 W=8 verify",
1176            Self::LmsSha256M24H20W1Sign => "LMS SHA-256 M=24 H=20 W=1 sign",
1177            Self::LmsSha256M24H20W1Verify => "LMS SHA-256 M=24 H=20 W=1 verify",
1178            Self::LmsSha256M24H20W2Sign => "LMS SHA-256 M=24 H=20 W=2 sign",
1179            Self::LmsSha256M24H20W2Verify => "LMS SHA-256 M=24 H=20 W=2 verify",
1180            Self::LmsSha256M24H20W4Sign => "LMS SHA-256 M=24 H=20 W=4 sign",
1181            Self::LmsSha256M24H20W4Verify => "LMS SHA-256 M=24 H=20 W=4 verify",
1182            Self::LmsSha256M24H20W8Sign => "LMS SHA-256 M=24 H=20 W=8 sign",
1183            Self::LmsSha256M24H20W8Verify => "LMS SHA-256 M=24 H=20 W=8 verify",
1184            Self::LmsSha256M24H25W1Sign => "LMS SHA-256 M=24 H=25 W=1 sign",
1185            Self::LmsSha256M24H25W1Verify => "LMS SHA-256 M=24 H=25 W=1 verify",
1186            Self::LmsSha256M24H25W2Sign => "LMS SHA-256 M=24 H=25 W=2 sign",
1187            Self::LmsSha256M24H25W2Verify => "LMS SHA-256 M=24 H=25 W=2 verify",
1188            Self::LmsSha256M24H25W4Sign => "LMS SHA-256 M=24 H=25 W=4 sign",
1189            Self::LmsSha256M24H25W4Verify => "LMS SHA-256 M=24 H=25 W=4 verify",
1190            Self::LmsSha256M24H25W8Sign => "LMS SHA-256 M=24 H=25 W=8 sign",
1191            Self::LmsSha256M24H25W8Verify => "LMS SHA-256 M=24 H=25 W=8 verify",
1192            Self::LmsShakeM32H5W1Sign => "LMS SHAKE M=32 H=5 W=1 sign",
1193            Self::LmsShakeM32H5W1Verify => "LMS SHAKE M=32 H=5 W=1 verify",
1194            Self::LmsShakeM32H5W2Sign => "LMS SHAKE M=32 H=5 W=2 sign",
1195            Self::LmsShakeM32H5W2Verify => "LMS SHAKE M=32 H=5 W=2 verify",
1196            Self::LmsShakeM32H5W4Sign => "LMS SHAKE M=32 H=5 W=4 sign",
1197            Self::LmsShakeM32H5W4Verify => "LMS SHAKE M=32 H=5 W=4 verify",
1198            Self::LmsShakeM32H5W8Sign => "LMS SHAKE M=32 H=5 W=8 sign",
1199            Self::LmsShakeM32H5W8Verify => "LMS SHAKE M=32 H=5 W=8 verify",
1200            Self::LmsShakeM32H10W1Sign => "LMS SHAKE M=32 H=10 W=1 sign",
1201            Self::LmsShakeM32H10W1Verify => "LMS SHAKE M=32 H=10 W=1 verify",
1202            Self::LmsShakeM32H10W2Sign => "LMS SHAKE M=32 H=10 W=2 sign",
1203            Self::LmsShakeM32H10W2Verify => "LMS SHAKE M=32 H=10 W=2 verify",
1204            Self::LmsShakeM32H10W4Sign => "LMS SHAKE M=32 H=10 W=4 sign",
1205            Self::LmsShakeM32H10W4Verify => "LMS SHAKE M=32 H=10 W=4 verify",
1206            Self::LmsShakeM32H10W8Sign => "LMS SHAKE M=32 H=10 W=8 sign",
1207            Self::LmsShakeM32H10W8Verify => "LMS SHAKE M=32 H=10 W=8 verify",
1208            Self::LmsShakeM32H15W1Sign => "LMS SHAKE M=32 H=15 W=1 sign",
1209            Self::LmsShakeM32H15W1Verify => "LMS SHAKE M=32 H=15 W=1 verify",
1210            Self::LmsShakeM32H15W2Sign => "LMS SHAKE M=32 H=15 W=2 sign",
1211            Self::LmsShakeM32H15W2Verify => "LMS SHAKE M=32 H=15 W=2 verify",
1212            Self::LmsShakeM32H15W4Sign => "LMS SHAKE M=32 H=15 W=4 sign",
1213            Self::LmsShakeM32H15W4Verify => "LMS SHAKE M=32 H=15 W=4 verify",
1214            Self::LmsShakeM32H15W8Sign => "LMS SHAKE M=32 H=15 W=8 sign",
1215            Self::LmsShakeM32H15W8Verify => "LMS SHAKE M=32 H=15 W=8 verify",
1216            Self::LmsShakeM32H20W1Sign => "LMS SHAKE M=32 H=20 W=1 sign",
1217            Self::LmsShakeM32H20W1Verify => "LMS SHAKE M=32 H=20 W=1 verify",
1218            Self::LmsShakeM32H20W2Sign => "LMS SHAKE M=32 H=20 W=2 sign",
1219            Self::LmsShakeM32H20W2Verify => "LMS SHAKE M=32 H=20 W=2 verify",
1220            Self::LmsShakeM32H20W4Sign => "LMS SHAKE M=32 H=20 W=4 sign",
1221            Self::LmsShakeM32H20W4Verify => "LMS SHAKE M=32 H=20 W=4 verify",
1222            Self::LmsShakeM32H20W8Sign => "LMS SHAKE M=32 H=20 W=8 sign",
1223            Self::LmsShakeM32H20W8Verify => "LMS SHAKE M=32 H=20 W=8 verify",
1224            Self::LmsShakeM32H25W1Sign => "LMS SHAKE M=32 H=25 W=1 sign",
1225            Self::LmsShakeM32H25W1Verify => "LMS SHAKE M=32 H=25 W=1 verify",
1226            Self::LmsShakeM32H25W2Sign => "LMS SHAKE M=32 H=25 W=2 sign",
1227            Self::LmsShakeM32H25W2Verify => "LMS SHAKE M=32 H=25 W=2 verify",
1228            Self::LmsShakeM32H25W4Sign => "LMS SHAKE M=32 H=25 W=4 sign",
1229            Self::LmsShakeM32H25W4Verify => "LMS SHAKE M=32 H=25 W=4 verify",
1230            Self::LmsShakeM32H25W8Sign => "LMS SHAKE M=32 H=25 W=8 sign",
1231            Self::LmsShakeM32H25W8Verify => "LMS SHAKE M=32 H=25 W=8 verify",
1232            Self::LmsShakeM24H5W1Sign => "LMS SHAKE M=24 H=5 W=1 sign",
1233            Self::LmsShakeM24H5W1Verify => "LMS SHAKE M=24 H=5 W=1 verify",
1234            Self::LmsShakeM24H5W2Sign => "LMS SHAKE M=24 H=5 W=2 sign",
1235            Self::LmsShakeM24H5W2Verify => "LMS SHAKE M=24 H=5 W=2 verify",
1236            Self::LmsShakeM24H5W4Sign => "LMS SHAKE M=24 H=5 W=4 sign",
1237            Self::LmsShakeM24H5W4Verify => "LMS SHAKE M=24 H=5 W=4 verify",
1238            Self::LmsShakeM24H5W8Sign => "LMS SHAKE M=24 H=5 W=8 sign",
1239            Self::LmsShakeM24H5W8Verify => "LMS SHAKE M=24 H=5 W=8 verify",
1240            Self::LmsShakeM24H10W1Sign => "LMS SHAKE M=24 H=10 W=1 sign",
1241            Self::LmsShakeM24H10W1Verify => "LMS SHAKE M=24 H=10 W=1 verify",
1242            Self::LmsShakeM24H10W2Sign => "LMS SHAKE M=24 H=10 W=2 sign",
1243            Self::LmsShakeM24H10W2Verify => "LMS SHAKE M=24 H=10 W=2 verify",
1244            Self::LmsShakeM24H10W4Sign => "LMS SHAKE M=24 H=10 W=4 sign",
1245            Self::LmsShakeM24H10W4Verify => "LMS SHAKE M=24 H=10 W=4 verify",
1246            Self::LmsShakeM24H10W8Sign => "LMS SHAKE M=24 H=10 W=8 sign",
1247            Self::LmsShakeM24H10W8Verify => "LMS SHAKE M=24 H=10 W=8 verify",
1248            Self::LmsShakeM24H15W1Sign => "LMS SHAKE M=24 H=15 W=1 sign",
1249            Self::LmsShakeM24H15W1Verify => "LMS SHAKE M=24 H=15 W=1 verify",
1250            Self::LmsShakeM24H15W2Sign => "LMS SHAKE M=24 H=15 W=2 sign",
1251            Self::LmsShakeM24H15W2Verify => "LMS SHAKE M=24 H=15 W=2 verify",
1252            Self::LmsShakeM24H15W4Sign => "LMS SHAKE M=24 H=15 W=4 sign",
1253            Self::LmsShakeM24H15W4Verify => "LMS SHAKE M=24 H=15 W=4 verify",
1254            Self::LmsShakeM24H15W8Sign => "LMS SHAKE M=24 H=15 W=8 sign",
1255            Self::LmsShakeM24H15W8Verify => "LMS SHAKE M=24 H=15 W=8 verify",
1256            Self::LmsShakeM24H20W1Sign => "LMS SHAKE M=24 H=20 W=1 sign",
1257            Self::LmsShakeM24H20W1Verify => "LMS SHAKE M=24 H=20 W=1 verify",
1258            Self::LmsShakeM24H20W2Sign => "LMS SHAKE M=24 H=20 W=2 sign",
1259            Self::LmsShakeM24H20W2Verify => "LMS SHAKE M=24 H=20 W=2 verify",
1260            Self::LmsShakeM24H20W4Sign => "LMS SHAKE M=24 H=20 W=4 sign",
1261            Self::LmsShakeM24H20W4Verify => "LMS SHAKE M=24 H=20 W=4 verify",
1262            Self::LmsShakeM24H20W8Sign => "LMS SHAKE M=24 H=20 W=8 sign",
1263            Self::LmsShakeM24H20W8Verify => "LMS SHAKE M=24 H=20 W=8 verify",
1264            Self::LmsShakeM24H25W1Sign => "LMS SHAKE M=24 H=25 W=1 sign",
1265            Self::LmsShakeM24H25W1Verify => "LMS SHAKE M=24 H=25 W=1 verify",
1266            Self::LmsShakeM24H25W2Sign => "LMS SHAKE M=24 H=25 W=2 sign",
1267            Self::LmsShakeM24H25W2Verify => "LMS SHAKE M=24 H=25 W=2 verify",
1268            Self::LmsShakeM24H25W4Sign => "LMS SHAKE M=24 H=25 W=4 sign",
1269            Self::LmsShakeM24H25W4Verify => "LMS SHAKE M=24 H=25 W=4 verify",
1270            Self::LmsShakeM24H25W8Sign => "LMS SHAKE M=24 H=25 W=8 sign",
1271            Self::LmsShakeM24H25W8Verify => "LMS SHAKE M=24 H=25 W=8 verify",
1272            Self::XmssSign => "XMSS sign",
1273            Self::XmssVerify => "XMSS verify",
1274            Self::Dh3072 => "DH-3072",
1275        };
1276        f.write_str(name)
1277    }
1278}
1279
1280/// Guard used at the entry point of every approved service, after
1281/// [`require_operational`], to enforce the active algorithm profile.
1282///
1283/// Returns `Ok(())` if the service is permitted under the profile
1284/// selected at initialization, or
1285/// `Err(Error::AlgorithmRestricted { .. })` otherwise.
1286///
1287/// This gate does **not** check [`State`] — callers must call
1288/// [`require_operational`] first. The two-gate pattern is:
1289///
1290/// ```ignore
1291/// require_operational()?;
1292/// require_allowed(Service::Sha256)?;
1293/// // ... perform the operation
1294/// ```
1295pub fn require_allowed(service: Service) -> Result<(), Error> {
1296    let profile = active_profile();
1297    if is_allowed(profile, service) {
1298        Ok(())
1299    } else {
1300        Err(Error::AlgorithmRestricted { service })
1301    }
1302}
1303
1304/// Single source of truth for which services are allowed in each
1305/// profile.
1306const fn is_allowed(profile: AlgorithmProfile, service: Service) -> bool {
1307    match profile {
1308        AlgorithmProfile::Unrestricted => true,
1309        AlgorithmProfile::Cnsa2 => is_cnsa2_allowed(service),
1310        AlgorithmProfile::Cnsa1 => is_cnsa1_allowed(service),
1311    }
1312}
1313
1314/// True for every LMS service, matching on the contiguous discriminant
1315/// block the `Service` enum reserves for LMS.
1316///
1317/// A range test rather than 160 enumerated arms: the LMS block occupies
1318/// `500..=659` with nothing else inside it. That is a property of the
1319/// enum, not of this function, so it is pinned separately by
1320/// `lms_discriminant_block_is_contiguous_and_pure` — without which a
1321/// variant inserted into the range would silently become CNSA-permitted.
1322const fn is_lms_service(service: Service) -> bool {
1323    lms_block_contains(service as u16)
1324}
1325
1326/// The range arithmetic behind [`is_lms_service`], over a raw discriminant.
1327///
1328/// Split out so the boundaries can be tested at all. `is_lms_service` takes a
1329/// `Service`, and the LMS block sits at the very top of the enum — there is no
1330/// variant above 659 — so a widened *upper* bound admits nothing today and no
1331/// `Service`-typed probe can detect it. Over a raw `u16`, both edges are
1332/// reachable: `lms_block_boundaries_are_exact` pins 499/500/659/660, which
1333/// fails if either bound moves in either direction.
1334const fn lms_block_contains(discriminant: u16) -> bool {
1335    discriminant >= Service::LmsSha256M32H5W1Sign as u16
1336        && discriminant <= Service::LmsShakeM24H25W8Verify as u16
1337}
1338
1339/// CNSA 2.0 allowed set. Only quantum-resistant algorithms plus
1340/// AES-256, SHA-384/512, SHA3-384/512, and 256-bit SP 800-185, and the
1341/// SP 800-208 stateful hash-based signatures for software and firmware
1342/// signing.
1343const fn is_cnsa2_allowed(service: Service) -> bool {
1344    matches!(
1345        service,
1346        // AES-256 all modes
1347        Service::Aes256Ecb
1348            | Service::Aes256Cbc
1349            | Service::Aes256Ctr
1350            | Service::Aes256Gcm
1351            | Service::Aes256Ccm
1352            | Service::Aes256Kw
1353            | Service::Aes256Kwp
1354            | Service::Aes256
1355            // SHA-384, SHA-512
1356            | Service::Sha384
1357            | Service::Sha512
1358            // SHA3-384, SHA3-512 (for internal / hardware — see plan note)
1359            | Service::Sha3_384
1360            | Service::Sha3_512
1361            // SHAKE-256
1362            | Service::Shake256
1363            // 256-bit SP 800-185 variants
1364            | Service::CShake256
1365            | Service::Kmac256
1366            | Service::KmacXof256
1367            | Service::TupleHash256
1368            | Service::TupleHashXof256
1369            | Service::ParallelHash256
1370            | Service::ParallelHashXof256
1371            // HMAC with allowed hashes
1372            | Service::HmacSha384
1373            | Service::HmacSha512
1374            | Service::HmacSha3_384
1375            | Service::HmacSha3_512
1376            // CMAC-AES-256
1377            | Service::CmacAes256
1378            // DRBGs backed by AES-256 or SHA-384/512
1379            | Service::CtrDrbgAes256
1380            | Service::HashDrbgSha384
1381            | Service::HashDrbgSha512
1382            | Service::HmacDrbgSha384
1383            | Service::HmacDrbgSha512
1384            // KDFs with allowed backing
1385            | Service::HkdfSha384
1386            | Service::HkdfSha512
1387            | Service::KbkdfHmacSha384
1388            | Service::KbkdfHmacSha512
1389            | Service::KbkdfCmacAes256
1390            | Service::Pbkdf2HmacSha384
1391            | Service::Pbkdf2HmacSha512
1392            // TLS 1.3 KDF — mandatory transport in CNSA 2.0
1393            | Service::Tls13Kdf
1394            // Post-quantum (CNSA 2.0 core)
1395            | Service::MlKem1024Encaps
1396            | Service::MlKem1024Decaps
1397            | Service::MlKem1024Keygen
1398            | Service::MlDsa87Sign
1399            | Service::MlDsa87Verify
1400            | Service::MlDsa87Keygen
1401            // XMSS — SP 800-208 stateful HBS for software/firmware signing.
1402            | Service::XmssSign
1403            | Service::XmssVerify
1404    )
1405        // LMS — SP 800-208 stateful HBS for software/firmware signing.
1406        // CNSSP-15 Annex B states "all parameters appropriate to protect to
1407        // TOP SECRET", recommending LMS-SHA-256/192, so every approved
1408        // parameter set the module implements is permitted rather than a
1409        // subset. Every non-LMS service outside the arms above still reaches
1410        // the `matches!` fail-safe default-block.
1411        || is_lms_service(service)
1412}
1413
1414/// CNSA 1.0 allowed set. Classical algorithms for the transition
1415/// period: AES-256, SHA-384, ECDSA/ECDH P-384, RSA >= 3072,
1416/// DH >= 3072. Also includes SHA-256 (widely needed for
1417/// interoperability and certificate verification) and the
1418/// post-quantum algorithms allowed in CNSA 2.0 (CNSA 1.0 is
1419/// the transition profile; PQ algorithms are allowed if present).
1420const fn is_cnsa1_allowed(service: Service) -> bool {
1421    matches!(
1422        service,
1423        // AES-256 all modes
1424        Service::Aes256Ecb
1425            | Service::Aes256Cbc
1426            | Service::Aes256Ctr
1427            | Service::Aes256Gcm
1428            | Service::Aes256Ccm
1429            | Service::Aes256Kw
1430            | Service::Aes256Kwp
1431            | Service::Aes256
1432            // SHA-256, SHA-384, SHA-512 (SHA-256 needed for certs)
1433            | Service::Sha256
1434            | Service::Sha384
1435            | Service::Sha512
1436            // SHA3-256, SHA3-384, SHA3-512
1437            | Service::Sha3_256
1438            | Service::Sha3_384
1439            | Service::Sha3_512
1440            // SHAKE-256
1441            | Service::Shake256
1442            // 256-bit SP 800-185 variants
1443            | Service::CShake256
1444            | Service::Kmac256
1445            | Service::KmacXof256
1446            | Service::TupleHash256
1447            | Service::TupleHashXof256
1448            | Service::ParallelHash256
1449            | Service::ParallelHashXof256
1450            // HMAC with allowed hashes
1451            | Service::HmacSha256
1452            | Service::HmacSha384
1453            | Service::HmacSha512
1454            | Service::HmacSha3_256
1455            | Service::HmacSha3_384
1456            | Service::HmacSha3_512
1457            // CMAC-AES-256
1458            | Service::CmacAes256
1459            // DRBGs backed by AES-256 or allowed hashes
1460            | Service::CtrDrbgAes256
1461            | Service::HashDrbgSha256
1462            | Service::HashDrbgSha384
1463            | Service::HashDrbgSha512
1464            | Service::HmacDrbgSha256
1465            | Service::HmacDrbgSha384
1466            | Service::HmacDrbgSha512
1467            // KDFs with allowed backing
1468            | Service::HkdfSha256
1469            | Service::HkdfSha384
1470            | Service::HkdfSha512
1471            | Service::KbkdfHmacSha256
1472            | Service::KbkdfHmacSha384
1473            | Service::KbkdfHmacSha512
1474            | Service::KbkdfCmacAes256
1475            | Service::Pbkdf2HmacSha256
1476            | Service::Pbkdf2HmacSha384
1477            | Service::Pbkdf2HmacSha512
1478            // ECDSA/ECDH P-384
1479            | Service::EcdsaP384Sign
1480            | Service::EcdsaP384Verify
1481            | Service::EcdsaP384Keygen
1482            | Service::EcdhP384
1483            // RSA >= 3072
1484            | Service::RsaKeygen3072
1485            | Service::RsaPkcs1v15Sign3072
1486            | Service::RsaPssSign3072
1487            | Service::RsaOaep3072
1488            | Service::RsaPkcs1v15Verify3072
1489            | Service::RsaPssVerify3072
1490            | Service::RsaKeygen4096
1491            | Service::RsaPkcs1v15Sign4096
1492            | Service::RsaPssSign4096
1493            | Service::RsaOaep4096
1494            | Service::RsaPkcs1v15Verify4096
1495            | Service::RsaPssVerify4096
1496            // DH >= 3072
1497            | Service::Dh3072
1498            // TLS 1.3 KDF — accepted as the modern transport KDF in the
1499            // transition profile too (TLS 1.3 with classical-only ciphers
1500            // is CNSA-1.0-compatible)
1501            | Service::Tls13Kdf
1502            // PQ algorithms (allowed during transition for hybrid use)
1503            | Service::MlKem1024Encaps
1504            | Service::MlKem1024Decaps
1505            | Service::MlKem1024Keygen
1506            | Service::MlDsa87Sign
1507            | Service::MlDsa87Verify
1508            | Service::MlDsa87Keygen
1509            // XMSS — mirrors CNSA 2.0 during the transition.
1510            | Service::XmssSign
1511            | Service::XmssVerify
1512    )
1513        // LMS — mirrors CNSA 2.0 during the transition. Stateful HBS is
1514        // permitted under both profiles for software and firmware signing.
1515        || is_lms_service(service)
1516}
1517
1518// -------------------------------------------------------------------------
1519// Test-only utilities
1520// -------------------------------------------------------------------------
1521//
1522// The global `STATE` makes the normal lifecycle effectively run-once per
1523// process. Unit tests in this crate need to exercise multiple transitions,
1524// so we expose a test-only reset helper gated behind `cfg(test)`. It is
1525// **not** part of the public API and is not compiled into release builds.
1526
1527/// The test-only state lock, in its own module so the guard cannot be forged.
1528///
1529/// The globals are a process singleton and cargo runs unit tests on a parallel
1530/// thread pool, so without serialisation one test's reset lands inside
1531/// another's initialize-then-assert window and the second observes a profile it
1532/// did not install. Measured at 1 failure in 20 runs before this lock, on
1533/// `require_allowed_returns_restricted_error_under_cnsa2`.
1534///
1535/// Serialising is deliberate where per-test isolation would be easier: the
1536/// singleton is the thing under test, and giving each test private state would
1537/// stop exercising it.
1538///
1539/// **Why a module and not merely a private field.** `MutexGuard` carries no
1540/// identity tying it to a particular static, so a `StateGuard` wrapping *any*
1541/// mutex would satisfy the type and still reach `reset`. Confining the type
1542/// here — field private to this module — means the only way to obtain one is
1543/// [`reset_for_test`], which takes the real lock; a sibling module cannot build
1544/// a decoy. Editing this module still defeats it. That is the honest bound, and
1545/// it is a much smaller one than "every future test must remember".
1546#[cfg(test)]
1547pub(crate) mod state_lock {
1548    use super::{AlgorithmProfile, Ordering, PROFILE, STATE, State};
1549
1550    static STATE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
1551
1552    /// Proof that the holder owns the state lock. Constructible only by
1553    /// [`reset_for_test`], because the field is private to this module.
1554    #[must_use]
1555    pub(crate) struct StateGuard(
1556        /// Held for its `Drop`, never read: the lock releases when the guard
1557        /// leaves the test's scope.
1558        #[allow(dead_code)]
1559        std::sync::MutexGuard<'static, ()>,
1560    );
1561
1562    impl StateGuard {
1563        /// Re-reset inside a test that already holds the lock — a loop over
1564        /// profiles needs fresh state per iteration without releasing it.
1565        // `&self` is the point: it is the proof the caller holds the lock.
1566        #[allow(clippy::unused_self)]
1567        pub(crate) fn reset(&self) {
1568            STATE.store(State::PowerOff as u8, Ordering::Release);
1569            PROFILE.store(AlgorithmProfile::Unrestricted as u8, Ordering::Release);
1570        }
1571    }
1572
1573    /// Resets the module to power-off and returns the guard that makes the
1574    /// reset safe. The lock is not a convention a future test has to remember:
1575    /// this is the only way to obtain a `StateGuard`, and `StateGuard` is the
1576    /// only way to reach a reset.
1577    ///
1578    /// `StateGuard` is `#[must_use]`, so a bare `reset_for_test();` fails
1579    /// `clippy -D warnings`, which the workspace gate runs — dropping the guard
1580    /// early breaks the build rather than reintroducing the race quietly.
1581    ///
1582    /// Residual, stated rather than implied: `let _ = reset_for_test();` still
1583    /// drops the guard immediately and does not warn. That is a deliberate act,
1584    /// which is the most this construction can bound.
1585    ///
1586    /// A panicking test poisons the mutex. The poison carries no meaning here
1587    /// because the state it protects is unconditionally overwritten on the next
1588    /// line, so it is recovered rather than propagated.
1589    pub(crate) fn reset_for_test() -> StateGuard {
1590        let guard = StateGuard(
1591            STATE_LOCK
1592                .lock()
1593                .unwrap_or_else(std::sync::PoisonError::into_inner),
1594        );
1595        guard.reset();
1596        guard
1597    }
1598}
1599
1600#[cfg(test)]
1601extern crate alloc;
1602
1603#[cfg(test)]
1604extern crate std;
1605
1606#[cfg(test)]
1607#[allow(clippy::unwrap_used, clippy::panic)]
1608mod tests {
1609    use super::state_lock::reset_for_test;
1610    use super::{
1611        AlgorithmProfile, Error, KatEntry, SelfTest, SelfTestFailure, Service, State,
1612        active_profile, enter_error_state, initialize, initialize_with_profile,
1613        initialize_with_tests, is_allowed, is_lms_service, is_operational, lms_block_contains,
1614        require_allowed, require_operational, state,
1615    };
1616    use alloc::string::ToString;
1617
1618    // Both of these helpers are used as `fn() -> Result<(), SelfTestFailure>`
1619    // function pointers in `KatEntry`, so the `Result` wrapper is mandatory
1620    // even though clippy sees each body as always returning the same variant.
1621    #[allow(clippy::unnecessary_wraps)]
1622    fn always_pass() -> Result<(), SelfTestFailure> {
1623        Ok(())
1624    }
1625    fn always_fail() -> Result<(), SelfTestFailure> {
1626        Err(SelfTestFailure)
1627    }
1628
1629    // All tests in this module share the single global `STATE`, so they
1630    // cannot run in parallel. `cargo test` in this crate must be invoked
1631    // with `--test-threads=1` — enforced by the CI workflow.
1632
1633    #[test]
1634    fn initial_state_is_power_off() {
1635        let _guard = reset_for_test();
1636        assert_eq!(state(), State::PowerOff);
1637        assert!(!is_operational());
1638    }
1639
1640    #[test]
1641    fn initialize_transitions_to_operational() {
1642        let _guard = reset_for_test();
1643        initialize().unwrap();
1644        assert_eq!(state(), State::Operational);
1645        assert!(is_operational());
1646        require_operational().unwrap();
1647    }
1648
1649    #[test]
1650    fn second_initialize_reports_already_initialized() {
1651        let _guard = reset_for_test();
1652        initialize().unwrap();
1653        match initialize() {
1654            Err(Error::AlreadyInitialized) => {}
1655            other => panic!("expected AlreadyInitialized, got {other:?}"),
1656        }
1657    }
1658
1659    #[test]
1660    fn require_operational_rejects_power_off() {
1661        let _guard = reset_for_test();
1662        match require_operational() {
1663            Err(Error::NotOperational {
1664                current: State::PowerOff,
1665            }) => {}
1666            other => panic!("expected NotOperational{{PowerOff}}, got {other:?}"),
1667        }
1668    }
1669
1670    #[test]
1671    fn enter_error_state_is_terminal_for_require_operational() {
1672        let _guard = reset_for_test();
1673        initialize().unwrap();
1674        assert!(is_operational());
1675        enter_error_state("unit-test forced failure");
1676        assert_eq!(state(), State::Error);
1677        match require_operational() {
1678            Err(Error::NotOperational {
1679                current: State::Error,
1680            }) => {}
1681            other => panic!("expected NotOperational{{Error}}, got {other:?}"),
1682        }
1683    }
1684
1685    #[test]
1686    fn state_display_is_stable() {
1687        // The exact strings appear in log output and audit trails,
1688        // so pin them.
1689        assert_eq!(State::PowerOff.to_string(), "PowerOff");
1690        assert_eq!(State::SelfTest.to_string(), "SelfTest");
1691        assert_eq!(State::Operational.to_string(), "Operational");
1692        assert_eq!(State::Error.to_string(), "Error");
1693    }
1694
1695    #[test]
1696    fn registry_runs_passing_tests_and_reaches_operational() {
1697        let _guard = reset_for_test();
1698        let tests = [
1699            KatEntry {
1700                name: "dummy-pass-a",
1701                run: always_pass,
1702            },
1703            KatEntry {
1704                name: "dummy-pass-b",
1705                run: always_pass,
1706            },
1707        ];
1708        initialize_with_tests(&tests).unwrap();
1709        assert_eq!(state(), State::Operational);
1710    }
1711
1712    #[test]
1713    fn registry_failing_test_latches_error_and_returns_name() {
1714        let _guard = reset_for_test();
1715        let tests = [
1716            KatEntry {
1717                name: "dummy-pass",
1718                run: always_pass,
1719            },
1720            KatEntry {
1721                name: "dummy-fail",
1722                run: always_fail,
1723            },
1724            // This third entry must never run.
1725            KatEntry {
1726                name: "dummy-unreached",
1727                run: always_pass,
1728            },
1729        ];
1730        match initialize_with_tests(&tests) {
1731            Err(Error::SelfTestFailed { test: "dummy-fail" }) => {}
1732            other => panic!("expected SelfTestFailed{{dummy-fail}}, got {other:?}"),
1733        }
1734        assert_eq!(state(), State::Error);
1735        // Operational-only guard must reject calls from here on.
1736        assert!(matches!(
1737            require_operational(),
1738            Err(Error::NotOperational {
1739                current: State::Error
1740            })
1741        ));
1742    }
1743
1744    // Trivial SelfTest implementation exercised outside the registry
1745    // so the trait stays part of the compiled public surface.
1746    struct DummyTest;
1747    impl SelfTest for DummyTest {
1748        const NAME: &'static str = "dummy-trait-vector";
1749        fn run() -> Result<(), SelfTestFailure> {
1750            Ok(())
1751        }
1752    }
1753
1754    #[test]
1755    fn dummy_self_test_trait_is_usable() {
1756        assert_eq!(DummyTest::NAME, "dummy-trait-vector");
1757        DummyTest::run().unwrap();
1758    }
1759
1760    // =====================================================================
1761    // Algorithm profile gating tests
1762    // =====================================================================
1763
1764    #[test]
1765    fn default_profile_is_unrestricted() {
1766        let _guard = reset_for_test();
1767        assert_eq!(active_profile(), AlgorithmProfile::Unrestricted);
1768    }
1769
1770    #[test]
1771    fn initialize_with_profile_sets_cnsa2() {
1772        let _guard = reset_for_test();
1773        initialize_with_profile(&[], AlgorithmProfile::Cnsa2).unwrap();
1774        assert_eq!(active_profile(), AlgorithmProfile::Cnsa2);
1775        assert!(is_operational());
1776    }
1777
1778    #[test]
1779    fn initialize_with_profile_sets_cnsa1() {
1780        let _guard = reset_for_test();
1781        initialize_with_profile(&[], AlgorithmProfile::Cnsa1).unwrap();
1782        assert_eq!(active_profile(), AlgorithmProfile::Cnsa1);
1783        assert!(is_operational());
1784    }
1785
1786    #[test]
1787    fn unrestricted_allows_all_services() {
1788        // Spot-check: every category should pass in Unrestricted.
1789        let spot = [
1790            Service::Sha1,
1791            Service::Sha256,
1792            Service::Aes128Ecb,
1793            Service::Aes256Gcm,
1794            Service::CtrDrbgAes128,
1795            Service::EcdsaP256Sign,
1796            Service::Ed25519Sign,
1797            Service::RsaPssSign2048,
1798            Service::MlKem1024Encaps,
1799            Service::LmsSha256M32H10W4Sign,
1800            Service::Dh3072,
1801            Service::SlhDsaSha2256sSign,
1802            Service::Tls12Kdf,
1803            Service::Tls13Kdf,
1804        ];
1805        for svc in spot {
1806            assert!(
1807                is_allowed(AlgorithmProfile::Unrestricted, svc),
1808                "{svc} should be allowed in Unrestricted"
1809            );
1810        }
1811    }
1812
1813    #[test]
1814    fn cnsa2_allows_aes256_and_pq() {
1815        let allowed = [
1816            Service::Aes256Gcm,
1817            Service::Aes256Cbc,
1818            Service::Sha384,
1819            Service::Sha512,
1820            Service::Sha3_384,
1821            Service::Sha3_512,
1822            Service::HmacSha384,
1823            Service::HmacSha512,
1824            Service::CtrDrbgAes256,
1825            Service::HashDrbgSha384,
1826            Service::HmacDrbgSha512,
1827            Service::Kmac256,
1828            Service::KmacXof256,
1829            Service::MlKem1024Encaps,
1830            Service::MlKem1024Decaps,
1831            Service::MlKem1024Keygen,
1832            Service::MlDsa87Sign,
1833            Service::MlDsa87Verify,
1834            Service::MlDsa87Keygen,
1835            Service::LmsSha256M32H10W4Sign,
1836            Service::LmsSha256M32H10W4Verify,
1837            Service::XmssSign,
1838            Service::XmssVerify,
1839            Service::Tls13Kdf,
1840        ];
1841        for svc in allowed {
1842            assert!(
1843                is_allowed(AlgorithmProfile::Cnsa2, svc),
1844                "{svc} should be allowed in CNSA 2.0"
1845            );
1846        }
1847    }
1848
1849    #[test]
1850    fn cnsa2_blocks_classical_and_small_keys() {
1851        let blocked = [
1852            Service::Sha1,
1853            Service::Sha224,
1854            Service::Sha256,
1855            Service::Sha512_224,
1856            Service::Sha512_256,
1857            Service::Sha3_224,
1858            Service::Sha3_256,
1859            Service::Shake128,
1860            Service::Kmac128,
1861            Service::Aes128Ecb,
1862            Service::Aes128Gcm,
1863            Service::Aes192Cbc,
1864            Service::CtrDrbgAes128,
1865            Service::CtrDrbgAes192,
1866            Service::HashDrbgSha256,
1867            Service::HmacDrbgSha256,
1868            Service::HmacSha1,
1869            Service::HmacSha256,
1870            Service::CmacAes128,
1871            Service::CmacAes192,
1872            Service::EcdsaP256Sign,
1873            Service::EcdsaP256Verify,
1874            Service::EcdhP256,
1875            Service::Ed25519Sign,
1876            Service::Ed25519Verify,
1877            Service::RsaPssSign2048,
1878            Service::RsaOaep2048,
1879            Service::Tls12Kdf,
1880            // No SLH-DSA parameter set is in CNSA 2.0 — CNSSP-15 lists none.
1881            Service::SlhDsaSha2128sSign,
1882            Service::MlDsa44Sign,
1883            Service::MlDsa44Verify,
1884            Service::MlDsa44Keygen,
1885            Service::MlDsa65Sign,
1886            Service::MlDsa65Verify,
1887            Service::MlDsa65Keygen,
1888        ];
1889        for svc in blocked {
1890            assert!(
1891                !is_allowed(AlgorithmProfile::Cnsa2, svc),
1892                "{svc} should be BLOCKED in CNSA 2.0"
1893            );
1894        }
1895    }
1896
1897    #[test]
1898    fn cnsa1_allows_p384_rsa3072_and_pq() {
1899        let allowed = [
1900            Service::Aes256Gcm,
1901            Service::Sha384,
1902            Service::Sha256,
1903            Service::EcdsaP384Sign,
1904            Service::EcdsaP384Verify,
1905            Service::EcdsaP384Keygen,
1906            Service::EcdhP384,
1907            Service::RsaPssSign3072,
1908            Service::RsaOaep3072,
1909            Service::RsaPssSign4096,
1910            Service::Dh3072,
1911            Service::MlKem1024Encaps,
1912            Service::LmsSha256M32H10W4Sign,
1913            Service::Tls13Kdf,
1914        ];
1915        for svc in allowed {
1916            assert!(
1917                is_allowed(AlgorithmProfile::Cnsa1, svc),
1918                "{svc} should be allowed in CNSA 1.0"
1919            );
1920        }
1921    }
1922
1923    #[test]
1924    fn cnsa1_blocks_small_keys_and_ed25519() {
1925        let blocked = [
1926            Service::Sha1,
1927            Service::Sha224,
1928            Service::Aes128Ecb,
1929            Service::Aes192Gcm,
1930            Service::CtrDrbgAes128,
1931            Service::EcdsaP256Sign,
1932            Service::EcdhP256,
1933            Service::Ed25519Sign,
1934            Service::Ed25519Verify,
1935            Service::RsaPssSign2048,
1936            Service::RsaOaep2048,
1937            Service::Tls12Kdf,
1938        ];
1939        for svc in blocked {
1940            assert!(
1941                !is_allowed(AlgorithmProfile::Cnsa1, svc),
1942                "{svc} should be BLOCKED in CNSA 1.0"
1943            );
1944        }
1945    }
1946
1947    // The full LMS service enumeration, shared by the gating test and the
1948    // discriminant-block test. Explicit rather than generated: CMVP reviewers
1949    // read this list, and a macro would hide exactly what it must show.
1950    #[allow(clippy::too_many_lines)]
1951    fn lms_all_160() -> [Service; 160] {
1952        [
1953            // SHA-256 / N=32 family (20 pairs, 40 entries).
1954            Service::LmsSha256M32H5W1Sign,
1955            Service::LmsSha256M32H5W1Verify,
1956            Service::LmsSha256M32H5W2Sign,
1957            Service::LmsSha256M32H5W2Verify,
1958            Service::LmsSha256M32H5W4Sign,
1959            Service::LmsSha256M32H5W4Verify,
1960            Service::LmsSha256M32H5W8Sign,
1961            Service::LmsSha256M32H5W8Verify,
1962            Service::LmsSha256M32H10W1Sign,
1963            Service::LmsSha256M32H10W1Verify,
1964            Service::LmsSha256M32H10W2Sign,
1965            Service::LmsSha256M32H10W2Verify,
1966            Service::LmsSha256M32H10W4Sign,
1967            Service::LmsSha256M32H10W4Verify,
1968            Service::LmsSha256M32H10W8Sign,
1969            Service::LmsSha256M32H10W8Verify,
1970            Service::LmsSha256M32H15W1Sign,
1971            Service::LmsSha256M32H15W1Verify,
1972            Service::LmsSha256M32H15W2Sign,
1973            Service::LmsSha256M32H15W2Verify,
1974            Service::LmsSha256M32H15W4Sign,
1975            Service::LmsSha256M32H15W4Verify,
1976            Service::LmsSha256M32H15W8Sign,
1977            Service::LmsSha256M32H15W8Verify,
1978            Service::LmsSha256M32H20W1Sign,
1979            Service::LmsSha256M32H20W1Verify,
1980            Service::LmsSha256M32H20W2Sign,
1981            Service::LmsSha256M32H20W2Verify,
1982            Service::LmsSha256M32H20W4Sign,
1983            Service::LmsSha256M32H20W4Verify,
1984            Service::LmsSha256M32H20W8Sign,
1985            Service::LmsSha256M32H20W8Verify,
1986            Service::LmsSha256M32H25W1Sign,
1987            Service::LmsSha256M32H25W1Verify,
1988            Service::LmsSha256M32H25W2Sign,
1989            Service::LmsSha256M32H25W2Verify,
1990            Service::LmsSha256M32H25W4Sign,
1991            Service::LmsSha256M32H25W4Verify,
1992            Service::LmsSha256M32H25W8Sign,
1993            Service::LmsSha256M32H25W8Verify,
1994            // SHA-256 / N=24 family (20 pairs, 40 entries).
1995            Service::LmsSha256M24H5W1Sign,
1996            Service::LmsSha256M24H5W1Verify,
1997            Service::LmsSha256M24H5W2Sign,
1998            Service::LmsSha256M24H5W2Verify,
1999            Service::LmsSha256M24H5W4Sign,
2000            Service::LmsSha256M24H5W4Verify,
2001            Service::LmsSha256M24H5W8Sign,
2002            Service::LmsSha256M24H5W8Verify,
2003            Service::LmsSha256M24H10W1Sign,
2004            Service::LmsSha256M24H10W1Verify,
2005            Service::LmsSha256M24H10W2Sign,
2006            Service::LmsSha256M24H10W2Verify,
2007            Service::LmsSha256M24H10W4Sign,
2008            Service::LmsSha256M24H10W4Verify,
2009            Service::LmsSha256M24H10W8Sign,
2010            Service::LmsSha256M24H10W8Verify,
2011            Service::LmsSha256M24H15W1Sign,
2012            Service::LmsSha256M24H15W1Verify,
2013            Service::LmsSha256M24H15W2Sign,
2014            Service::LmsSha256M24H15W2Verify,
2015            Service::LmsSha256M24H15W4Sign,
2016            Service::LmsSha256M24H15W4Verify,
2017            Service::LmsSha256M24H15W8Sign,
2018            Service::LmsSha256M24H15W8Verify,
2019            Service::LmsSha256M24H20W1Sign,
2020            Service::LmsSha256M24H20W1Verify,
2021            Service::LmsSha256M24H20W2Sign,
2022            Service::LmsSha256M24H20W2Verify,
2023            Service::LmsSha256M24H20W4Sign,
2024            Service::LmsSha256M24H20W4Verify,
2025            Service::LmsSha256M24H20W8Sign,
2026            Service::LmsSha256M24H20W8Verify,
2027            Service::LmsSha256M24H25W1Sign,
2028            Service::LmsSha256M24H25W1Verify,
2029            Service::LmsSha256M24H25W2Sign,
2030            Service::LmsSha256M24H25W2Verify,
2031            Service::LmsSha256M24H25W4Sign,
2032            Service::LmsSha256M24H25W4Verify,
2033            Service::LmsSha256M24H25W8Sign,
2034            Service::LmsSha256M24H25W8Verify,
2035            // SHAKE-256 / N=32 family (20 pairs, 40 entries).
2036            Service::LmsShakeM32H5W1Sign,
2037            Service::LmsShakeM32H5W1Verify,
2038            Service::LmsShakeM32H5W2Sign,
2039            Service::LmsShakeM32H5W2Verify,
2040            Service::LmsShakeM32H5W4Sign,
2041            Service::LmsShakeM32H5W4Verify,
2042            Service::LmsShakeM32H5W8Sign,
2043            Service::LmsShakeM32H5W8Verify,
2044            Service::LmsShakeM32H10W1Sign,
2045            Service::LmsShakeM32H10W1Verify,
2046            Service::LmsShakeM32H10W2Sign,
2047            Service::LmsShakeM32H10W2Verify,
2048            Service::LmsShakeM32H10W4Sign,
2049            Service::LmsShakeM32H10W4Verify,
2050            Service::LmsShakeM32H10W8Sign,
2051            Service::LmsShakeM32H10W8Verify,
2052            Service::LmsShakeM32H15W1Sign,
2053            Service::LmsShakeM32H15W1Verify,
2054            Service::LmsShakeM32H15W2Sign,
2055            Service::LmsShakeM32H15W2Verify,
2056            Service::LmsShakeM32H15W4Sign,
2057            Service::LmsShakeM32H15W4Verify,
2058            Service::LmsShakeM32H15W8Sign,
2059            Service::LmsShakeM32H15W8Verify,
2060            Service::LmsShakeM32H20W1Sign,
2061            Service::LmsShakeM32H20W1Verify,
2062            Service::LmsShakeM32H20W2Sign,
2063            Service::LmsShakeM32H20W2Verify,
2064            Service::LmsShakeM32H20W4Sign,
2065            Service::LmsShakeM32H20W4Verify,
2066            Service::LmsShakeM32H20W8Sign,
2067            Service::LmsShakeM32H20W8Verify,
2068            Service::LmsShakeM32H25W1Sign,
2069            Service::LmsShakeM32H25W1Verify,
2070            Service::LmsShakeM32H25W2Sign,
2071            Service::LmsShakeM32H25W2Verify,
2072            Service::LmsShakeM32H25W4Sign,
2073            Service::LmsShakeM32H25W4Verify,
2074            Service::LmsShakeM32H25W8Sign,
2075            Service::LmsShakeM32H25W8Verify,
2076            // SHAKE-256 / N=24 family (20 pairs, 40 entries).
2077            Service::LmsShakeM24H5W1Sign,
2078            Service::LmsShakeM24H5W1Verify,
2079            Service::LmsShakeM24H5W2Sign,
2080            Service::LmsShakeM24H5W2Verify,
2081            Service::LmsShakeM24H5W4Sign,
2082            Service::LmsShakeM24H5W4Verify,
2083            Service::LmsShakeM24H5W8Sign,
2084            Service::LmsShakeM24H5W8Verify,
2085            Service::LmsShakeM24H10W1Sign,
2086            Service::LmsShakeM24H10W1Verify,
2087            Service::LmsShakeM24H10W2Sign,
2088            Service::LmsShakeM24H10W2Verify,
2089            Service::LmsShakeM24H10W4Sign,
2090            Service::LmsShakeM24H10W4Verify,
2091            Service::LmsShakeM24H10W8Sign,
2092            Service::LmsShakeM24H10W8Verify,
2093            Service::LmsShakeM24H15W1Sign,
2094            Service::LmsShakeM24H15W1Verify,
2095            Service::LmsShakeM24H15W2Sign,
2096            Service::LmsShakeM24H15W2Verify,
2097            Service::LmsShakeM24H15W4Sign,
2098            Service::LmsShakeM24H15W4Verify,
2099            Service::LmsShakeM24H15W8Sign,
2100            Service::LmsShakeM24H15W8Verify,
2101            Service::LmsShakeM24H20W1Sign,
2102            Service::LmsShakeM24H20W1Verify,
2103            Service::LmsShakeM24H20W2Sign,
2104            Service::LmsShakeM24H20W2Verify,
2105            Service::LmsShakeM24H20W4Sign,
2106            Service::LmsShakeM24H20W4Verify,
2107            Service::LmsShakeM24H20W8Sign,
2108            Service::LmsShakeM24H20W8Verify,
2109            Service::LmsShakeM24H25W1Sign,
2110            Service::LmsShakeM24H25W1Verify,
2111            Service::LmsShakeM24H25W2Sign,
2112            Service::LmsShakeM24H25W2Verify,
2113            Service::LmsShakeM24H25W4Sign,
2114            Service::LmsShakeM24H25W4Verify,
2115            Service::LmsShakeM24H25W8Sign,
2116            Service::LmsShakeM24H25W8Verify,
2117        ]
2118    }
2119
2120    #[test]
2121    fn lms_gating_is_exhaustive_across_all_160_variants() {
2122        // Enumerates every LMS Service variant (80 (LMS, LM-OTS) pairs ×
2123        // {Sign, Verify} = 160 entries) and verifies that all 160 are
2124        // permitted under all three profiles: CNSSP-15 Annex B places no
2125        // subset restriction on LMS, and Unrestricted permits everything.
2126        // If a new LMS variant is added without a matching entry in
2127        // `lms_all_160`, the length assertion catches the drift.
2128        let all_160 = lms_all_160();
2129        assert_eq!(all_160.len(), 160, "LMS enumeration drift");
2130
2131        for svc in all_160 {
2132            // Unrestricted: every LMS variant is permitted.
2133            assert!(
2134                is_allowed(AlgorithmProfile::Unrestricted, svc),
2135                "{svc} must be allowed in Unrestricted"
2136            );
2137
2138            // CNSA 2.0: every approved SP 800-208 parameter set is permitted.
2139            // CNSSP-15 Annex B places no subset restriction on LMS.
2140            assert!(
2141                is_allowed(AlgorithmProfile::Cnsa2, svc),
2142                "{svc} must be allowed in CNSA 2.0"
2143            );
2144
2145            // CNSA 1.0: mirrors CNSA 2.0 on LMS (transition profile).
2146            assert!(
2147                is_allowed(AlgorithmProfile::Cnsa1, svc),
2148                "{svc} must be allowed in CNSA 1.0"
2149            );
2150        }
2151    }
2152
2153    #[test]
2154    fn lms_discriminant_block_is_contiguous_and_pure() {
2155        // `is_lms_service` range-tests `500..=659` instead of enumerating 160
2156        // arms, which is only sound while that block holds every LMS service
2157        // and nothing else. Enum discriminants are unique, so proving the 160
2158        // LMS variants occupy the range exactly also proves no other variant
2159        // can sit inside it.
2160        let mut discriminants = [0u16; 160];
2161        for (slot, svc) in discriminants.iter_mut().zip(lms_all_160()) {
2162            *slot = svc as u16;
2163        }
2164        discriminants.sort_unstable();
2165
2166        // Strictly increasing after sorting == all 160 are distinct. The
2167        // comparison count is asserted too, so an empty or short sweep
2168        // cannot pass this vacuously.
2169        let mut compared = 0usize;
2170        for pair in discriminants.windows(2) {
2171            let [lo, hi] = pair else {
2172                unreachable!("windows(2) yields two-element slices")
2173            };
2174            assert!(hi > lo, "duplicate LMS discriminant {lo}");
2175            compared += 1;
2176        }
2177        assert_eq!(compared, 159, "distinctness sweep did not run in full");
2178
2179        let (Some(&first), Some(&last)) = (discriminants.first(), discriminants.last()) else {
2180            unreachable!("the array holds 160 elements")
2181        };
2182        assert_eq!(first, 500, "LMS block does not start at 500");
2183        assert_eq!(last, 659, "LMS block does not end at 659");
2184        // 160 distinct values spanning an inclusive range of 160 is exactly
2185        // full occupancy — no gap a non-LMS variant could occupy.
2186        assert_eq!(last - first + 1, 160, "LMS block is not contiguous");
2187
2188        for svc in lms_all_160() {
2189            assert!(is_lms_service(svc), "{svc} must be inside the LMS range");
2190        }
2191
2192        // Positive control on the other side of the boundary: the nearest
2193        // non-LMS neighbours must not be swept in by a widened range.
2194        for svc in [
2195            Service::SlhDsaShake256fVerify,
2196            Service::XmssSign,
2197            Service::XmssVerify,
2198            Service::Dh3072,
2199        ] {
2200            assert!(!is_lms_service(svc), "{svc} must be outside the LMS range");
2201        }
2202    }
2203
2204    #[test]
2205    fn slh_dsa_gating_is_exhaustive_across_all_36_variants() {
2206        // Enumerates every SLH-DSA Service variant (12 parameter sets ×
2207        // {Keygen, Sign, Verify} = 36 entries) and verifies the
2208        // fail-safe gating: all 36 are blocked under both CNSA profiles,
2209        // because CNSSP-15 lists no SLH-DSA parameter set at all; all 36
2210        // are permitted under Unrestricted.  If a new SLH-DSA variant is
2211        // added without a matching entry in this array, the assertion at
2212        // the end catches the gap.
2213        let all_36 = [
2214            // SHA-2 family (18 entries).
2215            Service::SlhDsaSha2256sKeygen,
2216            Service::SlhDsaSha2256sSign,
2217            Service::SlhDsaSha2256sVerify,
2218            Service::SlhDsaSha2128sKeygen,
2219            Service::SlhDsaSha2128sSign,
2220            Service::SlhDsaSha2128sVerify,
2221            Service::SlhDsaSha2128fKeygen,
2222            Service::SlhDsaSha2128fSign,
2223            Service::SlhDsaSha2128fVerify,
2224            Service::SlhDsaSha2192sKeygen,
2225            Service::SlhDsaSha2192sSign,
2226            Service::SlhDsaSha2192sVerify,
2227            Service::SlhDsaSha2192fKeygen,
2228            Service::SlhDsaSha2192fSign,
2229            Service::SlhDsaSha2192fVerify,
2230            Service::SlhDsaSha2256fKeygen,
2231            Service::SlhDsaSha2256fSign,
2232            Service::SlhDsaSha2256fVerify,
2233            // SHAKE family (18 entries).
2234            Service::SlhDsaShake128sKeygen,
2235            Service::SlhDsaShake128sSign,
2236            Service::SlhDsaShake128sVerify,
2237            Service::SlhDsaShake128fKeygen,
2238            Service::SlhDsaShake128fSign,
2239            Service::SlhDsaShake128fVerify,
2240            Service::SlhDsaShake192sKeygen,
2241            Service::SlhDsaShake192sSign,
2242            Service::SlhDsaShake192sVerify,
2243            Service::SlhDsaShake192fKeygen,
2244            Service::SlhDsaShake192fSign,
2245            Service::SlhDsaShake192fVerify,
2246            Service::SlhDsaShake256sKeygen,
2247            Service::SlhDsaShake256sSign,
2248            Service::SlhDsaShake256sVerify,
2249            Service::SlhDsaShake256fKeygen,
2250            Service::SlhDsaShake256fSign,
2251            Service::SlhDsaShake256fVerify,
2252        ];
2253        assert_eq!(all_36.len(), 36, "SLH-DSA enumeration drift");
2254
2255        for svc in all_36 {
2256            // Unrestricted: every SLH-DSA variant is permitted.
2257            assert!(
2258                is_allowed(AlgorithmProfile::Unrestricted, svc),
2259                "{svc} must be allowed in Unrestricted"
2260            );
2261
2262            // CNSA 1.0: no SLH-DSA variant is permitted.
2263            assert!(
2264                !is_allowed(AlgorithmProfile::Cnsa1, svc),
2265                "{svc} must be BLOCKED in CNSA 1.0"
2266            );
2267
2268            // CNSA 2.0: no SLH-DSA variant is permitted either. CNSSP-15
2269            // contains no SLH-DSA entry, so admitting one would be a
2270            // conformance claim the policy does not support.
2271            assert!(
2272                !is_allowed(AlgorithmProfile::Cnsa2, svc),
2273                "{svc} must be BLOCKED in CNSA 2.0"
2274            );
2275        }
2276    }
2277
2278    #[test]
2279    fn require_allowed_returns_restricted_error_under_cnsa2() {
2280        let _guard = reset_for_test();
2281        initialize_with_profile(&[], AlgorithmProfile::Cnsa2).unwrap();
2282        match require_allowed(Service::Aes128Ecb) {
2283            Err(Error::AlgorithmRestricted {
2284                service: Service::Aes128Ecb,
2285            }) => {}
2286            other => panic!("expected AlgorithmRestricted(Aes128Ecb), got {other:?}"),
2287        }
2288    }
2289
2290    #[test]
2291    fn require_allowed_passes_permitted_services() {
2292        let _guard = reset_for_test();
2293        initialize_with_profile(&[], AlgorithmProfile::Cnsa2).unwrap();
2294        require_allowed(Service::Aes256Gcm).unwrap();
2295        require_allowed(Service::Sha384).unwrap();
2296        require_allowed(Service::MlKem1024Encaps).unwrap();
2297    }
2298
2299    #[test]
2300    fn require_allowed_passes_everything_in_unrestricted() {
2301        let _guard = reset_for_test();
2302        initialize().unwrap();
2303        // Spot-check a few from each end of the spectrum.
2304        require_allowed(Service::Sha1).unwrap();
2305        require_allowed(Service::Aes128Ecb).unwrap();
2306        require_allowed(Service::Ed25519Sign).unwrap();
2307        require_allowed(Service::MlKem1024Encaps).unwrap();
2308    }
2309
2310    /// Entry-point coverage for the SLH-DSA correction. The exhaustive
2311    /// `slh_dsa_gating_*` test calls the private `is_allowed` helper directly,
2312    /// so it says nothing about whether the public gate reaches that helper.
2313    /// This one goes through `require_allowed`, which is what every approved
2314    /// service actually calls.
2315    #[test]
2316    fn require_allowed_refuses_every_slh_dsa_service_under_cnsa2() {
2317        let guard = reset_for_test();
2318        for profile in [AlgorithmProfile::Cnsa2, AlgorithmProfile::Cnsa1] {
2319            guard.reset();
2320            initialize_with_profile(&[], profile).unwrap();
2321            for svc in [
2322                Service::SlhDsaSha2256sKeygen,
2323                Service::SlhDsaSha2256sSign,
2324                Service::SlhDsaSha2256sVerify,
2325                Service::SlhDsaShake128fVerify,
2326            ] {
2327                match require_allowed(svc) {
2328                    Err(Error::AlgorithmRestricted { service }) => assert_eq!(service, svc),
2329                    other => {
2330                        panic!("expected AlgorithmRestricted({svc}) under {profile}, got {other:?}")
2331                    }
2332                }
2333            }
2334        }
2335    }
2336
2337    /// Entry-point coverage for the LMS correction, on the `M=24` sets.
2338    /// CNSSP-15 Annex B recommends LMS-SHA-256/192, which is `M=24`, so these
2339    /// are the parameter sets the policy names by name.
2340    #[test]
2341    fn require_allowed_admits_lms_m24_under_both_cnsa_profiles() {
2342        let guard = reset_for_test();
2343        for profile in [AlgorithmProfile::Cnsa2, AlgorithmProfile::Cnsa1] {
2344            guard.reset();
2345            initialize_with_profile(&[], profile).unwrap();
2346            for svc in [
2347                Service::LmsSha256M24H5W1Sign,
2348                Service::LmsSha256M24H25W8Verify,
2349                Service::LmsShakeM24H10W4Sign,
2350                Service::LmsShakeM24H25W8Verify,
2351            ] {
2352                require_allowed(svc)
2353                    .unwrap_or_else(|e| panic!("{svc} must be allowed under {profile}: {e:?}"));
2354            }
2355        }
2356    }
2357
2358    /// Both edges of the LMS discriminant block, over raw values.
2359    ///
2360    /// The block sits at the very top of the enum, so a widened *upper* bound
2361    /// admits nothing today and no `Service`-typed probe can see it — moving
2362    /// the bound to 709 leaves the whole suite green. Testing the arithmetic
2363    /// directly is what makes the upper edge checkable at all.
2364    #[test]
2365    fn lms_block_boundaries_are_exact() {
2366        assert!(!lms_block_contains(499), "499 is below the LMS block");
2367        assert!(lms_block_contains(500), "500 is the first LMS discriminant");
2368        assert!(lms_block_contains(659), "659 is the last LMS discriminant");
2369        assert!(!lms_block_contains(660), "660 is above the LMS block");
2370
2371        // ...and the block the arithmetic describes is the one the enum has.
2372        assert_eq!(Service::LmsSha256M32H5W1Sign as u16, 500);
2373        assert_eq!(Service::LmsShakeM24H25W8Verify as u16, 659);
2374    }
2375
2376    /// The two CNSA profiles must be told apart at the entry point. Every other
2377    /// `require_allowed` test uses a service the profiles treat alike, so a bug
2378    /// wiring `Cnsa1` through as `Cnsa2` would survive them all. P-384 ECDSA is
2379    /// the discriminator: CNSA 1.0 permits it, CNSA 2.0 does not.
2380    #[test]
2381    fn require_allowed_distinguishes_cnsa1_from_cnsa2() {
2382        let guard = reset_for_test();
2383
2384        initialize_with_profile(&[], AlgorithmProfile::Cnsa1).unwrap();
2385        require_allowed(Service::EcdsaP384Sign)
2386            .unwrap_or_else(|e| panic!("P-384 ECDSA must be permitted under CNSA 1.0: {e:?}"));
2387
2388        guard.reset();
2389        initialize_with_profile(&[], AlgorithmProfile::Cnsa2).unwrap();
2390        match require_allowed(Service::EcdsaP384Sign) {
2391            Err(Error::AlgorithmRestricted {
2392                service: Service::EcdsaP384Sign,
2393            }) => {}
2394            other => panic!("expected P-384 ECDSA refused under CNSA 2.0, got {other:?}"),
2395        }
2396    }
2397
2398    #[test]
2399    fn algorithm_profile_display() {
2400        assert_eq!(AlgorithmProfile::Unrestricted.to_string(), "Unrestricted");
2401        assert_eq!(AlgorithmProfile::Cnsa2.to_string(), "CNSA 2.0");
2402        assert_eq!(AlgorithmProfile::Cnsa1.to_string(), "CNSA 1.0");
2403    }
2404
2405    #[test]
2406    fn algorithm_restricted_error_display() {
2407        let _guard = reset_for_test();
2408        initialize_with_profile(&[], AlgorithmProfile::Cnsa2).unwrap();
2409        let err = Error::AlgorithmRestricted {
2410            service: Service::Aes128Ecb,
2411        };
2412        let msg = err.to_string();
2413        assert!(msg.contains("AES-128-ECB"), "got: {msg}");
2414        assert!(msg.contains("CNSA 2.0"), "got: {msg}");
2415    }
2416
2417    #[test]
2418    fn not_implemented_error_display() {
2419        let err = Error::NotImplemented;
2420        assert_eq!(err.to_string(), "algorithm not yet implemented");
2421    }
2422
2423    #[test]
2424    fn service_display_is_stable() {
2425        // Pin a few representative Display strings.
2426        assert_eq!(Service::Sha256.to_string(), "SHA-256");
2427        assert_eq!(Service::Aes256Gcm.to_string(), "AES-256-GCM");
2428        assert_eq!(Service::MlKem1024Encaps.to_string(), "ML-KEM-1024 encaps");
2429        assert_eq!(Service::Ed25519Sign.to_string(), "Ed25519 sign");
2430    }
2431}