Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 101 additions & 14 deletions kem/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 [`KemParams`], such as an encapsulator or decapsulator.
pub type Ciphertext<K> = array::Array<u8, <K as KemParams>::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 [`KemParams`], such as an encapsulator or decapsulator.
pub type SharedSecret<K> = array::Array<u8, <K as KemParams>::SharedSecretSize>;

/// 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 KemParams {
/// 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<EK, SS>: TryKeyInit + KeyExport {
/// Encapsulates a fresh shared secret
fn encapsulate_with_rng<R>(&self, rng: &mut R) -> Result<(EK, SS), R::Error>
where
R: TryCryptoRng + ?Sized;
pub trait Encapsulate: KemParams + TryKeyInit + KeyExport {
/// Encapsulates a fresh [`SharedSecret`] generated using the supplied random number
/// generator `R`.
fn encapsulate_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
) -> Result<(Ciphertext<Self>, SharedSecret<Self>), 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<Self>, SharedSecret<Self>) {
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:
KemParams<
CiphertextSize = <Self::Encapsulator as KemParams>::CiphertextSize,
SharedSecretSize = <Self::Encapsulator as KemParams>::SharedSecretSize,
>
{
/// Encapsulator which corresponds to this decapsulator.
type Encapsulator: Encapsulate + Clone + KemParams;

/// Retrieve the encapsulator associated with this decapsulator.
fn encapsulator(&self) -> &Self::Encapsulator;
}

impl<K: Decapsulator> KemParams for K {
type CiphertextSize = <K::Encapsulator as KemParams>::CiphertextSize;
type SharedSecretSize = <K::Encapsulator as KemParams>::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<EK, SS> {
/// Encapsulator which corresponds to this decapsulator.
type Encapsulator: Encapsulate<EK, SS>;
pub trait Decapsulate: Decapsulator + TryDecapsulate<Error = Infallible> {
/// Decapsulates the given [`Ciphertext`] a.k.a. "encapsulated key".
fn decapsulate(&self, ct: &Ciphertext<Self>) -> SharedSecret<Self>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not define this to do the try_decapsulate then unwrap?

Copy link
Member Author

@tarcieri tarcieri Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because that would introduce a panic.

The idea is that Decapsulate can be used as a TryDecapsulate if you want, but not the other way around (unless Error = Infallible). So if you have a mixture, TryDecapsulate becomes the common abstraction.

Think about it like From/TryFrom. TryFrom has a blanket impl for types which impl From where Error = Infallible. But you wouldn't want the other way around, for From impls to unwrap TryFrom meaning using From might cause panics (this is giving me generic-array flashbacks)


/// 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 `<Self as Kem>::CiphertextSize`.
fn decapsulate_slice(&self, ct: &[u8]) -> Result<SharedSecret<Self>, 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<Self>) -> Result<SharedSecret<Self>, 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 `<Self as Kem>::CiphertextSize`.
fn try_decapsulate_slice(&self, ct: &[u8]) -> Result<SharedSecret<Self>, Self::Error>
where
Self::Error: From<TryFromSliceError>,
{
self.try_decapsulate(ct.try_into()?)
}
}

impl<D> TryDecapsulate for D
where
D: Decapsulate,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decapsulate already implies TryDecapsulate with Error = Infallible, no? It’s in the trait def

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a blanket impl that always satisfies that bound. It means that any type that impls Decapsulate also impls TryDecapsulate with Error = Infallible automatically.

The explicit bound means we always know D can be used as TryDecapsulate (we ran into a problem where a blanket impl like this wasn't working in rust-random/rand_core#51 and it was really hard to debug, so this reduces the problem to why TryDecapsulate isn't impl'd)

{
type Error = Infallible;

fn try_decapsulate(&self, ct: &Ciphertext<Self>) -> Result<SharedSecret<Self>, Infallible> {
Ok(self.decapsulate(ct))
}
}