aws_lc_rs/
kem.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0 OR ISC
3
4//! Key-Encapsulation Mechanisms (KEMs), including support for Kyber Round 3 Submission.
5//!
6//! # Example
7//!
8//! Note that this example uses the Kyber-512 Round 3 algorithm, but other algorithms can be used
9//! in the exact same way by substituting
10//! `kem::<desired_algorithm_here>` for `kem::KYBER512_R3`.
11//!
12//! ```rust
13//! use aws_lc_rs::{
14//!     kem::{Ciphertext, DecapsulationKey, EncapsulationKey},
15//!     kem::{ML_KEM_512}
16//! };
17//!
18//! // Alice generates their (private) decapsulation key.
19//! let decapsulation_key = DecapsulationKey::generate(&ML_KEM_512)?;
20//!
21//! // Alices computes the (public) encapsulation key.
22//! let encapsulation_key = decapsulation_key.encapsulation_key()?;
23//!
24//! let encapsulation_key_bytes = encapsulation_key.key_bytes()?;
25//!
26//! // Alice sends the encapsulation key bytes to bob through some
27//! // protocol message.
28//! let encapsulation_key_bytes = encapsulation_key_bytes.as_ref();
29//!
30//! // Bob constructs the (public) encapsulation key from the key bytes provided by Alice.
31//! let retrieved_encapsulation_key = EncapsulationKey::new(&ML_KEM_512, encapsulation_key_bytes)?;
32//!
33//! // Bob executes the encapsulation algorithm to to produce their copy of the secret, and associated ciphertext.
34//! let (ciphertext, bob_secret) = retrieved_encapsulation_key.encapsulate()?;
35//!
36//! // Alice receives ciphertext bytes from bob
37//! let ciphertext_bytes = ciphertext.as_ref();
38//!
39//! // Bob sends Alice the ciphertext computed from the encapsulation algorithm, Alice runs decapsulation to derive their
40//! // copy of the secret.
41//! let alice_secret = decapsulation_key.decapsulate(Ciphertext::from(ciphertext_bytes))?;
42//!
43//! // Alice and Bob have now arrived to the same secret
44//! assert_eq!(alice_secret.as_ref(), bob_secret.as_ref());
45//!
46//! # Ok::<(), aws_lc_rs::error::Unspecified>(())
47//! ```
48use crate::aws_lc::{
49    EVP_PKEY_CTX_kem_set_params, EVP_PKEY_decapsulate, EVP_PKEY_encapsulate,
50    EVP_PKEY_kem_new_raw_public_key, EVP_PKEY, EVP_PKEY_KEM,
51};
52use crate::buffer::Buffer;
53use crate::encoding::generated_encodings;
54use crate::error::{KeyRejected, Unspecified};
55use crate::ptr::LcPtr;
56use alloc::borrow::Cow;
57use core::cmp::Ordering;
58use zeroize::Zeroize;
59
60const ML_KEM_512_SHARED_SECRET_LENGTH: usize = 32;
61const ML_KEM_512_PUBLIC_KEY_LENGTH: usize = 800;
62const ML_KEM_512_SECRET_KEY_LENGTH: usize = 1632;
63const ML_KEM_512_CIPHERTEXT_LENGTH: usize = 768;
64
65const ML_KEM_768_SHARED_SECRET_LENGTH: usize = 32;
66const ML_KEM_768_PUBLIC_KEY_LENGTH: usize = 1184;
67const ML_KEM_768_SECRET_KEY_LENGTH: usize = 2400;
68const ML_KEM_768_CIPHERTEXT_LENGTH: usize = 1088;
69
70const ML_KEM_1024_SHARED_SECRET_LENGTH: usize = 32;
71const ML_KEM_1024_PUBLIC_KEY_LENGTH: usize = 1568;
72const ML_KEM_1024_SECRET_KEY_LENGTH: usize = 3168;
73const ML_KEM_1024_CIPHERTEXT_LENGTH: usize = 1568;
74
75/// NIST FIPS 203 ML-KEM-512 algorithm.
76pub const ML_KEM_512: Algorithm<AlgorithmId> = Algorithm {
77    id: AlgorithmId::MlKem512,
78    decapsulate_key_size: ML_KEM_512_SECRET_KEY_LENGTH,
79    encapsulate_key_size: ML_KEM_512_PUBLIC_KEY_LENGTH,
80    ciphertext_size: ML_KEM_512_CIPHERTEXT_LENGTH,
81    shared_secret_size: ML_KEM_512_SHARED_SECRET_LENGTH,
82};
83
84/// NIST FIPS 203 ML-KEM-768 algorithm.
85pub const ML_KEM_768: Algorithm<AlgorithmId> = Algorithm {
86    id: AlgorithmId::MlKem768,
87    decapsulate_key_size: ML_KEM_768_SECRET_KEY_LENGTH,
88    encapsulate_key_size: ML_KEM_768_PUBLIC_KEY_LENGTH,
89    ciphertext_size: ML_KEM_768_CIPHERTEXT_LENGTH,
90    shared_secret_size: ML_KEM_768_SHARED_SECRET_LENGTH,
91};
92
93/// NIST FIPS 203 ML-KEM-1024 algorithm.
94pub const ML_KEM_1024: Algorithm<AlgorithmId> = Algorithm {
95    id: AlgorithmId::MlKem1024,
96    decapsulate_key_size: ML_KEM_1024_SECRET_KEY_LENGTH,
97    encapsulate_key_size: ML_KEM_1024_PUBLIC_KEY_LENGTH,
98    ciphertext_size: ML_KEM_1024_CIPHERTEXT_LENGTH,
99    shared_secret_size: ML_KEM_1024_SHARED_SECRET_LENGTH,
100};
101
102use crate::aws_lc::{NID_MLKEM1024, NID_MLKEM512, NID_MLKEM768};
103
104/// An identifier for a KEM algorithm.
105pub trait AlgorithmIdentifier:
106    Copy + Clone + Debug + PartialEq + crate::sealed::Sealed + 'static
107{
108    /// Returns the algorithm's associated AWS-LC nid.
109    fn nid(self) -> i32;
110}
111
112/// A KEM algorithm
113#[derive(PartialEq)]
114pub struct Algorithm<Id = AlgorithmId>
115where
116    Id: AlgorithmIdentifier,
117{
118    pub(crate) id: Id,
119    pub(crate) decapsulate_key_size: usize,
120    pub(crate) encapsulate_key_size: usize,
121    pub(crate) ciphertext_size: usize,
122    pub(crate) shared_secret_size: usize,
123}
124
125impl<Id> Algorithm<Id>
126where
127    Id: AlgorithmIdentifier,
128{
129    /// Returns the identifier for this algorithm.
130    #[must_use]
131    pub fn id(&self) -> Id {
132        self.id
133    }
134
135    #[inline]
136    #[allow(dead_code)]
137    pub(crate) fn decapsulate_key_size(&self) -> usize {
138        self.decapsulate_key_size
139    }
140
141    #[inline]
142    pub(crate) fn encapsulate_key_size(&self) -> usize {
143        self.encapsulate_key_size
144    }
145
146    #[inline]
147    pub(crate) fn ciphertext_size(&self) -> usize {
148        self.ciphertext_size
149    }
150
151    #[inline]
152    pub(crate) fn shared_secret_size(&self) -> usize {
153        self.shared_secret_size
154    }
155}
156
157impl<Id> Debug for Algorithm<Id>
158where
159    Id: AlgorithmIdentifier,
160{
161    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
162        Debug::fmt(&self.id, f)
163    }
164}
165
166/// A serializable decapulsation key usable with KEMs. This can be randomly generated with `DecapsulationKey::generate`.
167pub struct DecapsulationKey<Id = AlgorithmId>
168where
169    Id: AlgorithmIdentifier,
170{
171    algorithm: &'static Algorithm<Id>,
172    evp_pkey: LcPtr<EVP_PKEY>,
173}
174
175/// Identifier for a KEM algorithm.
176#[non_exhaustive]
177#[derive(Clone, Copy, Debug, PartialEq)]
178pub enum AlgorithmId {
179    /// NIST FIPS 203 ML-KEM-512 algorithm.
180    MlKem512,
181
182    /// NIST FIPS 203 ML-KEM-768 algorithm.
183    MlKem768,
184
185    /// NIST FIPS 203 ML-KEM-1024 algorithm.
186    MlKem1024,
187}
188
189impl AlgorithmIdentifier for AlgorithmId {
190    fn nid(self) -> i32 {
191        match self {
192            AlgorithmId::MlKem512 => NID_MLKEM512,
193            AlgorithmId::MlKem768 => NID_MLKEM768,
194            AlgorithmId::MlKem1024 => NID_MLKEM1024,
195        }
196    }
197}
198
199impl crate::sealed::Sealed for AlgorithmId {}
200
201impl<Id> DecapsulationKey<Id>
202where
203    Id: AlgorithmIdentifier,
204{
205    /// Generate a new KEM decapsulation key for the given algorithm.
206    ///
207    /// # Errors
208    /// `error::Unspecified` when operation fails due to internal error.
209    pub fn generate(alg: &'static Algorithm<Id>) -> Result<Self, Unspecified> {
210        let kyber_key = kem_key_generate(alg.id.nid())?;
211        Ok(DecapsulationKey {
212            algorithm: alg,
213            evp_pkey: kyber_key,
214        })
215    }
216
217    /// Return the algorithm associated with the given KEM decapsulation key.
218    #[must_use]
219    pub fn algorithm(&self) -> &'static Algorithm<Id> {
220        self.algorithm
221    }
222
223    /// Computes the KEM encapsulation key from the KEM decapsulation key.
224    ///
225    /// # Errors
226    /// `error::Unspecified` when operation fails due to internal error.
227    #[allow(clippy::missing_panics_doc)]
228    pub fn encapsulation_key(&self) -> Result<EncapsulationKey<Id>, Unspecified> {
229        let evp_pkey = self.evp_pkey.clone();
230
231        Ok(EncapsulationKey {
232            algorithm: self.algorithm,
233            evp_pkey,
234        })
235    }
236
237    /// Performs the decapsulate operation using this KEM decapsulation key on the given ciphertext.
238    ///
239    /// `ciphertext` is the ciphertext generated by the encapsulate operation using the KEM encapsulation key
240    /// associated with this KEM decapsulation key.
241    ///
242    /// # Errors
243    /// `Unspecified` when operation fails due to internal error.
244    #[allow(clippy::needless_pass_by_value)]
245    pub fn decapsulate(&self, ciphertext: Ciphertext<'_>) -> Result<SharedSecret, Unspecified> {
246        let mut shared_secret_len = self.algorithm.shared_secret_size();
247        let mut shared_secret: Vec<u8> = vec![0u8; shared_secret_len];
248
249        let mut ctx = self.evp_pkey.create_EVP_PKEY_CTX()?;
250
251        let ciphertext = ciphertext.as_ref();
252
253        if 1 != unsafe {
254            EVP_PKEY_decapsulate(
255                *ctx.as_mut(),
256                shared_secret.as_mut_ptr(),
257                &mut shared_secret_len,
258                // AWS-LC incorrectly has this as an unqualified `uint8_t *`, it should be qualified with const
259                ciphertext.as_ptr() as *mut u8,
260                ciphertext.len(),
261            )
262        } {
263            return Err(Unspecified);
264        }
265
266        // This is currently pedantic but done for safety in-case the shared_secret buffer
267        // size changes in the future. `EVP_PKEY_decapsulate` updates `shared_secret_len` with
268        // the length of the shared secret in the event the buffer provided was larger then the secret.
269        // This truncates the buffer to the proper length to match the shared secret written.
270        debug_assert_eq!(shared_secret_len, shared_secret.len());
271        shared_secret.truncate(shared_secret_len);
272
273        Ok(SharedSecret(shared_secret.into_boxed_slice()))
274    }
275}
276
277unsafe impl<Id> Send for DecapsulationKey<Id> where Id: AlgorithmIdentifier {}
278
279unsafe impl<Id> Sync for DecapsulationKey<Id> where Id: AlgorithmIdentifier {}
280
281impl<Id> Debug for DecapsulationKey<Id>
282where
283    Id: AlgorithmIdentifier,
284{
285    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
286        f.debug_struct("DecapsulationKey")
287            .field("algorithm", &self.algorithm)
288            .finish_non_exhaustive()
289    }
290}
291
292use paste::paste;
293
294generated_encodings!(EncapsulationKeyBytes);
295
296/// A serializable encapsulation key usable with KEM algorithms. Constructed
297/// from either a `DecapsulationKey` or raw bytes.
298pub struct EncapsulationKey<Id = AlgorithmId>
299where
300    Id: AlgorithmIdentifier,
301{
302    algorithm: &'static Algorithm<Id>,
303    evp_pkey: LcPtr<EVP_PKEY>,
304}
305
306impl<Id> EncapsulationKey<Id>
307where
308    Id: AlgorithmIdentifier,
309{
310    /// Return the algorithm associated with the given KEM encapsulation key.
311    #[must_use]
312    pub fn algorithm(&self) -> &'static Algorithm<Id> {
313        self.algorithm
314    }
315
316    /// Performs the encapsulate operation using this KEM encapsulation key, generating a ciphertext
317    /// and associated shared secret.
318    ///
319    /// # Errors
320    /// `error::Unspecified` when operation fails due to internal error.
321    pub fn encapsulate(&self) -> Result<(Ciphertext<'static>, SharedSecret), Unspecified> {
322        let mut ciphertext_len = self.algorithm.ciphertext_size();
323        let mut shared_secret_len = self.algorithm.shared_secret_size();
324        let mut ciphertext: Vec<u8> = vec![0u8; ciphertext_len];
325        let mut shared_secret: Vec<u8> = vec![0u8; shared_secret_len];
326
327        let mut ctx = self.evp_pkey.create_EVP_PKEY_CTX()?;
328
329        if 1 != unsafe {
330            EVP_PKEY_encapsulate(
331                *ctx.as_mut(),
332                ciphertext.as_mut_ptr(),
333                &mut ciphertext_len,
334                shared_secret.as_mut_ptr(),
335                &mut shared_secret_len,
336            )
337        } {
338            return Err(Unspecified);
339        }
340
341        // The following two steps are currently pedantic but done for safety in-case the buffer allocation
342        // sizes change in the future. `EVP_PKEY_encapsulate` updates `ciphertext_len` and `shared_secret_len` with
343        // the length of the ciphertext and shared secret respectivly in the event the buffer provided for each was
344        // larger then the actual values. Thus these two steps truncate the buffers to the proper length to match the
345        // value lengths written.
346        debug_assert_eq!(ciphertext_len, ciphertext.len());
347        ciphertext.truncate(ciphertext_len);
348        debug_assert_eq!(shared_secret_len, shared_secret.len());
349        shared_secret.truncate(shared_secret_len);
350
351        Ok((
352            Ciphertext::new(ciphertext),
353            SharedSecret::new(shared_secret.into_boxed_slice()),
354        ))
355    }
356
357    /// Returns the `EnscapsulationKey` bytes.
358    ///
359    /// # Errors
360    /// * `Unspecified`: Any failure to retrieve the `EnscapsulationKey` bytes.
361    pub fn key_bytes(&self) -> Result<EncapsulationKeyBytes<'static>, Unspecified> {
362        let mut encapsulate_bytes = vec![0u8; self.algorithm.encapsulate_key_size()];
363        let encapsulate_key_size = self
364            .evp_pkey
365            .marshal_raw_public_to_buffer(&mut encapsulate_bytes)?;
366
367        debug_assert_eq!(encapsulate_key_size, encapsulate_bytes.len());
368        encapsulate_bytes.truncate(encapsulate_key_size);
369
370        Ok(EncapsulationKeyBytes::new(encapsulate_bytes))
371    }
372
373    /// Creates a new KEM encapsulation key from raw bytes. This method MUST NOT be used to generate
374    /// a new encapsulation key, rather it MUST be used to construct `EncapsulationKey` previously serialized
375    /// to raw bytes.
376    ///
377    /// `alg` is the [`Algorithm`] to be associated with the generated `EncapsulationKey`.
378    ///
379    /// `bytes` is a slice of raw bytes representing a `EncapsulationKey`.
380    ///
381    /// # Errors
382    /// `error::KeyRejected` when operation fails during key creation.
383    pub fn new(alg: &'static Algorithm<Id>, bytes: &[u8]) -> Result<Self, KeyRejected> {
384        match bytes.len().cmp(&alg.encapsulate_key_size()) {
385            Ordering::Less => Err(KeyRejected::too_small()),
386            Ordering::Greater => Err(KeyRejected::too_large()),
387            Ordering::Equal => Ok(()),
388        }?;
389        let pubkey = LcPtr::new(unsafe {
390            EVP_PKEY_kem_new_raw_public_key(alg.id.nid(), bytes.as_ptr(), bytes.len())
391        })?;
392        Ok(EncapsulationKey {
393            algorithm: alg,
394            evp_pkey: pubkey,
395        })
396    }
397}
398
399unsafe impl<Id> Send for EncapsulationKey<Id> where Id: AlgorithmIdentifier {}
400
401unsafe impl<Id> Sync for EncapsulationKey<Id> where Id: AlgorithmIdentifier {}
402
403impl<Id> Debug for EncapsulationKey<Id>
404where
405    Id: AlgorithmIdentifier,
406{
407    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
408        f.debug_struct("EncapsulationKey")
409            .field("algorithm", &self.algorithm)
410            .finish_non_exhaustive()
411    }
412}
413
414/// A set of encrypted bytes produced by [`EncapsulationKey::encapsulate`],
415/// and used as an input to [`DecapsulationKey::decapsulate`].
416pub struct Ciphertext<'a>(Cow<'a, [u8]>);
417
418impl<'a> Ciphertext<'a> {
419    fn new(value: Vec<u8>) -> Ciphertext<'a> {
420        Self(Cow::Owned(value))
421    }
422}
423
424impl Drop for Ciphertext<'_> {
425    fn drop(&mut self) {
426        if let Cow::Owned(ref mut v) = self.0 {
427            v.zeroize();
428        }
429    }
430}
431
432impl AsRef<[u8]> for Ciphertext<'_> {
433    fn as_ref(&self) -> &[u8] {
434        match self.0 {
435            Cow::Borrowed(v) => v,
436            Cow::Owned(ref v) => v.as_ref(),
437        }
438    }
439}
440
441impl<'a> From<&'a [u8]> for Ciphertext<'a> {
442    fn from(value: &'a [u8]) -> Self {
443        Self(Cow::Borrowed(value))
444    }
445}
446
447/// The cryptographic shared secret output from the KEM encapsulate / decapsulate process.
448pub struct SharedSecret(Box<[u8]>);
449
450impl SharedSecret {
451    fn new(value: Box<[u8]>) -> Self {
452        Self(value)
453    }
454}
455
456impl Drop for SharedSecret {
457    fn drop(&mut self) {
458        self.0.zeroize();
459    }
460}
461
462impl AsRef<[u8]> for SharedSecret {
463    fn as_ref(&self) -> &[u8] {
464        self.0.as_ref()
465    }
466}
467
468// Returns an LcPtr to an EVP_PKEY
469#[inline]
470fn kem_key_generate(nid: i32) -> Result<LcPtr<EVP_PKEY>, Unspecified> {
471    let params_fn = |ctx| {
472        if 1 == unsafe { EVP_PKEY_CTX_kem_set_params(ctx, nid) } {
473            Ok(())
474        } else {
475            Err(())
476        }
477    };
478
479    LcPtr::<EVP_PKEY>::generate(EVP_PKEY_KEM, Some(params_fn))
480}
481
482#[cfg(test)]
483mod tests {
484    use super::{Ciphertext, DecapsulationKey, EncapsulationKey, SharedSecret};
485    use crate::error::KeyRejected;
486
487    use crate::kem::{ML_KEM_1024, ML_KEM_512, ML_KEM_768};
488
489    #[test]
490    fn ciphertext() {
491        let ciphertext_bytes = vec![42u8; 4];
492        let ciphertext = Ciphertext::from(ciphertext_bytes.as_ref());
493        assert_eq!(ciphertext.as_ref(), &[42, 42, 42, 42]);
494        drop(ciphertext);
495
496        let ciphertext_bytes = vec![42u8; 4];
497        let ciphertext = Ciphertext::<'static>::new(ciphertext_bytes);
498        assert_eq!(ciphertext.as_ref(), &[42, 42, 42, 42]);
499    }
500
501    #[test]
502    fn shared_secret() {
503        let secret_bytes = vec![42u8; 4];
504        let shared_secret = SharedSecret::new(secret_bytes.into_boxed_slice());
505        assert_eq!(shared_secret.as_ref(), &[42, 42, 42, 42]);
506    }
507
508    #[test]
509    fn test_kem_serialize() {
510        for algorithm in [&ML_KEM_512, &ML_KEM_768, &ML_KEM_1024] {
511            let priv_key = DecapsulationKey::generate(algorithm).unwrap();
512            assert_eq!(priv_key.algorithm(), algorithm);
513
514            let pub_key = priv_key.encapsulation_key().unwrap();
515            let pubkey_raw_bytes = pub_key.key_bytes().unwrap();
516            let pub_key_from_bytes =
517                EncapsulationKey::new(algorithm, pubkey_raw_bytes.as_ref()).unwrap();
518
519            assert_eq!(
520                pub_key.key_bytes().unwrap().as_ref(),
521                pub_key_from_bytes.key_bytes().unwrap().as_ref()
522            );
523            assert_eq!(pub_key.algorithm(), pub_key_from_bytes.algorithm());
524        }
525    }
526
527    #[test]
528    fn test_kem_wrong_sizes() {
529        for algorithm in [&ML_KEM_512, &ML_KEM_768, &ML_KEM_1024] {
530            let too_long_bytes = vec![0u8; algorithm.encapsulate_key_size() + 1];
531            let long_pub_key_from_bytes = EncapsulationKey::new(algorithm, &too_long_bytes);
532            assert_eq!(
533                long_pub_key_from_bytes.err(),
534                Some(KeyRejected::too_large())
535            );
536
537            let too_short_bytes = vec![0u8; algorithm.encapsulate_key_size() - 1];
538            let short_pub_key_from_bytes = EncapsulationKey::new(algorithm, &too_short_bytes);
539            assert_eq!(
540                short_pub_key_from_bytes.err(),
541                Some(KeyRejected::too_small())
542            );
543        }
544    }
545
546    #[test]
547    fn test_kem_e2e() {
548        for algorithm in [&ML_KEM_512, &ML_KEM_768, &ML_KEM_1024] {
549            let priv_key = DecapsulationKey::generate(algorithm).unwrap();
550            assert_eq!(priv_key.algorithm(), algorithm);
551
552            let pub_key = priv_key.encapsulation_key().unwrap();
553
554            let (alice_ciphertext, alice_secret) =
555                pub_key.encapsulate().expect("encapsulate successful");
556
557            let bob_secret = priv_key
558                .decapsulate(alice_ciphertext)
559                .expect("decapsulate successful");
560
561            assert_eq!(alice_secret.as_ref(), bob_secret.as_ref());
562        }
563    }
564
565    #[test]
566    fn test_serialized_kem_e2e() {
567        for algorithm in [&ML_KEM_512, &ML_KEM_768, &ML_KEM_1024] {
568            let priv_key = DecapsulationKey::generate(algorithm).unwrap();
569            assert_eq!(priv_key.algorithm(), algorithm);
570
571            let pub_key = priv_key.encapsulation_key().unwrap();
572
573            // Generate public key bytes to send to bob
574            let pub_key_bytes = pub_key.key_bytes().unwrap();
575
576            // Test that priv_key's EVP_PKEY isn't entirely freed since we remove this pub_key's reference.
577            drop(pub_key);
578
579            let retrieved_pub_key =
580                EncapsulationKey::new(algorithm, pub_key_bytes.as_ref()).unwrap();
581            let (ciphertext, bob_secret) = retrieved_pub_key
582                .encapsulate()
583                .expect("encapsulate successful");
584
585            let alice_secret = priv_key
586                .decapsulate(ciphertext)
587                .expect("decapsulate successful");
588
589            assert_eq!(alice_secret.as_ref(), bob_secret.as_ref());
590        }
591    }
592
593    #[test]
594    fn test_debug_fmt() {
595        let private = DecapsulationKey::generate(&ML_KEM_512).expect("successful generation");
596        assert_eq!(
597            format!("{private:?}"),
598            "DecapsulationKey { algorithm: MlKem512, .. }"
599        );
600        assert_eq!(
601            format!(
602                "{:?}",
603                private.encapsulation_key().expect("public key retrievable")
604            ),
605            "EncapsulationKey { algorithm: MlKem512, .. }"
606        );
607    }
608}