Skip to main content

hpke/
kem.rs

1//! Key encapsulation mechanisms (KEMs) for HPKE (`draft-ietf-hpke-hpke-05` Section 4 and Section 7.1).
2
3use crypto::{Hash, curve25519::x25519, p256, p521};
4
5use super::{
6    HpkeError,
7    kdf::{HkdfSha256, HkdfSha512, Kdf, Shake256 as Shake256Kdf},
8    wipe,
9};
10
11/// Key encapsulation mechanism (KEM) usable within an HPKE ciphersuite.
12///
13/// A KEM encapsulates a shared secret against a public key: the sender runs
14/// [`Kem::encap`] to obtain a `(shared secret, encapsulated key)` pair, and
15/// the recipient runs [`Kem::decap`] on the encapsulated key to recover the
16/// same shared secret.
17///
18/// `draft-ietf-hpke-hpke-05` removed the authenticated modes of RFC 9180, so a
19/// KEM only needs `encap`/`decap`; the `AuthEncap`/`AuthDecap` interface is not
20/// part of HPKE anymore. Sender authentication is provided by the pre-shared
21/// key mode or by a signature over the `(encapped_key, ciphertext)` tuple.
22///
23/// # Example
24///
25/// ```
26/// # #[cfg(all(feature = "alloc", feature = "random"))] {
27/// use hpke::kem::{Kem, X25519HkdfSha256};
28///
29/// let (secret_key, public_key) = X25519HkdfSha256::generate_keypair().unwrap();
30/// let (shared_secret, encapped_key) = X25519HkdfSha256::encap(&public_key).unwrap();
31/// let decapsulated = X25519HkdfSha256::decap(&encapped_key, &secret_key).unwrap();
32/// assert_eq!(shared_secret.as_ref(), decapsulated.as_ref());
33/// # }
34/// ```
35///
36/// # Custom KEMs
37///
38/// Implement this trait to use a custom KEM (e.g. ML-KEM for the
39/// post-quantum HPKE draft) inside HPKE. The shared secret returned by
40/// `encap`/`decap` must be exactly [`Kem::SHARED_SECRET_SIZE`] bytes long. Use
41/// [`Kdf::labeled_extract`]/[`Kdf::labeled_expand`] (two-stage KDFs) or
42/// [`Kdf::labeled_derive`] (single-stage KDFs) to derive keys exactly like the
43/// KEMs in `draft-ietf-hpke-hpke-05` do.
44pub trait Kem: Sized {
45    /// The HPKE KEM identifier (`draft-ietf-hpke-hpke-05` Section 7.1, IANA "HPKE KEM
46    /// Identifiers"), used to build the ciphersuite `suite_id`.
47    const HPKE_KEM_ID: u16;
48
49    /// The size of the shared secret produced by this KEM.
50    ///
51    /// Must not exceed 64 bytes, the maximum capacity of [`struct@Hash`].
52    const SHARED_SECRET_SIZE: usize;
53
54    /// The size of an encapsulated key.
55    const ENCAPPED_KEY_SIZE: usize;
56
57    /// The size of a serialized public key.
58    const PUBLIC_KEY_SIZE: usize;
59
60    /// The size of a serialized secret key.
61    const SECRET_KEY_SIZE: usize;
62
63    /// The size of the randomness consumed by [`Kem::encap_deterministic`].
64    ///
65    /// Defaults to [`Kem::SECRET_KEY_SIZE`], the size of an ephemeral secret
66    /// key / the `DeriveKeyPair` input for the Diffie-Hellman KEMs. KEMs whose
67    /// deterministic encapsulation takes a different amount of randomness (e.g.
68    /// [`MLKEM768X25519`]) override this.
69    const RANDOMNESS_SIZE: usize = Self::SECRET_KEY_SIZE;
70
71    /// The public (encapsulation) key type.
72    type PublicKey: Clone;
73
74    /// The secret (decapsulation) key type.
75    type SecretKey: Clone;
76
77    /// The encapsulated key type (the KEM ciphertext sent alongside the
78    /// first encrypted message).
79    type EncappedKey: Clone;
80
81    /// Deterministically derives a key pair from input keying material.
82    ///
83    /// `input_keying_material` SHOULD be at least
84    /// [`Kem::SECRET_KEY_SIZE`] bytes long and contain at least that many
85    /// bytes of entropy (`draft-ietf-hpke-hpke-05` Sections 7.1.3 and 9.2.1). It
86    /// MUST NOT be reused for any other purpose, in particular not with
87    /// another KEM.
88    ///
89    /// # Errors
90    ///
91    /// Returns [`HpkeError::DeriveKeyPairError`] when no valid key pair could
92    /// be derived (rejection sampling exhausted).
93    fn derive_keypair(input_keying_material: &[u8]) -> Result<(Self::SecretKey, Self::PublicKey), HpkeError>;
94
95    /// Generates a fresh random key pair using the operating system's random
96    /// number generator.
97    ///
98    /// # Errors
99    ///
100    /// Returns [`HpkeError::Random`] when the operating system's random
101    /// number generator is unavailable or fails.
102    #[cfg(feature = "random")]
103    fn generate_keypair() -> Result<(Self::SecretKey, Self::PublicKey), HpkeError> {
104        // Maximum secret key size across the algorithms shipped by this crate
105        // (P-521: 66 bytes). Custom KEMs with a larger `SECRET_KEY_SIZE` must
106        // override this method.
107        const MAX_SECRET_KEY_SIZE: usize = 66;
108        const {
109            assert!(
110                Self::SECRET_KEY_SIZE <= MAX_SECRET_KEY_SIZE,
111                "SECRET_KEY_SIZE is too large; override generate_keypair"
112            )
113        };
114
115        let mut input_keying_material = [0u8; MAX_SECRET_KEY_SIZE];
116        crypto::random::fill(&mut input_keying_material[..Self::SECRET_KEY_SIZE])?;
117        return Self::derive_keypair(&input_keying_material[..Self::SECRET_KEY_SIZE]);
118    }
119
120    /// Derives the public key of a secret key.
121    fn derive_public_key(secret_key: &Self::SecretKey) -> Self::PublicKey;
122
123    /// Serializes a public key into `out`, which must be exactly
124    /// [`Kem::PUBLIC_KEY_SIZE`] bytes long.
125    ///
126    /// # Errors
127    ///
128    /// Returns [`HpkeError::DeserializeError`] when `out` has the wrong
129    /// length.
130    fn public_key_to_bytes(out: &mut [u8], public_key: &Self::PublicKey) -> Result<(), HpkeError>;
131
132    /// Deserializes a public key from `bytes`, which must be exactly
133    /// [`Kem::PUBLIC_KEY_SIZE`] bytes long and a valid, fully validated public key.
134    ///
135    /// # Errors
136    ///
137    /// Returns [`HpkeError::DeserializeError`] when `bytes` has the wrong
138    /// length or does not encode a valid public key.
139    fn public_key_from_bytes(bytes: &[u8]) -> Result<Self::PublicKey, HpkeError>;
140
141    /// Serializes a secret key into `out`, which must be exactly
142    /// [`Kem::SECRET_KEY_SIZE`] bytes long.
143    ///
144    /// # Errors
145    ///
146    /// Returns [`HpkeError::DeserializeError`] when `out` has the wrong
147    /// length.
148    fn secret_key_to_bytes(out: &mut [u8], secret_key: &Self::SecretKey) -> Result<(), HpkeError>;
149
150    /// Deserializes a secret key from `bytes`, which must be exactly
151    /// [`Kem::SECRET_KEY_SIZE`] bytes long.
152    ///
153    /// # Errors
154    ///
155    /// Returns [`HpkeError::DeserializeError`] when `bytes` has the wrong
156    /// length or does not encode a valid secret key.
157    fn secret_key_from_bytes(bytes: &[u8]) -> Result<Self::SecretKey, HpkeError>;
158
159    /// Serializes an encapsulated key into `out`, which must be exactly
160    /// [`Kem::ENCAPPED_KEY_SIZE`] bytes long.
161    ///
162    /// # Errors
163    ///
164    /// Returns [`HpkeError::DeserializeError`] when `out` has the wrong
165    /// length.
166    fn encapped_key_to_bytes(out: &mut [u8], encapped_key: &Self::EncappedKey) -> Result<(), HpkeError>;
167
168    /// Deserializes an encapsulated key from `bytes`, which must be exactly
169    /// [`Kem::ENCAPPED_KEY_SIZE`] bytes long.
170    ///
171    /// # Errors
172    ///
173    /// Returns [`HpkeError::DeserializeError`] when `bytes` has the wrong
174    /// length or does not encode a valid encapsulated key.
175    fn encapped_key_from_bytes(bytes: &[u8]) -> Result<Self::EncappedKey, HpkeError>;
176
177    /// Encapsulates a shared secret against `recipient_public_key`, using the given ephemeral
178    /// secret key `ephemeral_secret_key`.
179    ///
180    /// Returns the shared secret and the encapsulated key that the owner of
181    /// `recipient_public_key` can use to recover it. This is the deterministic variant of
182    /// [`Kem::encap`]; it exists for protocols that need reproducible
183    /// encapsulation (e.g. test vectors) and MUST NOT be called with a
184    /// non-fresh `ephemeral_secret_key`.
185    ///
186    /// # Errors
187    ///
188    /// Returns [`HpkeError::EncapError`] when `recipient_public_key` is invalid.
189    fn encap_with_ephemeral(
190        ephemeral_secret_key: &Self::SecretKey,
191        recipient_public_key: &Self::PublicKey,
192    ) -> Result<(Hash, Self::EncappedKey), HpkeError>;
193
194    /// Encapsulates a fresh shared secret against `recipient_public_key` using a freshly
195    /// generated ephemeral key pair.
196    ///
197    /// # Errors
198    ///
199    /// Returns [`HpkeError::EncapError`] when `recipient_public_key` is invalid, or
200    /// [`HpkeError::Random`] when the operating system's random number
201    /// generator is unavailable or fails.
202    #[cfg(feature = "random")]
203    fn encap(recipient_public_key: &Self::PublicKey) -> Result<(Hash, Self::EncappedKey), HpkeError> {
204        let (ephemeral_secret_key, _ephemeral_public_key) = Self::generate_keypair()?;
205        return Self::encap_with_ephemeral(&ephemeral_secret_key, recipient_public_key);
206    }
207
208    /// Encapsulates a shared secret against `recipient_public_key` from caller-provided
209    /// `randomness` (`EncapDerand`).
210    ///
211    /// This is the deterministic equivalent of [`Kem::encap`], intended for
212    /// test vectors and protocols that need reproducible encapsulation.
213    /// `randomness` MUST be exactly [`Kem::RANDOMNESS_SIZE`] bytes long, uniformly
214    /// random, and MUST NOT be reused across encapsulations.
215    ///
216    /// The default implementation derives an ephemeral key pair from
217    /// `randomness` and forwards to [`Kem::encap_with_ephemeral`], which is
218    /// correct for the Diffie-Hellman KEMs. KEMs with a different
219    /// deterministic-encapsulation interface (e.g. [`MLKEM768X25519`], whose
220    /// randomness is the 64-byte encapsulation randomness rather than a secret
221    /// key) override this.
222    ///
223    /// # Errors
224    ///
225    /// Returns [`HpkeError::DeserializeError`] when `randomness` does not have
226    /// exactly [`Kem::RANDOMNESS_SIZE`] bytes, or [`HpkeError::EncapError`] when
227    /// `recipient_public_key` is invalid.
228    fn encap_deterministic(
229        recipient_public_key: &Self::PublicKey,
230        randomness: &[u8],
231    ) -> Result<(Hash, Self::EncappedKey), HpkeError> {
232        if randomness.len() != Self::RANDOMNESS_SIZE {
233            return Err(HpkeError::DeserializeError);
234        }
235        let (ephemeral_secret_key, _ephemeral_public_key) = Self::derive_keypair(randomness)?;
236        return Self::encap_with_ephemeral(&ephemeral_secret_key, recipient_public_key);
237    }
238
239    /// Recovers the shared secret encapsulated in `encapped_key` with the secret key
240    /// `recipient_secret_key`.
241    ///
242    /// # Errors
243    ///
244    /// Returns [`HpkeError::DecapError`] when `encapped_key` is invalid or when the
245    /// key exchange fails.
246    fn decap(encapped_key: &Self::EncappedKey, recipient_secret_key: &Self::SecretKey) -> Result<Hash, HpkeError>;
247}
248
249/// MLKEM768-X25519 hybrid post-quantum KEM (ML-KEM-768 with X25519), as
250/// specified in `draft-ietf-hpke-pq-05`.
251///
252/// This is the same construction as X-Wing (`draft-connolly-cfrg-xwing-kem`),
253/// as noted in `draft-irtf-cfrg-concrete-hybrid-kems-03` Section 4.2.
254///
255/// MLKEM768-X25519 is usable with the Base and pre-shared key modes of HPKE.
256/// `draft-ietf-hpke-hpke-05` removed the authenticated modes, so no KEM is
257/// required to provide `AuthEncap`/`AuthDecap`; for sender authentication, sign
258/// the `(encapped_key, ciphertext)` tuple instead.
259///
260/// The KEM identifier is `0x647a`, as registered by the draft in the IANA
261/// "HPKE KEM Identifiers" registry.
262///
263/// # Key encoding
264///
265/// A secret key is the 32-byte X-Wing seed: [`Kem::secret_key_to_bytes`]
266/// returns that seed, and [`Kem::secret_key_from_bytes`] takes it and
267/// re-derives the full key pair (recomputing the public key). The seed is
268/// therefore the entire secret; treat it as such.
269///
270/// # Example
271///
272/// ```
273/// # #[cfg(all(feature = "alloc", feature = "random"))] {
274/// use hpke::kem::{Kem, MLKEM768X25519};
275///
276/// let (secret_key, public_key) = MLKEM768X25519::generate_keypair().unwrap();
277/// let (shared_secret, encapped_key) = MLKEM768X25519::encap(&public_key).unwrap();
278/// let decapsulated = MLKEM768X25519::decap(&encapped_key, &secret_key).unwrap();
279/// assert_eq!(shared_secret.as_ref(), decapsulated.as_ref());
280/// # }
281/// ```
282#[derive(Clone, Copy, Debug, PartialEq, Eq)]
283pub struct MLKEM768X25519;
284
285impl Kem for MLKEM768X25519 {
286    const HPKE_KEM_ID: u16 = 0x647a;
287    const SHARED_SECRET_SIZE: usize = crypto::xwing::SHARED_SECRET_SIZE;
288    const ENCAPPED_KEY_SIZE: usize = crypto::xwing::CIPHERTEXT_SIZE;
289    const PUBLIC_KEY_SIZE: usize = crypto::xwing::PUBLIC_KEY_SIZE;
290    const SECRET_KEY_SIZE: usize = crypto::xwing::SECRET_KEY_SIZE;
291    const RANDOMNESS_SIZE: usize = 64;
292
293    type PublicKey = crypto::xwing::PublicKey;
294    type SecretKey = crypto::xwing::SecretKey;
295    type EncappedKey = [u8; crypto::xwing::CIPHERTEXT_SIZE];
296
297    fn derive_keypair(input_keying_material: &[u8]) -> Result<(Self::SecretKey, Self::PublicKey), HpkeError> {
298        // draft-ietf-hpke-pq-05 Section 4 (MLKEM768-X25519):
299        //   seed = SHAKE256.LabeledDerive(input_keying_material, "DeriveKeyPair", "", 32)
300        //   return KEM.DeriveKeyPair(seed)
301        let suite_id = kem_suite_id(Self::HPKE_KEM_ID);
302        let mut seed = [0u8; crypto::xwing::SECRET_KEY_SIZE];
303        <Shake256Kdf as Kdf>::labeled_derive(&mut seed, &[input_keying_material], &suite_id, b"DeriveKeyPair", &[])?;
304
305        let keypair = crypto::xwing::generate_keypair_derand(&seed);
306        wipe(&mut seed);
307        return Ok(keypair);
308    }
309
310    #[cfg(feature = "random")]
311    fn generate_keypair() -> Result<(Self::SecretKey, Self::PublicKey), HpkeError> {
312        return crypto::xwing::generate_keypair().map_err(HpkeError::from);
313    }
314
315    fn derive_public_key(secret_key: &Self::SecretKey) -> Self::PublicKey {
316        return secret_key.public_key();
317    }
318
319    fn public_key_to_bytes(out: &mut [u8], public_key: &Self::PublicKey) -> Result<(), HpkeError> {
320        if out.len() != Self::PUBLIC_KEY_SIZE {
321            return Err(HpkeError::DeserializeError);
322        }
323        out.copy_from_slice(&public_key.to_bytes());
324        return Ok(());
325    }
326
327    fn public_key_from_bytes(bytes: &[u8]) -> Result<Self::PublicKey, HpkeError> {
328        let bytes: &[u8; Self::PUBLIC_KEY_SIZE] = bytes.try_into().map_err(|_| HpkeError::DeserializeError)?;
329        return crypto::xwing::PublicKey::from_bytes(bytes).map_err(HpkeError::from);
330    }
331
332    fn secret_key_to_bytes(out: &mut [u8], secret_key: &Self::SecretKey) -> Result<(), HpkeError> {
333        if out.len() != Self::SECRET_KEY_SIZE {
334            return Err(HpkeError::DeserializeError);
335        }
336        out.copy_from_slice(&secret_key.to_bytes());
337        return Ok(());
338    }
339
340    fn secret_key_from_bytes(bytes: &[u8]) -> Result<Self::SecretKey, HpkeError> {
341        let bytes: &[u8; Self::SECRET_KEY_SIZE] = bytes.try_into().map_err(|_| HpkeError::DeserializeError)?;
342        return Ok(crypto::xwing::generate_keypair_derand(bytes).0);
343    }
344
345    fn encapped_key_to_bytes(out: &mut [u8], encapped_key: &Self::EncappedKey) -> Result<(), HpkeError> {
346        if out.len() != Self::ENCAPPED_KEY_SIZE {
347            return Err(HpkeError::DeserializeError);
348        }
349        out.copy_from_slice(encapped_key);
350        return Ok(());
351    }
352
353    fn encapped_key_from_bytes(bytes: &[u8]) -> Result<Self::EncappedKey, HpkeError> {
354        let bytes: &[u8; Self::ENCAPPED_KEY_SIZE] = bytes.try_into().map_err(|_| HpkeError::DeserializeError)?;
355        return Ok(*bytes);
356    }
357
358    #[cfg(feature = "random")]
359    fn encap(recipient_public_key: &Self::PublicKey) -> Result<(Hash, Self::EncappedKey), HpkeError> {
360        let mut randomness = [0u8; Self::RANDOMNESS_SIZE];
361        crypto::random::fill(&mut randomness)?;
362        let result = Self::encap_deterministic(recipient_public_key, &randomness);
363        wipe(&mut randomness);
364        return result;
365    }
366
367    fn encap_deterministic(
368        recipient_public_key: &Self::PublicKey,
369        randomness: &[u8],
370    ) -> Result<(Hash, Self::EncappedKey), HpkeError> {
371        // draft-irtf-cfrg-hybrid-kems-12 Appendix A: the hybrid KEM randomness
372        // is the concatenation of the ML-KEM-768 and X25519 randomness, i.e.
373        // exactly X-Wing's 64-byte `encapsulation_seed`.
374        let randomness: &[u8; 64] = randomness.try_into().map_err(|_| HpkeError::DeserializeError)?;
375        let (shared_secret, encapped_key) = recipient_public_key.encapsulate_derand(randomness);
376        return Ok((Hash::from(shared_secret), encapped_key));
377    }
378
379    fn encap_with_ephemeral(
380        ephemeral_secret_key: &Self::SecretKey,
381        recipient_public_key: &Self::PublicKey,
382    ) -> Result<(Hash, Self::EncappedKey), HpkeError> {
383        // The trait models the ephemeral secret as a KEM secret key, whereas
384        // MLKEM768-X25519 encapsulation consumes a 64-byte seed. Expand the
385        // 32-byte ephemeral seed deterministically with SHAKE256.
386        let mut encapsulation_seed = [0u8; 64];
387        crypto::sha3::Shake256::hash(&ephemeral_secret_key.to_bytes(), &mut encapsulation_seed);
388
389        let (shared_secret, encapped_key) = recipient_public_key.encapsulate_derand(&encapsulation_seed);
390        wipe(&mut encapsulation_seed);
391        return Ok((Hash::from(shared_secret), encapped_key));
392    }
393
394    fn decap(encapped_key: &Self::EncappedKey, recipient_secret_key: &Self::SecretKey) -> Result<Hash, HpkeError> {
395        let shared_secret = recipient_secret_key
396            .decapsulate(encapped_key)
397            .map_err(|_| HpkeError::DecapError)?;
398        return Ok(Hash::from(shared_secret));
399    }
400}
401
402////////////////////////////////////////////////////////////////////////////////////////////////////
403// Diffie-Hellman KEM (draft-ietf-hpke-hpke-05 Section 4.5)
404
405/// Maximum serialized public key / encapsulated key size across the supported
406/// groups (P-521: 133 bytes).
407const MAX_PUBLIC_KEY_SIZE: usize = 133;
408/// Maximum secret key and Diffie-Hellman output size across the supported
409/// groups (P-521: 66 bytes).
410const MAX_DIFFIE_HELLMAN_OUTPUT_SIZE: usize = 66;
411/// Maximum size of a KEM context: `encapped_key || recipient_public_key`.
412const MAX_KEM_CONTEXT: usize = 2 * MAX_PUBLIC_KEY_SIZE;
413
414/// Builds the Diffie-Hellman KEM `suite_id`: `"KEM" || I2OSP(kem_id, 2)`
415/// (`draft-ietf-hpke-hpke-05` Section 4.5).
416fn kem_suite_id(kem_id: u16) -> [u8; 5] {
417    let mut suite_id = [0u8; 5];
418    suite_id[..3].copy_from_slice(b"KEM");
419    suite_id[3] = (kem_id >> 8) as u8;
420    suite_id[4] = (kem_id & 0xff) as u8;
421    return suite_id;
422}
423
424/// A Diffie-Hellman group usable by the Diffie-Hellman KEM construction.
425///
426/// This trait is private: the crate provides the groups required by `draft-ietf-hpke-hpke-05`.
427/// Custom KEMs are expected to implement [`Kem`] directly (the Diffie-Hellman KEM
428/// construction can be reproduced with [`Kdf::labeled_extract`] and
429/// [`Kdf::labeled_expand`]).
430trait DiffieHellmanGroup: Copy + Clone {
431    /// Size of the raw Diffie-Hellman output.
432    const DIFFIE_HELLMAN_OUTPUT_SIZE: usize;
433    /// Size of a serialized public key.
434    const PUBLIC_KEY_SIZE: usize;
435    /// Size of a serialized secret key.
436    const SECRET_KEY_SIZE: usize;
437    /// Mask applied to the first byte of a NIST candidate scalar.
438    const BITMASK: u8;
439    /// Whether `DeriveKeyPair` uses NIST rejection sampling (true) or the
440    /// single-shot X25519 derivation (false).
441    const REJECTION_SAMPLING: bool;
442
443    type SecretKey: Clone;
444    type PublicKey: Clone;
445
446    /// Computes the raw Diffie-Hellman shared secret, written into `out`
447    /// (`out.len() == DIFFIE_HELLMAN_OUTPUT_SIZE`).
448    ///
449    /// Returns `Err(())` on failure (e.g. an all-zero X25519 output).
450    fn diffie_hellman(secret_key: &Self::SecretKey, public_key: &Self::PublicKey, out: &mut [u8]) -> Result<(), ()>;
451
452    /// Derives the public key of a secret key.
453    fn derive_public_key(secret_key: &Self::SecretKey) -> Self::PublicKey;
454
455    /// Serializes a public key into `out` (`out.len() == PUBLIC_KEY_SIZE`).
456    fn serialize_public_key(out: &mut [u8], public_key: &Self::PublicKey) -> Result<(), HpkeError>;
457
458    /// Deserializes and validates a public key.
459    fn deserialize_public_key(bytes: &[u8]) -> Result<Self::PublicKey, HpkeError>;
460
461    /// Serializes a secret key into `out` (`out.len() == SECRET_KEY_SIZE`).
462    fn serialize_secret_key(out: &mut [u8], secret_key: &Self::SecretKey) -> Result<(), HpkeError>;
463
464    /// Deserializes a secret key.
465    fn deserialize_secret_key(bytes: &[u8]) -> Result<Self::SecretKey, HpkeError>;
466
467    /// Interprets a candidate scalar for `DeriveKeyPair`, returning `Err(())`
468    /// when it is out of range (rejection sampling).
469    fn scalar_from_candidate(bytes: &[u8]) -> Result<Self::SecretKey, ()>;
470}
471
472#[derive(Clone, Copy, Debug, PartialEq, Eq)]
473struct X25519Group;
474
475impl DiffieHellmanGroup for X25519Group {
476    const DIFFIE_HELLMAN_OUTPUT_SIZE: usize = x25519::SHARED_SECRET_SIZE;
477    const PUBLIC_KEY_SIZE: usize = x25519::KEY_SIZE;
478    const SECRET_KEY_SIZE: usize = x25519::KEY_SIZE;
479    const BITMASK: u8 = 0xff;
480    const REJECTION_SAMPLING: bool = false;
481
482    type SecretKey = x25519::SecretKey;
483    type PublicKey = x25519::PublicKey;
484
485    fn diffie_hellman(secret_key: &Self::SecretKey, public_key: &Self::PublicKey, out: &mut [u8]) -> Result<(), ()> {
486        // `ecdh` rejects all-zero shared secrets, as draft-ietf-hpke-hpke-05
487        // Section 7.1.4 requires for X25519.
488        let shared = secret_key.ecdh(public_key).map_err(|_| ())?;
489        out.copy_from_slice(&shared);
490        return Ok(());
491    }
492
493    fn derive_public_key(secret_key: &Self::SecretKey) -> Self::PublicKey {
494        return secret_key.public_key();
495    }
496
497    fn serialize_public_key(out: &mut [u8], public_key: &Self::PublicKey) -> Result<(), HpkeError> {
498        if out.len() != Self::PUBLIC_KEY_SIZE {
499            return Err(HpkeError::DeserializeError);
500        }
501        out.copy_from_slice(&public_key.to_bytes());
502        return Ok(());
503    }
504
505    fn deserialize_public_key(bytes: &[u8]) -> Result<Self::PublicKey, HpkeError> {
506        let bytes: &[u8; Self::PUBLIC_KEY_SIZE] = bytes.try_into().map_err(|_| HpkeError::DeserializeError)?;
507        return Ok(x25519::PublicKey::from_bytes(bytes));
508    }
509
510    fn serialize_secret_key(out: &mut [u8], secret_key: &Self::SecretKey) -> Result<(), HpkeError> {
511        if out.len() != Self::SECRET_KEY_SIZE {
512            return Err(HpkeError::DeserializeError);
513        }
514        // `draft-ietf-hpke-hpke-05` Section 7.1.2: X25519 private keys are
515        // identical to their byte string representation. The scalar is clamped
516        // inside `x25519` during scalar multiplication, not at serialization.
517        out.copy_from_slice(&secret_key.to_bytes());
518        return Ok(());
519    }
520
521    fn deserialize_secret_key(bytes: &[u8]) -> Result<Self::SecretKey, HpkeError> {
522        let bytes: &[u8; Self::SECRET_KEY_SIZE] = bytes.try_into().map_err(|_| HpkeError::DeserializeError)?;
523        return Ok(x25519::SecretKey::from_bytes(bytes));
524    }
525
526    fn scalar_from_candidate(bytes: &[u8]) -> Result<Self::SecretKey, ()> {
527        let bytes: &[u8; Self::SECRET_KEY_SIZE] = bytes.try_into().map_err(|_| ())?;
528        return Ok(x25519::SecretKey::from_bytes(bytes));
529    }
530}
531
532#[derive(Clone, Copy, Debug, PartialEq, Eq)]
533struct P256Group;
534
535impl DiffieHellmanGroup for P256Group {
536    const DIFFIE_HELLMAN_OUTPUT_SIZE: usize = p256::ECDH_SHARED_SECRET_SIZE;
537    const PUBLIC_KEY_SIZE: usize = p256::PUBLIC_KEY_UNCOMPRESSED_SIZE;
538    const SECRET_KEY_SIZE: usize = p256::SECRET_KEY_SIZE;
539    const BITMASK: u8 = 0xff;
540    const REJECTION_SAMPLING: bool = true;
541
542    type SecretKey = p256::SecretKey;
543    type PublicKey = p256::PublicKey;
544
545    fn diffie_hellman(secret_key: &Self::SecretKey, public_key: &Self::PublicKey, out: &mut [u8]) -> Result<(), ()> {
546        let shared = secret_key.ecdh(public_key).map_err(|_| ())?;
547        out.copy_from_slice(&shared);
548        return Ok(());
549    }
550
551    fn derive_public_key(secret_key: &Self::SecretKey) -> Self::PublicKey {
552        return secret_key.public_key();
553    }
554
555    fn serialize_public_key(out: &mut [u8], public_key: &Self::PublicKey) -> Result<(), HpkeError> {
556        if out.len() != Self::PUBLIC_KEY_SIZE {
557            return Err(HpkeError::DeserializeError);
558        }
559        out.copy_from_slice(&public_key.to_bytes());
560        return Ok(());
561    }
562
563    fn deserialize_public_key(bytes: &[u8]) -> Result<Self::PublicKey, HpkeError> {
564        // draft-ietf-hpke-hpke-05 Section 7.1.1 requires the uncompressed SEC1 encoding.
565        if bytes.len() != Self::PUBLIC_KEY_SIZE || bytes[0] != 0x04 {
566            return Err(HpkeError::DeserializeError);
567        }
568        return p256::PublicKey::from_bytes(bytes).map_err(|_| HpkeError::DeserializeError);
569    }
570
571    fn serialize_secret_key(out: &mut [u8], secret_key: &Self::SecretKey) -> Result<(), HpkeError> {
572        if out.len() != Self::SECRET_KEY_SIZE {
573            return Err(HpkeError::DeserializeError);
574        }
575        out.copy_from_slice(&secret_key.to_bytes());
576        return Ok(());
577    }
578
579    fn deserialize_secret_key(bytes: &[u8]) -> Result<Self::SecretKey, HpkeError> {
580        let bytes: &[u8; Self::SECRET_KEY_SIZE] = bytes.try_into().map_err(|_| HpkeError::DeserializeError)?;
581        return p256::SecretKey::from_bytes(bytes).map_err(|_| HpkeError::DeserializeError);
582    }
583
584    fn scalar_from_candidate(bytes: &[u8]) -> Result<Self::SecretKey, ()> {
585        let bytes: &[u8; Self::SECRET_KEY_SIZE] = bytes.try_into().map_err(|_| ())?;
586        return p256::SecretKey::from_bytes(bytes).map_err(|_| ());
587    }
588}
589
590#[derive(Clone, Copy, Debug, PartialEq, Eq)]
591struct P521Group;
592
593impl DiffieHellmanGroup for P521Group {
594    const DIFFIE_HELLMAN_OUTPUT_SIZE: usize = p521::ECDH_SHARED_SECRET_SIZE;
595    const PUBLIC_KEY_SIZE: usize = p521::PUBLIC_KEY_UNCOMPRESSED_SIZE;
596    const SECRET_KEY_SIZE: usize = p521::SECRET_KEY_SIZE;
597    const BITMASK: u8 = 0x01;
598    const REJECTION_SAMPLING: bool = true;
599
600    type SecretKey = p521::SecretKey;
601    type PublicKey = p521::PublicKey;
602
603    fn diffie_hellman(secret_key: &Self::SecretKey, public_key: &Self::PublicKey, out: &mut [u8]) -> Result<(), ()> {
604        let shared = secret_key.ecdh(public_key).map_err(|_| ())?;
605        out.copy_from_slice(&shared);
606        return Ok(());
607    }
608
609    fn derive_public_key(secret_key: &Self::SecretKey) -> Self::PublicKey {
610        return secret_key.public_key();
611    }
612
613    fn serialize_public_key(out: &mut [u8], public_key: &Self::PublicKey) -> Result<(), HpkeError> {
614        if out.len() != Self::PUBLIC_KEY_SIZE {
615            return Err(HpkeError::DeserializeError);
616        }
617        out.copy_from_slice(&public_key.to_bytes());
618        return Ok(());
619    }
620
621    fn deserialize_public_key(bytes: &[u8]) -> Result<Self::PublicKey, HpkeError> {
622        if bytes.len() != Self::PUBLIC_KEY_SIZE || bytes[0] != 0x04 {
623            return Err(HpkeError::DeserializeError);
624        }
625        return p521::PublicKey::from_bytes(bytes).map_err(|_| HpkeError::DeserializeError);
626    }
627
628    fn serialize_secret_key(out: &mut [u8], secret_key: &Self::SecretKey) -> Result<(), HpkeError> {
629        if out.len() != Self::SECRET_KEY_SIZE {
630            return Err(HpkeError::DeserializeError);
631        }
632        out.copy_from_slice(&secret_key.to_bytes());
633        return Ok(());
634    }
635
636    fn deserialize_secret_key(bytes: &[u8]) -> Result<Self::SecretKey, HpkeError> {
637        let bytes: &[u8; Self::SECRET_KEY_SIZE] = bytes.try_into().map_err(|_| HpkeError::DeserializeError)?;
638        return p521::SecretKey::from_bytes(bytes).map_err(|_| HpkeError::DeserializeError);
639    }
640
641    fn scalar_from_candidate(bytes: &[u8]) -> Result<Self::SecretKey, ()> {
642        let bytes: &[u8; Self::SECRET_KEY_SIZE] = bytes.try_into().map_err(|_| ())?;
643        return p521::SecretKey::from_bytes(bytes).map_err(|_| ());
644    }
645}
646
647/// `draft-ietf-hpke-hpke-05` Section 4.5 `ExtractAndExpand_TwoStage`.
648fn extract_and_expand<K: Kdf>(kem_id: u16, diffie_hellman: &[u8], kem_context: &[u8]) -> Result<Hash, HpkeError> {
649    const { assert!(K::OUTPUT_SIZE <= crypto::MAX_HASH_OUTPUT_SIZE) };
650
651    let suite_id = kem_suite_id(kem_id);
652    let mut pseudorandom_key = K::labeled_extract(b"", &suite_id, b"eae_prk", diffie_hellman);
653
654    let mut shared_secret_bytes = [0u8; crypto::MAX_HASH_OUTPUT_SIZE];
655    K::labeled_expand(
656        &mut shared_secret_bytes[..K::OUTPUT_SIZE],
657        &pseudorandom_key,
658        &suite_id,
659        b"shared_secret",
660        kem_context,
661    )?;
662    let shared_secret =
663        Hash::try_from(&shared_secret_bytes[..K::OUTPUT_SIZE]).expect("SHARED_SECRET_SIZE fits in a Hash");
664
665    wipe(&mut shared_secret_bytes);
666    wipe(pseudorandom_key.as_mut());
667    return Ok(shared_secret);
668}
669
670/// `draft-ietf-hpke-hpke-05` Section 7.1.3 `DeriveKeyPair` for a Diffie-Hellman KEM.
671fn diffie_hellman_kem_derive_keypair<G: DiffieHellmanGroup, K: Kdf>(
672    kem_id: u16,
673    input_keying_material: &[u8],
674) -> Result<(G::SecretKey, G::PublicKey), HpkeError> {
675    const { assert!(G::SECRET_KEY_SIZE <= MAX_DIFFIE_HELLMAN_OUTPUT_SIZE && G::PUBLIC_KEY_SIZE <= MAX_PUBLIC_KEY_SIZE) };
676
677    let suite_id = kem_suite_id(kem_id);
678    let mut derive_key_pair_pseudorandom_key = K::labeled_extract(b"", &suite_id, b"dkp_prk", input_keying_material);
679
680    if G::REJECTION_SAMPLING {
681        let mut candidate = [0u8; MAX_DIFFIE_HELLMAN_OUTPUT_SIZE];
682        for counter in 0..=255u8 {
683            K::labeled_expand(
684                &mut candidate[..G::SECRET_KEY_SIZE],
685                &derive_key_pair_pseudorandom_key,
686                &suite_id,
687                b"candidate",
688                &[counter],
689            )?;
690            candidate[0] &= G::BITMASK;
691
692            if let Ok(secret_key) = G::scalar_from_candidate(&candidate[..G::SECRET_KEY_SIZE]) {
693                let public_key = G::derive_public_key(&secret_key);
694                wipe(&mut candidate);
695                wipe(derive_key_pair_pseudorandom_key.as_mut());
696                return Ok((secret_key, public_key));
697            }
698        }
699        wipe(&mut candidate);
700        wipe(derive_key_pair_pseudorandom_key.as_mut());
701        return Err(HpkeError::DeriveKeyPairError);
702    }
703
704    let mut secret_key_bytes = [0u8; MAX_DIFFIE_HELLMAN_OUTPUT_SIZE];
705    K::labeled_expand(
706        &mut secret_key_bytes[..G::SECRET_KEY_SIZE],
707        &derive_key_pair_pseudorandom_key,
708        &suite_id,
709        b"sk",
710        b"",
711    )?;
712    let secret_key =
713        G::scalar_from_candidate(&secret_key_bytes[..G::SECRET_KEY_SIZE]).map_err(|_| HpkeError::DeriveKeyPairError)?;
714    let public_key = G::derive_public_key(&secret_key);
715    wipe(&mut secret_key_bytes);
716    wipe(derive_key_pair_pseudorandom_key.as_mut());
717    return Ok((secret_key, public_key));
718}
719
720/// `draft-ietf-hpke-hpke-05` Section 4.5 `Encap` with a caller-provided ephemeral key.
721fn diffie_hellman_kem_encap_with_ephemeral<G: DiffieHellmanGroup, K: Kdf>(
722    kem_id: u16,
723    ephemeral_secret_key: &G::SecretKey,
724    recipient_public_key: &G::PublicKey,
725) -> Result<(Hash, G::PublicKey), HpkeError> {
726    const { assert!(G::SECRET_KEY_SIZE <= MAX_DIFFIE_HELLMAN_OUTPUT_SIZE && G::PUBLIC_KEY_SIZE <= MAX_PUBLIC_KEY_SIZE) };
727
728    let mut diffie_hellman = [0u8; MAX_DIFFIE_HELLMAN_OUTPUT_SIZE];
729    G::diffie_hellman(
730        ephemeral_secret_key,
731        recipient_public_key,
732        &mut diffie_hellman[..G::DIFFIE_HELLMAN_OUTPUT_SIZE],
733    )
734    .map_err(|_| HpkeError::EncapError)?;
735
736    let ephemeral_public_key = G::derive_public_key(ephemeral_secret_key);
737    let mut kem_context = [0u8; MAX_KEM_CONTEXT];
738    G::serialize_public_key(&mut kem_context[..G::PUBLIC_KEY_SIZE], &ephemeral_public_key)?;
739    G::serialize_public_key(
740        &mut kem_context[G::PUBLIC_KEY_SIZE..2 * G::PUBLIC_KEY_SIZE],
741        recipient_public_key,
742    )?;
743
744    let shared_secret = extract_and_expand::<K>(
745        kem_id,
746        &diffie_hellman[..G::DIFFIE_HELLMAN_OUTPUT_SIZE],
747        &kem_context[..2 * G::PUBLIC_KEY_SIZE],
748    )?;
749    wipe(&mut diffie_hellman);
750    return Ok((shared_secret, ephemeral_public_key));
751}
752
753/// `draft-ietf-hpke-hpke-05` Section 4.5 `Decap`.
754fn diffie_hellman_kem_decap<G: DiffieHellmanGroup, K: Kdf>(
755    kem_id: u16,
756    encapped_key: &G::PublicKey,
757    recipient_secret_key: &G::SecretKey,
758) -> Result<Hash, HpkeError> {
759    const { assert!(G::SECRET_KEY_SIZE <= MAX_DIFFIE_HELLMAN_OUTPUT_SIZE && G::PUBLIC_KEY_SIZE <= MAX_PUBLIC_KEY_SIZE) };
760
761    let mut diffie_hellman = [0u8; MAX_DIFFIE_HELLMAN_OUTPUT_SIZE];
762    G::diffie_hellman(
763        recipient_secret_key,
764        encapped_key,
765        &mut diffie_hellman[..G::DIFFIE_HELLMAN_OUTPUT_SIZE],
766    )
767    .map_err(|_| HpkeError::DecapError)?;
768
769    let recipient_public_key = G::derive_public_key(recipient_secret_key);
770    let mut kem_context = [0u8; MAX_KEM_CONTEXT];
771    G::serialize_public_key(&mut kem_context[..G::PUBLIC_KEY_SIZE], encapped_key)?;
772    G::serialize_public_key(
773        &mut kem_context[G::PUBLIC_KEY_SIZE..2 * G::PUBLIC_KEY_SIZE],
774        &recipient_public_key,
775    )?;
776
777    let shared_secret = extract_and_expand::<K>(
778        kem_id,
779        &diffie_hellman[..G::DIFFIE_HELLMAN_OUTPUT_SIZE],
780        &kem_context[..2 * G::PUBLIC_KEY_SIZE],
781    )?;
782    wipe(&mut diffie_hellman);
783    return Ok(shared_secret);
784}
785
786/// Defines a concrete Diffie-Hellman KEM type implementing [`Kem`] on top of a private
787/// [`DiffieHellmanGroup`] and a KDF.
788macro_rules! diffie_hellman_kem {
789    (
790        $(#[$meta:meta])*
791        $name:ident, $group:ty, $kdf:ty, $id:literal, $secret_key:ty, $public_key:ty
792    ) => {
793        $(#[$meta])*
794        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
795        pub struct $name;
796
797        impl Kem for $name {
798            const HPKE_KEM_ID: u16 = $id;
799            const SHARED_SECRET_SIZE: usize = <$kdf as Kdf>::OUTPUT_SIZE;
800            const ENCAPPED_KEY_SIZE: usize = <$group as DiffieHellmanGroup>::PUBLIC_KEY_SIZE;
801            const PUBLIC_KEY_SIZE: usize = <$group as DiffieHellmanGroup>::PUBLIC_KEY_SIZE;
802            const SECRET_KEY_SIZE: usize = <$group as DiffieHellmanGroup>::SECRET_KEY_SIZE;
803
804            type PublicKey = $public_key;
805            type SecretKey = $secret_key;
806            type EncappedKey = $public_key;
807
808            fn derive_keypair(input_keying_material: &[u8]) -> Result<(Self::SecretKey, Self::PublicKey), HpkeError> {
809                return diffie_hellman_kem_derive_keypair::<$group, $kdf>($id, input_keying_material);
810            }
811
812            fn derive_public_key(secret_key: &Self::SecretKey) -> Self::PublicKey {
813                return <$group as DiffieHellmanGroup>::derive_public_key(secret_key);
814            }
815
816            fn public_key_to_bytes(out: &mut [u8], public_key: &Self::PublicKey) -> Result<(), HpkeError> {
817                return <$group as DiffieHellmanGroup>::serialize_public_key(out, public_key);
818            }
819
820            fn public_key_from_bytes(bytes: &[u8]) -> Result<Self::PublicKey, HpkeError> {
821                return <$group as DiffieHellmanGroup>::deserialize_public_key(bytes);
822            }
823
824            fn secret_key_to_bytes(out: &mut [u8], secret_key: &Self::SecretKey) -> Result<(), HpkeError> {
825                return <$group as DiffieHellmanGroup>::serialize_secret_key(out, secret_key);
826            }
827
828            fn secret_key_from_bytes(bytes: &[u8]) -> Result<Self::SecretKey, HpkeError> {
829                return <$group as DiffieHellmanGroup>::deserialize_secret_key(bytes);
830            }
831
832            fn encapped_key_to_bytes(out: &mut [u8], encapped_key: &Self::EncappedKey) -> Result<(), HpkeError> {
833                return <$group as DiffieHellmanGroup>::serialize_public_key(out, encapped_key);
834            }
835
836            fn encapped_key_from_bytes(bytes: &[u8]) -> Result<Self::EncappedKey, HpkeError> {
837                return <$group as DiffieHellmanGroup>::deserialize_public_key(bytes);
838            }
839
840            fn encap_with_ephemeral(
841                ephemeral_secret_key: &Self::SecretKey,
842                recipient_public_key: &Self::PublicKey,
843            ) -> Result<(Hash, Self::EncappedKey), HpkeError> {
844                return diffie_hellman_kem_encap_with_ephemeral::<$group, $kdf>($id, ephemeral_secret_key, recipient_public_key);
845            }
846
847            fn decap(encapped_key: &Self::EncappedKey, recipient_secret_key: &Self::SecretKey) -> Result<Hash, HpkeError> {
848                return diffie_hellman_kem_decap::<$group, $kdf>($id, encapped_key, recipient_secret_key);
849            }
850        }
851    };
852}
853
854diffie_hellman_kem!(
855    /// Diffie-Hellman KEM(X25519, HKDF-SHA256) (KEM id `0x0020`).
856    ///
857    /// Provided for interoperability with existing RFC 9180 deployments. It is
858    /// not post-quantum; new deployments SHOULD prefer the hybrid post-quantum
859    /// [`MLKEM768X25519`] KEM.
860    X25519HkdfSha256,
861    X25519Group,
862    HkdfSha256,
863    0x0020,
864    x25519::SecretKey,
865    x25519::PublicKey
866);
867
868diffie_hellman_kem!(
869    /// Diffie-Hellman KEM(P-256, HKDF-SHA256) (KEM id `0x0010`).
870    ///
871    /// Provided for interoperability with existing RFC 9180 deployments. It is
872    /// not post-quantum; new deployments SHOULD prefer the hybrid post-quantum
873    /// [`MLKEM768X25519`] KEM.
874    P256HkdfSha256,
875    P256Group,
876    HkdfSha256,
877    0x0010,
878    p256::SecretKey,
879    p256::PublicKey
880);
881
882diffie_hellman_kem!(
883    /// Diffie-Hellman KEM(P-521, HKDF-SHA512) (KEM id `0x0012`).
884    ///
885    /// Provided for interoperability with existing RFC 9180 deployments. It is
886    /// not post-quantum; new deployments SHOULD prefer the hybrid post-quantum
887    /// [`MLKEM768X25519`] KEM.
888    P521HkdfSha512,
889    P521Group,
890    HkdfSha512,
891    0x0012,
892    p521::SecretKey,
893    p521::PublicKey
894);