From e492693c6f91b1ac5450594ddfd52d89fe389ece Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Thu, 22 Jan 2026 15:44:10 -0700 Subject: [PATCH 1/2] kem: use byte arrays for `EK` and `SS` Some terminology: - `EK`: encapsulated key, i.e. ciphertext, not to be confused with "encapsulation key", the public key - `SS`: shared secret, output of the decapsulator when given a ciphertext a.k.a. encapsulated key `EK` and `SK` were previously generic parameters on the `Encapsulate` and `Decapsulate` traits, however KEMs don't benefit from overlapping impls and have relatively fixed notions of what these types should be. This commit replaces them with new type aliases `Ciphertext` and `SharedSecret`: - `Ciphertext`: type alias for `Array`, a.k.a. "encapsulated key", where `Array` is from `hybrid-array`. - `SharedSecret`: type alias for `Array` This means consumers of the traits always use bytestrings, which should hopefully make it dramatically simpler to implement things generically across KEMs. The `K` generic parameter above is for types which impl a new `Kem` trait which defines two associated `ArraySize`s: - `Kem::CiphertextSize`: size of the ciphertext - `Kem::SharedSecretSize`: size of the shared secret This was split out into its own trait so the `Ciphertext` and `SharedSecret` type aliases work with either encapsulators or decapsulators. Next, `Decapsulate` was split into three(!) traits to handle fallible decapsulation: - `Decapsulate`: what we had before with the `Decapsulate::Encapsulator` associated type extracted into a supertrait `Decapsulator`, which it now bounds on. It has a provided `decapsulate_slice` method which returns `core::array::TryFromSliceError` in the event the provided slice does not match `CiphertextSize` - `TryDecapsulate`: fallible equivalent of `Decapsulate`, kind of like what we had prior to #2216, with an associated `Error` type and with a `try_decapsulate` method that returns a result. It also bounds on `Decapsulator` as its supertrait. - `Decapsulator`: common supertrait of `Decapsulate` and `TryDecapsulate` which defines the associated `Encapsulator` and provides the `Decapsulator::encapsulator` method for retrieving it. A blanket impl of `TryDecapsulate` is provided for types which impl `Decapsulate` which uses `Infallible` as the error type, so any type which impls `Decapsulate` can be used as a `TryDecapsulate`-bounded argument. Likewise `Decapsulate` carries a `TryDecapsulate` bound which is satisfied by the blanket impl but also enforces this property. The reason we need to reintroduce fallible decapsulation is `dhkem`: when I was scanning over our KEMs repo looking at the error types, `dhkem` has `Error = Infallible`, but this hides that it was using `elliptic_curve::PublicKey` as its "encapsulated key" / ciphertext type, which is a well-typed wrapper for a valid curve point. With this type now being a raw byte slice, `dhkem` needs to handle decoding the curve point in the `TryDecapsulate` impl, and if the point fails to decode return an error (there are ways we could pseudorandomly select a different point in constant time to compute a rejection symbol, but having it return an error in this case seems like a straightforward way to start). Closes #2219 --- kem/src/lib.rs | 115 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 101 insertions(+), 14 deletions(-) diff --git a/kem/src/lib.rs b/kem/src/lib.rs index ac2be9b0a..2c3007d14 100644 --- a/kem/src/lib.rs +++ b/kem/src/lib.rs @@ -12,44 +12,131 @@ pub use common::{ self, Generate, InvalidKey, Key, KeyExport, KeyInit, KeySizeUser, TryKeyInit, typenum::consts, }; +use common::array::{self, ArraySize}; +use core::{array::TryFromSliceError, convert::Infallible}; use rand_core::TryCryptoRng; #[cfg(feature = "getrandom")] use {common::getrandom, rand_core::TryRngCore}; +/// Ciphertext message (a.k.a. "encapsulated key") produced by [`Encapsulate::encapsulate`] which is +/// an encrypted [`SharedSecret`] that can be decrypted using [`Decapsulate::decapsulate`]. +/// +/// `K` is expected to be a type that impls [`Kem`], such as an encapsulator or decapsulator. +pub type Ciphertext = array::Array::CiphertextSize>; + +/// Shared secret: plaintext produced after decapsulation by [`Decapsulate::decapsulate`] which is +/// also returned by [`Encapsulate::encapsulate`]. +/// +/// `K` is expected to be a type that impls [`Kem`], such as an encapsulator or decapsulator. +pub type SharedSecret = array::Array::SharedSecretSize>; + +/// Key encapsulation mechanism. +/// +/// This trait is impl'd by types that impl either [`Encapsulate`] or [`Decapsulate`] and defines +/// the sizes of the encapsulated key and shared secret. +pub trait Kem { + /// Size of the ciphertext (a.k.a. "encapsulated key") produced by [`Encapsulate::encapsulate`]. + type CiphertextSize: ArraySize; + + /// Size of the shared secret after decapsulation by [`Decapsulate::decapsulate`]. + type SharedSecretSize: ArraySize; +} + /// Encapsulator for shared secrets. /// /// Often, this will just be a public key. However, it can also be a bundle of public keys, or it /// can include a sender's private key for authenticated encapsulation. -pub trait Encapsulate: TryKeyInit + KeyExport { - /// Encapsulates a fresh shared secret - fn encapsulate_with_rng(&self, rng: &mut R) -> Result<(EK, SS), R::Error> - where - R: TryCryptoRng + ?Sized; +pub trait Encapsulate: Kem + TryKeyInit + KeyExport { + /// Encapsulates a fresh [`SharedSecret`] generated using the supplied random number + /// generator `R`. + fn encapsulate_with_rng( + &self, + rng: &mut R, + ) -> Result<(Ciphertext, SharedSecret), R::Error>; /// Encapsulate a fresh shared secret generated using the system's secure RNG. #[cfg(feature = "getrandom")] - fn encapsulate(&self) -> (EK, SS) { + fn encapsulate(&self) -> (Ciphertext, SharedSecret) { match self.encapsulate_with_rng(&mut getrandom::SysRng.unwrap_err()) { Ok(ret) => ret, } } } -/// Decapsulator for an encapsulated keys, with an associated encapsulator. +/// Trait for decapsulators, which is a supertrait bound of both [`Decapsulate`] and +/// [`TryDecapsulate`]. +pub trait Decapsulator: + Kem< + CiphertextSize = ::CiphertextSize, + SharedSecretSize = ::SharedSecretSize, + > +{ + /// Encapsulator which corresponds to this decapsulator. + type Encapsulator: Encapsulate + Clone + Kem; + + /// Retrieve the encapsulator associated with this decapsulator. + fn encapsulator(&self) -> &Self::Encapsulator; +} + +impl Kem for K { + type CiphertextSize = ::CiphertextSize; + type SharedSecretSize = ::SharedSecretSize; +} + +/// Decapsulator for encapsulated keys, with an associated `Encapsulator` bounded by the +/// [`Encapsulate`] trait. /// /// Often, this will just be a secret key. But, as with [`Encapsulate`], it can be a bundle /// of secret keys, or it can include a sender's private key for authenticated encapsulation. +/// It could also be a hardware device like an HSM, TPM, or SEP. /// /// When possible (i.e. for software / non-HSM implementations) types which impl this trait should /// also impl the [`Generate`] trait to support key generation. -pub trait Decapsulate { - /// Encapsulator which corresponds to this decapsulator. - type Encapsulator: Encapsulate; +pub trait Decapsulate: Decapsulator + TryDecapsulate { + /// Decapsulates the given [`Ciphertext`] a.k.a. "encapsulated key". + fn decapsulate(&self, ct: &Ciphertext) -> SharedSecret; - /// Decapsulates the given encapsulated key - fn decapsulate(&self, encapsulated_key: &EK) -> SS; + /// Decapsulate the given byte slice containing a [`Ciphertext`] a.k.a. "encapsulated key". + /// + /// # Errors + /// - If the length of `ct` is not equal to `::CiphertextSize`. + fn decapsulate_slice(&self, ct: &[u8]) -> Result, TryFromSliceError> { + ct.try_into().map(|ct| self.decapsulate(&ct)) + } +} - /// Retrieve the encapsulator associated with this decapsulator. - fn encapsulator(&self) -> Self::Encapsulator; +/// Decapsulator for encapsulated keys with failure handling, with an associated `Encapsulator` +/// bounded by the [`Encapsulate`] trait. +/// +/// Prefer to implement the [`Decapsulate`] trait if possible. See that trait's documentation for +/// more information. +pub trait TryDecapsulate: Decapsulator { + /// Decapsulation error + type Error: core::error::Error; + + /// Decapsulates the given [`Ciphertext`] a.k.a. "encapsulated key". + fn try_decapsulate(&self, ct: &Ciphertext) -> Result, Self::Error>; + + /// Decapsulate the given byte slice containing a [`Ciphertext`] a.k.a. "encapsulated key". + /// + /// # Errors + /// - If the length of `ct` is not equal to `::CiphertextSize`. + fn try_decapsulate_slice(&self, ct: &[u8]) -> Result, Self::Error> + where + Self::Error: From, + { + self.try_decapsulate(ct.try_into()?) + } +} + +impl TryDecapsulate for D +where + D: Decapsulate, +{ + type Error = Infallible; + + fn try_decapsulate(&self, ct: &Ciphertext) -> Result, Infallible> { + Ok(self.decapsulate(ct)) + } } From 095ab8e3bb6f930cad1bc8bcae2119922ad3545a Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Fri, 23 Jan 2026 09:47:19 -0700 Subject: [PATCH 2/2] Rename Kem trait to KemParams --- kem/src/lib.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/kem/src/lib.rs b/kem/src/lib.rs index 2c3007d14..697ae2b95 100644 --- a/kem/src/lib.rs +++ b/kem/src/lib.rs @@ -22,20 +22,20 @@ use {common::getrandom, rand_core::TryRngCore}; /// Ciphertext message (a.k.a. "encapsulated key") produced by [`Encapsulate::encapsulate`] which is /// an encrypted [`SharedSecret`] that can be decrypted using [`Decapsulate::decapsulate`]. /// -/// `K` is expected to be a type that impls [`Kem`], such as an encapsulator or decapsulator. -pub type Ciphertext = array::Array::CiphertextSize>; +/// `K` is expected to be a type that impls [`KemParams`], such as an encapsulator or decapsulator. +pub type Ciphertext = array::Array::CiphertextSize>; /// Shared secret: plaintext produced after decapsulation by [`Decapsulate::decapsulate`] which is /// also returned by [`Encapsulate::encapsulate`]. /// -/// `K` is expected to be a type that impls [`Kem`], such as an encapsulator or decapsulator. -pub type SharedSecret = array::Array::SharedSecretSize>; +/// `K` is expected to be a type that impls [`KemParams`], such as an encapsulator or decapsulator. +pub type SharedSecret = array::Array::SharedSecretSize>; -/// Key encapsulation mechanism. +/// Key encapsulation mechanism parameters: sizes of the ciphertext and decrypted plaintext. /// /// This trait is impl'd by types that impl either [`Encapsulate`] or [`Decapsulate`] and defines /// the sizes of the encapsulated key and shared secret. -pub trait Kem { +pub trait KemParams { /// Size of the ciphertext (a.k.a. "encapsulated key") produced by [`Encapsulate::encapsulate`]. type CiphertextSize: ArraySize; @@ -47,7 +47,7 @@ pub trait Kem { /// /// Often, this will just be a public key. However, it can also be a bundle of public keys, or it /// can include a sender's private key for authenticated encapsulation. -pub trait Encapsulate: Kem + TryKeyInit + KeyExport { +pub trait Encapsulate: KemParams + TryKeyInit + KeyExport { /// Encapsulates a fresh [`SharedSecret`] generated using the supplied random number /// generator `R`. fn encapsulate_with_rng( @@ -67,21 +67,21 @@ pub trait Encapsulate: Kem + TryKeyInit + KeyExport { /// Trait for decapsulators, which is a supertrait bound of both [`Decapsulate`] and /// [`TryDecapsulate`]. pub trait Decapsulator: - Kem< - CiphertextSize = ::CiphertextSize, - SharedSecretSize = ::SharedSecretSize, + KemParams< + CiphertextSize = ::CiphertextSize, + SharedSecretSize = ::SharedSecretSize, > { /// Encapsulator which corresponds to this decapsulator. - type Encapsulator: Encapsulate + Clone + Kem; + type Encapsulator: Encapsulate + Clone + KemParams; /// Retrieve the encapsulator associated with this decapsulator. fn encapsulator(&self) -> &Self::Encapsulator; } -impl Kem for K { - type CiphertextSize = ::CiphertextSize; - type SharedSecretSize = ::SharedSecretSize; +impl KemParams for K { + type CiphertextSize = ::CiphertextSize; + type SharedSecretSize = ::SharedSecretSize; } /// Decapsulator for encapsulated keys, with an associated `Encapsulator` bounded by the