OpenMLS Traits

⚠️ These traits are responsible for all cryptographic operations and randomness within OpenMLS. Please ensure you know what you're doing when implementing your own versions.

Because implementing the OpenMLSCryptoProvider is challenging, requires tremendous care, and is not what the average OpenMLS consumer wants to (or should) do, we provide two implementations that can be used.

Rust Crypto Provider The go-to default at the moment is an implementation using commonly used, native Rust crypto implementations.

Libcrux Crypto Provider A crypto provider backed by the high-assurance cryptography library [libcrux]. Currently only supports relatively modern x86 and amd64 CPUs, as it requires AES-NI, SIMD and AVX.

The Traits

There are 4 different traits defined in the OpenMLS traits crate.

OpenMlsRand

This trait defines two functions to generate arrays and vectors, and is used by OpenMLS to generate randomness for key generation and random identifiers. While there is the commonly used rand crate, not all implementations use it. OpenMLS, therefore, defines its own randomness trait that needs to be implemented by an OpenMLS crypto provider. It simply needs to implement two functions to generate cryptographically secure randomness and store it in an array or vector.

pub trait OpenMlsRand {
    type Error: std::error::Error + Debug;

    /// Fill an array with random bytes.
    fn random_array<const N: usize>(&self) -> Result<[u8; N], Self::Error>;

    /// Fill a vector of length `len` with bytes.
    fn random_vec(&self, len: usize) -> Result<Vec<u8>, Self::Error>;
}

OpenMlsCrypto

This trait defines all cryptographic functions required by OpenMLS. In particular:

  • HKDF
  • Hashing
  • AEAD
  • Signatures
  • HPKE
};

OpenMlsKeyStore

This trait defines a CRUD API for a key store that is used to store long-term key material from OpenMLS.

The key store provides functions to store, read, and delete values. Note that it does not allow updating values. Instead, entries must be deleted and newly stored.

    /// Identifier used to downcast the actual entity within an [OpenMlsKeyStore] method.
    /// In case for example you need to select a SQL table depending on the entity type
    const ID: MlsEntityId;
}

/// Blanket impl for when you have to lookup a list of entities from the keystore
impl<T> MlsEntity for Vec<T>
where
    T: MlsEntity + std::fmt::Debug,
{
    const ID: MlsEntityId = T::ID;
}

/// The Key Store trait
pub trait OpenMlsKeyStore {
    /// The error type returned by the [`OpenMlsKeyStore`].
    type Error: std::error::Error + std::fmt::Debug;

    /// Store a value `v` that implements the [`MlsEntity`] trait for
    /// serialization for ID `k`.
    ///
    /// Returns an error if storing fails.
    fn store<V: MlsEntity>(&self, k: &[u8], v: &V) -> Result<(), Self::Error>
    where
        Self: Sized;

NOTE: Right now, key material must be extracted from the key store. This will most likely change in the future.

OpenMlsCryptoProvider

Additionally, there's a wrapper trait defined that is expected to be passed into the public OpenMLS API. Some OpenMLS APIs require only one of the sub-traits, though.

/// to perform randomness generation, cryptographic operations, and key storage.
pub trait OpenMlsProvider {
    type CryptoProvider: crypto::OpenMlsCrypto;
    type RandProvider: random::OpenMlsRand;
    type KeyStoreProvider: key_store::OpenMlsKeyStore;

    /// Get the crypto provider.
    fn crypto(&self) -> &Self::CryptoProvider;

    /// Get the randomness provider.
    fn rand(&self) -> &Self::RandProvider;

    /// Get the key store provider.
    fn key_store(&self) -> &Self::KeyStoreProvider;

Implementation Notes

It is not necessary to implement all sub-traits if one functionality is missing. Suppose you want to use a persisting key store. In that case, it is sufficient to do a new implementation of the key store trait and combine it with one of the provided crypto and randomness trait implementations.