Skip to main content

dryoc/
kem.rs

1//! # Key encapsulation
2//!
3//! A key encapsulation mechanism (KEM) lets a sender create a fresh shared
4//! secret for the holder of a public key: [`encapsulate`] returns the shared
5//! secret and a ciphertext, and the recipient recovers the same secret with
6//! [`KeyPair::decapsulate`]. Only the recipient needs a key pair. Feed the
7//! shared secret to a key-derivation function such as [`crate::hkdf`] before
8//! using it as an encryption key. A KEM does not authenticate the sender;
9//! combine it with [`crate::sign`] or an authenticated protocol when that
10//! matters.
11//!
12//! The items at the top of this module are [`xwing`], the hybrid of
13//! ML-KEM-768 and X25519 that libsodium's `crypto_kem_*` functions use. It
14//! stays secure if either component does, so it protects against quantum
15//! computers without giving up the security of elliptic-curve cryptography.
16//! [`mlkem768`] offers ML-KEM-768 alone for protocols that require it.
17//!
18//! # Example
19//!
20//! ```
21//! use dryoc::kem::*;
22//!
23//! let recipient = StackKeyPair::generate();
24//!
25//! // The sender needs only the recipient's public key.
26//! let (ciphertext, sender_secret): (Ciphertext, SharedSecret) =
27//!     encapsulate(&recipient.public_key).expect("encapsulation failed");
28//!
29//! let recipient_secret: SharedSecret = recipient
30//!     .decapsulate(&ciphertext)
31//!     .expect("decapsulation failed");
32//! assert_eq!(sender_secret, recipient_secret);
33//! ```
34//!
35//! # Protected memory
36//!
37//! Every function is generic over its key, ciphertext and secret types, so
38//! secret keys and shared secrets can live in locked memory; see
39//! [`protected`] (with the `protected` feature).
40
41/// Generates one algorithm's typed API from its Classic functions.
42macro_rules! kem_api {
43    (
44        algorithm:
45        $algo:literal,classic:
46        $classic:ident,public_key_bytes:
47        $pk_bytes:expr,secret_key_bytes:
48        $sk_bytes:expr,ciphertext_bytes:
49        $ct_bytes:expr,shared_secret_bytes:
50        $ss_bytes:expr,seed_bytes:
51        $seed_bytes:expr,seed_keypair:
52        $seed_keypair:ident,enc:
53        $enc:ident,public_key_of: |
54        $sk:ident,
55        $pk:ident |
56        $public_key_of:expr,dec: |
57        $dec_ss:ident,
58        $dec_ct:ident,
59        $dec_sk:ident |
60        $dec:expr $(,)?
61    ) => {
62        use core::fmt;
63
64        #[cfg(feature = "serde")]
65        use serde::{Deserialize, Serialize};
66        use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
67
68        use crate::classic::$classic::{$enc, $seed_keypair};
69        use crate::error::Error;
70        use crate::rng::copy_randombytes;
71        use crate::types::*;
72
73        #[doc = concat!("Stack-allocated ", $algo, " public key.")]
74        pub type PublicKey = StackByteArray<{ $pk_bytes }>;
75        #[doc = concat!("Stack-allocated ", $algo, " secret key.")]
76        pub type SecretKey = StackByteArray<{ $sk_bytes }>;
77        #[doc = concat!("Stack-allocated ", $algo, " ciphertext.")]
78        pub type Ciphertext = StackByteArray<{ $ct_bytes }>;
79        #[doc = concat!("Stack-allocated ", $algo, " shared secret.")]
80        pub type SharedSecret = StackByteArray<{ $ss_bytes }>;
81        #[doc = concat!("Stack-allocated ", $algo, " key-generation seed.")]
82        pub type Seed = StackByteArray<{ $seed_bytes }>;
83        /// Stack-allocated key pair.
84        pub type StackKeyPair = KeyPair<PublicKey, SecretKey>;
85
86        #[derive(Zeroize, ZeroizeOnDrop, Clone)]
87        #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
88        #[doc = concat!("An ", $algo, " key pair.")]
89        pub struct KeyPair<
90            PublicKey: ByteArray<{ $pk_bytes }> + Zeroize,
91            SecretKey: ByteArray<{ $sk_bytes }> + Zeroize,
92        > {
93            /// Public key, shared with senders.
94            pub public_key: PublicKey,
95            /// Secret key.
96            pub secret_key: SecretKey,
97        }
98
99        impl<
100            PublicKey: ByteArray<{ $pk_bytes }> + Zeroize,
101            SecretKey: ByteArray<{ $sk_bytes }> + Zeroize,
102        > fmt::Debug for KeyPair<PublicKey, SecretKey>
103        {
104            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105                f.debug_struct("KeyPair")
106                    .field("public_key", &"[REDACTED]")
107                    .field("secret_key", &"[REDACTED]")
108                    .finish()
109            }
110        }
111
112        impl<
113            PublicKey: NewByteArray<{ $pk_bytes }> + Zeroize,
114            SecretKey: NewByteArray<{ $sk_bytes }> + Zeroize,
115        > KeyPair<PublicKey, SecretKey>
116        {
117            /// Generates a random key pair.
118            #[must_use]
119            pub fn generate() -> Self {
120                let mut seed = Zeroizing::new([0u8; $seed_bytes]);
121                copy_randombytes(seed.as_mut_slice());
122                Self::from_seed(&*seed)
123            }
124
125            /// Deterministically derives a key pair from `seed`.
126            #[must_use]
127            pub fn from_seed<Seed: ByteArray<{ $seed_bytes }>>(seed: &Seed) -> Self {
128                let mut public_key = PublicKey::new_byte_array();
129                let mut secret_key = SecretKey::new_byte_array();
130                $seed_keypair(
131                    public_key.as_mut_array(),
132                    secret_key.as_mut_array(),
133                    seed.as_array(),
134                );
135                Self {
136                    public_key,
137                    secret_key,
138                }
139            }
140        }
141
142        impl<
143            PublicKey: NewByteArray<{ $pk_bytes }> + Zeroize,
144            SecretKey: ByteArray<{ $sk_bytes }> + Zeroize,
145        > KeyPair<PublicKey, SecretKey>
146        {
147            /// Returns the key pair for `secret_key`, deriving its public key.
148            #[must_use]
149            pub fn from_secret_key(secret_key: SecretKey) -> Self {
150                let mut public_key = PublicKey::new_byte_array();
151                {
152                    let $sk = secret_key.as_array();
153                    let $pk = public_key.as_mut_array();
154                    $public_key_of;
155                }
156                Self {
157                    public_key,
158                    secret_key,
159                }
160            }
161        }
162
163        impl<
164            PublicKey: ByteArray<{ $pk_bytes }> + Zeroize,
165            SecretKey: ByteArray<{ $sk_bytes }> + Zeroize,
166        > KeyPair<PublicKey, SecretKey>
167        {
168            /// Recovers the shared secret that `ciphertext` encapsulates for
169            /// this key pair.
170            ///
171            /// A ciphertext that was not created for this key pair yields an
172            /// unrelated pseudorandom secret rather than an error, so the
173            /// result reveals nothing about why it differs.
174            ///
175            /// # Errors
176            ///
177            /// For X-Wing, returns [`Error::InvalidKey`] if the X25519 part
178            /// of `ciphertext` is a low-order point. ML-KEM-768 decapsulation
179            /// does not fail.
180            pub fn decapsulate<
181                SharedSecret: NewByteArray<{ $ss_bytes }>,
182                Ciphertext: ByteArray<{ $ct_bytes }>,
183            >(
184                &self,
185                ciphertext: &Ciphertext,
186            ) -> Result<SharedSecret, Error> {
187                let mut shared_secret = SharedSecret::new_byte_array();
188                {
189                    let $dec_ss = shared_secret.as_mut_array();
190                    let $dec_ct = ciphertext.as_array();
191                    let $dec_sk = self.secret_key.as_array();
192                    $dec?;
193                }
194                Ok(shared_secret)
195            }
196        }
197
198        #[doc = concat!(
199                    "Creates a random shared secret for `public_key`, returning the ", $algo,
200                    "\nciphertext to send to its owner and the shared secret."
201                )]
202        ///
203        /// # Errors
204        ///
205        /// Returns [`Error::InvalidKey`] if `public_key` is not a valid public
206        /// key.
207        pub fn encapsulate<
208            Ciphertext: NewByteArray<{ $ct_bytes }>,
209            SharedSecret: NewByteArray<{ $ss_bytes }>,
210            PublicKey: ByteArray<{ $pk_bytes }>,
211        >(
212            public_key: &PublicKey,
213        ) -> Result<(Ciphertext, SharedSecret), Error> {
214            let mut ciphertext = Ciphertext::new_byte_array();
215            let mut shared_secret = SharedSecret::new_byte_array();
216            $enc(
217                ciphertext.as_mut_array(),
218                shared_secret.as_mut_array(),
219                public_key.as_array(),
220            )?;
221            Ok((ciphertext, shared_secret))
222        }
223
224        #[cfg(any(all(feature = "protected", any(unix, windows)), all(doc, not(doctest), feature = "std")))]
225        #[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
226        pub mod protected {
227            //! # Protected memory type aliases
228            //!
229            //! Heap-allocated, page-aligned keys and secrets for use with
230            //! protected memory. [`encapsulate`](super::encapsulate) and
231            //! [`KeyPair::decapsulate`](super::KeyPair::decapsulate) return
232            //! any of these output types.
233            use super::*;
234            pub use crate::protected::*;
235
236            /// Heap-allocated public key.
237            pub type PublicKey = HeapByteArray<{ $pk_bytes }>;
238            /// Heap-allocated secret key.
239            pub type SecretKey = HeapByteArray<{ $sk_bytes }>;
240            /// Heap-allocated shared secret.
241            pub type SharedSecret = HeapByteArray<{ $ss_bytes }>;
242            /// Locked key pair.
243            pub type LockedKeyPair = KeyPair<Locked<PublicKey>, Locked<SecretKey>>;
244            /// Locked, read-only key pair.
245            pub type LockedROKeyPair = KeyPair<LockedRO<PublicKey>, LockedRO<SecretKey>>;
246
247            /// Returns a locked, randomly generated public and secret key.
248            fn generate_locked_parts() -> Result<(Locked<PublicKey>, Locked<SecretKey>), Error> {
249                let mut seed = HeapByteArray::<{ $seed_bytes }>::new_locked()?;
250                copy_randombytes(seed.as_mut_slice());
251                let mut public_key = PublicKey::new_locked()?;
252                let mut secret_key = SecretKey::new_locked()?;
253                $seed_keypair(
254                    public_key.as_mut_array(),
255                    secret_key.as_mut_array(),
256                    seed.as_array(),
257                );
258                Ok((public_key, secret_key))
259            }
260
261            impl LockedKeyPair {
262                /// Returns a new randomly generated locked key pair.
263                ///
264                /// # Errors
265                ///
266                /// Returns [`Error::Io`] if an allocation cannot be locked.
267                pub fn generate_locked_keypair() -> Result<Self, Error> {
268                    let (public_key, secret_key) = generate_locked_parts()?;
269                    Ok(Self {
270                        public_key,
271                        secret_key,
272                    })
273                }
274            }
275
276            impl LockedROKeyPair {
277                /// Returns a new randomly generated locked, read-only key pair.
278                ///
279                /// # Errors
280                ///
281                /// Returns [`Error::Io`] if an allocation cannot be locked or
282                /// made read-only.
283                pub fn generate_readonly_locked_keypair() -> Result<Self, Error> {
284                    let (public_key, secret_key) = generate_locked_parts()?;
285                    Ok(Self {
286                        public_key: public_key.mprotect_readonly()?,
287                        secret_key: secret_key.mprotect_readonly()?,
288                    })
289                }
290            }
291        }
292    };
293}
294
295pub mod xwing {
296    //! # X-Wing (ML-KEM-768 + X25519)
297    //!
298    //! The hybrid KEM behind libsodium's `crypto_kem_*` functions; see
299    //! [`crate::classic::crypto_kem_xwing`] for the construction. The secret
300    //! key is a 32-byte seed.
301    use crate::classic::crypto_kem_xwing::crypto_kem_xwing_dec;
302    use crate::constants::{
303        CRYPTO_KEM_XWING_CIPHERTEXTBYTES, CRYPTO_KEM_XWING_PUBLICKEYBYTES,
304        CRYPTO_KEM_XWING_SECRETKEYBYTES, CRYPTO_KEM_XWING_SEEDBYTES,
305        CRYPTO_KEM_XWING_SHAREDSECRETBYTES,
306    };
307
308    kem_api! {
309        algorithm: "X-Wing",
310        classic: crypto_kem_xwing,
311        public_key_bytes: CRYPTO_KEM_XWING_PUBLICKEYBYTES,
312        secret_key_bytes: CRYPTO_KEM_XWING_SECRETKEYBYTES,
313        ciphertext_bytes: CRYPTO_KEM_XWING_CIPHERTEXTBYTES,
314        shared_secret_bytes: CRYPTO_KEM_XWING_SHAREDSECRETBYTES,
315        seed_bytes: CRYPTO_KEM_XWING_SEEDBYTES,
316        seed_keypair: crypto_kem_xwing_seed_keypair_inplace,
317        enc: crypto_kem_xwing_enc,
318        // The secret key is the seed.
319        public_key_of: |sk, pk| crypto_kem_xwing_seed_keypair_inplace(
320            pk,
321            &mut Zeroizing::new([0u8; CRYPTO_KEM_XWING_SECRETKEYBYTES]),
322            sk,
323        ),
324        dec: |ss, ct, sk| crypto_kem_xwing_dec(ss, ct, sk),
325    }
326}
327
328pub mod mlkem768 {
329    //! # ML-KEM-768
330    //!
331    //! FIPS 203 ML-KEM-768 alone; see [`crate::classic::crypto_kem_mlkem768`].
332    //! Prefer [`super::xwing`] unless a protocol requires ML-KEM-768. The
333    //! secret key is FIPS 203's expanded decapsulation key, which contains
334    //! the public key.
335    use crate::classic::crypto_kem_mlkem768::crypto_kem_mlkem768_dec;
336    use crate::constants::{
337        CRYPTO_KEM_MLKEM768_CIPHERTEXTBYTES, CRYPTO_KEM_MLKEM768_PUBLICKEYBYTES,
338        CRYPTO_KEM_MLKEM768_SECRETKEYBYTES, CRYPTO_KEM_MLKEM768_SEEDBYTES,
339        CRYPTO_KEM_MLKEM768_SHAREDSECRETBYTES,
340    };
341
342    /// Where the public key sits inside the expanded secret key.
343    const PUBLIC_KEY_OFFSET: usize =
344        CRYPTO_KEM_MLKEM768_SECRETKEYBYTES - CRYPTO_KEM_MLKEM768_PUBLICKEYBYTES - 64;
345
346    kem_api! {
347        algorithm: "ML-KEM-768",
348        classic: crypto_kem_mlkem768,
349        public_key_bytes: CRYPTO_KEM_MLKEM768_PUBLICKEYBYTES,
350        secret_key_bytes: CRYPTO_KEM_MLKEM768_SECRETKEYBYTES,
351        ciphertext_bytes: CRYPTO_KEM_MLKEM768_CIPHERTEXTBYTES,
352        shared_secret_bytes: CRYPTO_KEM_MLKEM768_SHAREDSECRETBYTES,
353        seed_bytes: CRYPTO_KEM_MLKEM768_SEEDBYTES,
354        seed_keypair: crypto_kem_mlkem768_seed_keypair_inplace,
355        enc: crypto_kem_mlkem768_enc,
356        public_key_of: |sk, pk| pk.copy_from_slice(
357            &sk[PUBLIC_KEY_OFFSET..PUBLIC_KEY_OFFSET + CRYPTO_KEM_MLKEM768_PUBLICKEYBYTES]
358        ),
359        dec: |ss, ct, sk| {
360            crypto_kem_mlkem768_dec(ss, ct, sk);
361            Ok::<(), Error>(())
362        },
363    }
364}
365
366pub use xwing::*;