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
107 changes: 58 additions & 49 deletions kem/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,74 +13,82 @@ pub use common::{
};

use common::array::{self, ArraySize};
use core::fmt::Debug;
use core::{array::TryFromSliceError, convert::Infallible};
use rand_core::CryptoRng;

#[cfg(feature = "getrandom")]
use common::getrandom::{SysRng, rand_core::UnwrapErr};

/// KEM decryption key (i.e. private key) which can decrypt encrypted shared secret ciphertexts
/// which were encrypted by [`EncapsulationKey<K>`].
pub type DecapsulationKey<K> = <K as Kem>::DecapsulationKey;

/// KEM encryption key (i.e. public key) which encrypts shared secrets into ciphertexts which
/// can be decrypted by [`DecapsulationKey<K>`].
pub type EncapsulationKey<K> = <K as Kem>::EncapsulationKey;

/// Shared key: plaintext produced after decapsulation by [`Decapsulate::decapsulate`] which is
/// also returned by [`Encapsulate::encapsulate`], which is the shared secret resulting from the
/// key encapsulation algorithm.
pub type SharedKey<K> = array::Array<u8, <K as Kem>::SharedKeySize>;

/// 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>;
/// an encrypted [`SharedKey`] that can be decrypted using [`Decapsulate::decapsulate`].
pub type Ciphertext<K> = array::Array<u8, <K as Kem>::CiphertextSize>;

/// Shared secret: plaintext produced after decapsulation by [`Decapsulate::decapsulate`] which is
/// also returned by [`Encapsulate::encapsulate`].
/// Key encapsulation mechanism.
///
/// `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>;
/// This trait describes the entire type family used by a KEM.
pub trait Kem: Copy + Clone + Debug + Default + Eq + Ord + Send + Sync + 'static {
Copy link
Member Author

Choose a reason for hiding this comment

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

These are the same bounds as elliptic_curve::Curve. They're important when a type that impls Kem is used as a generic parameter on other types, so custom derive works because it doesn't freak out about the bounds of the ZST it's generic around.

/// KEM decryption key (i.e. private key) which can decrypt encrypted shared secret ciphertexts
/// which were encrypted by [`Kem::EncapsulationKey`].
type DecapsulationKey: TryDecapsulate<Self> + Generate;

/// 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`].
/// KEM encryption key (i.e. public key) which encrypts shared secrets into ciphertexts which
/// can be decrypted by [`Kem::DecapsulationKey`].
type EncapsulationKey: Encapsulate<Self> + Clone + Debug + Eq;

/// Size of the shared key/secret returned by both encapsulation and decapsulation.
type SharedKeySize: ArraySize;

/// Size of the ciphertext (a.k.a. "encapsulated key") produced by [`Self::EncapsulationKey`].
type CiphertextSize: ArraySize;

/// Size of the shared secret after decapsulation by [`Decapsulate::decapsulate`].
type SharedSecretSize: ArraySize;
/// Generate a random KEM keypair using the provided random number generator.
fn generate_keypair_from_rng<R: CryptoRng>(
rng: &mut R,
) -> (Self::DecapsulationKey, Self::EncapsulationKey) {
let dk = Self::DecapsulationKey::generate_from_rng(rng);
let ek = dk.as_ref().clone();
(dk, ek)
}

/// Generate a random KEM keypair using the system's secure RNG.
#[cfg(feature = "getrandom")]
fn generate_keypair() -> (Self::DecapsulationKey, Self::EncapsulationKey) {
Self::generate_keypair_from_rng(&mut UnwrapErr(SysRng))
}
}

/// 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: KemParams + TryKeyInit + KeyExport {
/// Encapsulates a fresh [`SharedSecret`] generated using the supplied random number
pub trait Encapsulate<K: Kem>: TryKeyInit + KeyExport {
Copy link
Member Author

Choose a reason for hiding this comment

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

Now that I think about it, these generic parameters are important for binding an impl of Encapsulate/(Try)Decapsulate to a specific KEM algorithm, e.g. if an HSM library wanted to add one.

Unlike signature we can't infer the algorithm from the return type.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm also kind of torn using a generic here. There will probably be only one impl per type, so we don't need overlapping bounds.

It means that everyone needs to notate it in the downstream bounds, which is a little annoying, but they'll need to bound on Kem anyway since it's the trait holding everything together.

If we moved it to an associated type it would get out of the way, but then we should probably get rid of the Decapsulator trait so we don't have to worry about both Decapsulator and Decapsulate defining an associated Kem type.

We would, however, still have to worry about both Decapsulate and TryDecapsulate defining associated Kem types, and if we moved encapsulator to those traits they would then have overlapping encapsulator methods (which is the problem Decapsaulator exists to solve in the first place)

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree, it's kind of annoying having multiple generics here. A thought: what about putting all methods inside Kem, and just removing all the other traits? Instead of ek.encap(...), you'd call K::encap(ek, ...). Benefits: single generic parameter to manage, one place to define an entire KEM. Downside: it's not clear how to handle TryDecapsulate anymore. For what it's worth, I think it might be fine to make decapsulation always fallible (as of today, the only possible error in any known KEM is ciphertext deserialization). If someone wants to convey it's infallible, they can set Kem::DecapError = Infallible. Thoughts?

Copy link
Member Author

Choose a reason for hiding this comment

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

What's nice about the current approach is it's flexible to permit a Decapsulate or TryDecapsulate impl on a hardware-backed decapsulation key, where you can still retrieve its associated encapsulation key using a software implementation.

But also: we still need types to hold the state of decoded decapsulation/encapsulation keys. This trait is intended to be impl'd on a ZST.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahh, ok. So some simplifications that remain are: absorb Encapsulate into Kem, and merge Decapsulate and TryDecapsulate. I don't feel strongly about either though. Just thoughts

/// Encapsulates a fresh [`SharedKey`] generated using the supplied random number
/// generator `R`.
fn encapsulate_with_rng<R>(&self, rng: &mut R) -> (Ciphertext<Self>, SharedSecret<Self>)
fn encapsulate_with_rng<R>(&self, rng: &mut R) -> (Ciphertext<K>, SharedKey<K>)
where
R: CryptoRng + ?Sized;

/// Encapsulate a fresh shared secret generated using the system's secure RNG.
#[cfg(feature = "getrandom")]
fn encapsulate(&self) -> (Ciphertext<Self>, SharedSecret<Self>) {
fn encapsulate(&self) -> (Ciphertext<K>, SharedKey<K>) {
self.encapsulate_with_rng(&mut UnwrapErr(SysRng))
}
}

/// 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.
///
Expand All @@ -90,15 +98,15 @@ impl<K: Decapsulator> KemParams for K {
///
/// 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: Decapsulator + TryDecapsulate<Error = Infallible> {
pub trait Decapsulate<K: Kem>: TryDecapsulate<K, Error = Infallible> {
/// Decapsulates the given [`Ciphertext`] a.k.a. "encapsulated key".
fn decapsulate(&self, ct: &Ciphertext<Self>) -> SharedSecret<Self>;
fn decapsulate(&self, ct: &Ciphertext<K>) -> SharedKey<K>;

/// 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> {
fn decapsulate_slice(&self, ct: &[u8]) -> Result<SharedKey<K>, TryFromSliceError> {
ct.try_into().map(|ct| self.decapsulate(&ct))
}
}
Expand All @@ -108,32 +116,33 @@ pub trait Decapsulate: Decapsulator + TryDecapsulate<Error = Infallible> {
///
/// Prefer to implement the [`Decapsulate`] trait if possible. See that trait's documentation for
/// more information.
pub trait TryDecapsulate: Decapsulator {
pub trait TryDecapsulate<K: Kem>: AsRef<K::EncapsulationKey> {
/// 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>;
fn try_decapsulate(&self, ct: &Ciphertext<K>) -> Result<SharedKey<K>, 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>
fn try_decapsulate_slice(&self, ct: &[u8]) -> Result<SharedKey<K>, Self::Error>
where
Self::Error: From<TryFromSliceError>,
{
self.try_decapsulate(ct.try_into()?)
}
}

impl<D> TryDecapsulate for D
impl<D, K> TryDecapsulate<K> for D
where
D: Decapsulate,
D: Decapsulate<K>,
K: Kem,
{
type Error = Infallible;

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