use core::fmt;
use crate::Error;
use crate::error::KeyFormatError;
use crate::mid::mlkem768;
pub struct Algorithm {
id: AlgorithmId,
}
#[derive(Clone, Copy, Debug)]
enum AlgorithmId {
MlKem768,
}
impl fmt::Debug for Algorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.id.fmt(f)
}
}
pub static ML_KEM_768: Algorithm = Algorithm {
id: AlgorithmId::MlKem768,
};
pub struct DecapsulationKey(mlkem768::DecapKey);
impl DecapsulationKey {
pub fn generate(algorithm: &'static Algorithm) -> Result<Self, Error> {
match algorithm.id {
AlgorithmId::MlKem768 => mlkem768::DecapKey::generate().map(Self),
}
}
pub fn public_key(&self) -> EncapsulationKey {
EncapsulationKey::wrap(self.0.encapsulation_key())
}
pub fn decapsulate(self, ciphertext: &[u8]) -> Result<SharedSecret, Error> {
let ciphertext: [u8; 1088] = ciphertext.try_into().map_err(|_| Error::WrongLength)?;
Ok(SharedSecret(
self.0.decaps(&mlkem768::Ciphertext::from(ciphertext)),
))
}
}
impl fmt::Debug for DecapsulationKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DecapsulationKey").finish_non_exhaustive()
}
}
#[derive(Clone)]
pub struct EncapsulationKey {
key: mlkem768::EncapKey,
bytes: [u8; 1184],
}
impl EncapsulationKey {
pub fn new(algorithm: &'static Algorithm, bytes: &[u8]) -> Result<Self, Error> {
match algorithm.id {
AlgorithmId::MlKem768 => {
let bytes = bytes.try_into().map_err(|_| KeyFormatError::WrongLength)?;
mlkem768::EncapKey::from_bytes(bytes).map(Self::wrap)
}
}
}
fn wrap(key: mlkem768::EncapKey) -> Self {
Self {
bytes: key.as_bytes(),
key,
}
}
pub fn encapsulate(&self) -> Result<(Ciphertext, SharedSecret), Error> {
let (secret, ciphertext) = self.key.encaps()?;
Ok((Ciphertext(ciphertext), SharedSecret(secret)))
}
}
impl AsRef<[u8]> for EncapsulationKey {
fn as_ref(&self) -> &[u8] {
&self.bytes
}
}
impl fmt::Debug for EncapsulationKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("EncapsulationKey")
.field(&self.as_ref())
.finish()
}
}
#[derive(Clone, Debug)]
pub struct Ciphertext(mlkem768::Ciphertext);
impl AsRef<[u8]> for Ciphertext {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
pub struct SharedSecret(mlkem768::SharedSecret);
impl AsRef<[u8]> for SharedSecret {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl fmt::Debug for SharedSecret {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("SharedSecret").finish_non_exhaustive()
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use serde::Deserialize;
use super::*;
use crate::low;
use crate::test::{Expected, wycheproof};
#[derive(Deserialize, Debug)]
struct KemTest {
#[serde(rename = "tcId")]
id: usize,
#[serde(default, with = "hex::serde")]
seed: Vec<u8>,
#[serde(default, with = "hex::serde")]
ek: Vec<u8>,
#[serde(default, with = "hex::serde")]
dk: Vec<u8>,
#[serde(default, with = "hex::serde")]
m: Vec<u8>,
#[serde(default, with = "hex::serde")]
c: Vec<u8>,
#[serde(default, rename = "K", with = "hex::serde")]
k: Vec<u8>,
result: Expected,
}
fn secret_bytes(secret: &SharedSecret) -> Vec<u8> {
let bytes = secret.as_ref().to_vec();
low::ct::public_slice(&bytes);
bytes
}
#[test]
fn wycheproof_keygen() {
for test in wycheproof::<KemTest>("mlkem_768_keygen_seed_test.json") {
assert_eq!(test.result, Expected::Valid, "{}", test.id);
let key = mlkem768::DecapKey::keygen_internal(&test.seed.try_into().unwrap());
assert_eq!(&low::ct::into_public(key.as_bytes())[..], &test.dk);
let key = DecapsulationKey(key);
assert_eq!(key.public_key().as_ref(), &test.ek);
}
}
#[test]
fn wycheproof_encaps() {
for test in wycheproof::<KemTest>("mlkem_768_encaps_test.json") {
let m = mlkem768::Message(test.m.try_into().unwrap());
let result = EncapsulationKey::new(&ML_KEM_768, &test.ek).map(|key| {
let (secret, ciphertext) = key.key.encaps_internal(m);
(secret_bytes(&SharedSecret(secret)), ciphertext)
});
match (test.result, &result) {
(Expected::Valid, Ok((secret, ciphertext))) => {
assert_eq!(secret, &test.k, "{}", test.id);
assert_eq!(&ciphertext.as_ref()[..], &test.c, "{}", test.id);
}
(Expected::Invalid, Err(Error::KeyRejected(_))) => {}
_ => panic!("{}: expected {:?} got {:?}", test.id, test.result, result),
}
}
}
#[test]
fn wycheproof_decaps() {
for test in wycheproof::<KemTest>("mlkem_768_test.json") {
let Ok(seed) = test.seed.as_slice().try_into() else {
assert_eq!(test.result, Expected::Invalid, "{}", test.id);
continue;
};
let key = DecapsulationKey(mlkem768::DecapKey::keygen_internal(&seed));
assert_eq!(key.public_key().as_ref(), &test.ek, "{}", test.id);
let result = key.decapsulate(&test.c).map(|secret| secret_bytes(&secret));
match (test.result, &result) {
(Expected::Valid, Ok(secret)) => assert_eq!(secret, &test.k, "{}", test.id),
(Expected::Invalid, Err(Error::WrongLength)) => {}
_ => panic!("{}: expected {:?} got {:?}", test.id, test.result, result),
}
}
}
mod crosscheck {
use aws_lc_rs::kem::{self, ML_KEM_768};
use proptest::prelude::*;
use proptest::sample::Index;
use super::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(32))]
#[test]
fn encapsulated_by_theirs(alter in prop::option::of((any::<Index>(), 1..=255u8))) {
let ours = DecapsulationKey::generate(&super::ML_KEM_768).unwrap();
let theirs = kem::DecapsulationKey::new(
&ML_KEM_768,
&low::ct::into_public(ours.0.as_bytes()),
)
.unwrap();
let (ciphertext, secret) =
kem::EncapsulationKey::new(&ML_KEM_768, ours.public_key().as_ref())
.unwrap()
.encapsulate()
.unwrap();
let mut ciphertext = ciphertext.as_ref().to_vec();
if let Some((index, xor)) = alter {
let index = index.index(ciphertext.len());
ciphertext[index] ^= xor;
}
let theirs_secret = theirs.decapsulate(kem::Ciphertext::from(&ciphertext[..])).unwrap();
let ours_secret = secret_bytes(&ours.decapsulate(&ciphertext).unwrap());
prop_assert_eq!(&ours_secret[..], theirs_secret.as_ref());
prop_assert_eq!(&ours_secret[..] == secret.as_ref(), alter.is_none());
}
}
}
}