Skip to content

Conversation

@lbaquerofierro
Copy link
Contributor

Adds safe Rust wrappers around BoringSSL's ML-KEM (FIPS 203) key encapsulation, covering both ML-KEM-768 and ML-KEM-1024 parameter sets.

  • Key generation, encapsulation, and decapsulation
  • Seed-based private key storage and restoration
  • Public key derivation, serialization, and validation
  • Wipes private key material from memory on drop
  • Tests for roundtrip, implicit rejection, debug redaction, and validation

@bwesterb bwesterb changed the title Add safe Rust wrappers for ML-KEM-268 and ML-KEM-1020 Add safe Rust wrappers for ML-KEM-768 and ML-KEM-1024 Jan 28, 2026
@bwesterb bwesterb self-requested a review January 28, 2026 14:14
Copy link
Collaborator

@cjpatton cjpatton left a comment

Choose a reason for hiding this comment

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

It should be possible to deduplicate this code using traits.

pub trait Kem {
    /// Note: SEED_BYTES and SHARED_SECRET_BYTES are the same for all ML-KEM parameters
    const PUBLIC_KEY_BYTES: usize;
    const CIPHERTEXT_BYTES: usize;
    type PublicKey;
    type PrivateKey;
}

pub trait KemOps: Kem {
    fn generate() -> (Kem::PublicKey, Kem::PrivateKey) { ... }
    fn encapsulate .. 
    fn decapsulate ...
}

@lbaquerofierro lbaquerofierro force-pushed the lina/mlkem-wrappers branch 2 times, most recently from 719707e to 377c7b3 Compare January 28, 2026 22:24
@cjpatton cjpatton self-requested a review January 29, 2026 16:58
Adds safe Rust wrappers around BoringSSL's ML-KEM (FIPS 203) key
encapsulation, covering both ML-KEM-768 and ML-KEM-1024 parameter sets.

- Key generation, encapsulation, and decapsulation
- Seed-based private key storage and restoration
- Public key derivation, serialization, and validation
- Wipes private key material from memory on drop
- Tests for roundtrip, implicit rejection, debug redaction, and validation
Copy link
Collaborator

@cjpatton cjpatton left a comment

Choose a reason for hiding this comment

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

Hmm, I think defining the Kem and KemOps traits are worth it unless we can actually deduplicate between MLKEM768 and 1024. I think we'll either need to live with duplicated code (I'd be OK with this) or figure out something else.

I think the fundamental problem is that the API we're trying to design doesn't quite match the API of BoringSSL.

I think hash.rs in this directory may provide the inspiration we need. There is a struct Hasher that has a constructor (new) that takes in MessageDigest that defines the algorithm in use (SHA-256, SHA-384, etc.). Then the API calls for actually using the hash is the same regardless of which algorithm is used.

I think we could do the same thing for a struct Kem. Its constructor would determine whether we're using 768 or 1024, and it would have a unified API. One wrinkle is that we would need to handle the the public key and private key as byte strings, but I think that's pretty reasonable.

For example:

pub MlKemParam(...);

pub struct MlKem { ... }

impl MlKem {
   pub fn new(p: MlKemParam) -> Result<Self, ErrorStack> { ... }
   pub fn key_gen(&self) -> Result<(Vec<u8>, [SEED_BYTES; u8]), ErrorStack> { ... } // returns public key, private key
   pub fn encapsulate(&self, pk: &[u8]) -> Result<(Vec<u8>, [SHARED_SECRET_BYTES; u8], ErrorStack> { ... } // returns ciphertext
   pub fn decapsulate(&self, sk: &[u8], ciphertext: &[u8]) -> Result<[u8; SHARED_SECRET_BYTES], ErrorStack> { ... }
}

Notes:

  1. Elsewhere in this crate crypto operations return ErrorStack instead of a module-specific error type. I think that's probably appropriate here as well.
  2. The length of the public key and ciphertext depend on the KEM type, which is why I've handled them as a vector. Unfortunately we can't use an array without adding the length of the array as a generic const. This could work, but would clutter the API unnecessarily. An alternative that we can make alloc-free is have the user pass a &mut [u8] buffer and fail if it's the wrong size.

Comment on lines +3 to +4
//! ML-KEM is a low-level cryptographic primitive. For most applications,
//! using higher-level constructions like HPKE is preferred.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
//! ML-KEM is a low-level cryptographic primitive. For most applications,
//! using higher-level constructions like HPKE is preferred.
//! ML-KEM is a low-level cryptographic primitive. For most applications,
//! using higher-level constructions like HPKE is preferred.
//! Note that it's also enabled in TLS by default, in the X25519MLKEM768 exchange.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants