use ml_kem::{
DecapsulationKey1024, EncapsulationKey1024, KeyExport, MlKem1024,
kem::{Decapsulate, Encapsulate, Kem},
};
use zeroize::Zeroizing;
use crate::errors::CryptoError;
pub const PUBLIC_KEY_BYTES: usize = 1568;
pub const SECRET_KEY_BYTES: usize = 64;
pub const CIPHERTEXT_BYTES: usize = 1568;
pub const SHARED_SECRET_BYTES: usize = 32;
pub type SharedSecret = Zeroizing<[u8; SHARED_SECRET_BYTES]>;
pub struct KEMPair {
sec_key: DecapsulationKey1024,
}
impl KEMPair {
pub fn create() -> Self {
let (sec_key, _) = MlKem1024::generate_keypair();
Self { sec_key }
}
pub fn from_bytes(pub_key: &[u8], sec_key: &[u8]) -> Result<Self, CryptoError> {
let seed: Zeroizing<[u8; SECRET_KEY_BYTES]> = Zeroizing::new(
sec_key
.try_into()
.map_err(|_| CryptoError::IncongruentLength(SECRET_KEY_BYTES, sec_key.len()))?,
);
let pair = Self {
sec_key: DecapsulationKey1024::from_seed((*seed).into()),
};
if pair.pub_key_bytes()[..] != *pub_key {
return Err(CryptoError::InvalidKey);
}
Ok(pair)
}
pub fn pub_key_bytes(&self) -> [u8; PUBLIC_KEY_BYTES] {
self.sec_key.encapsulation_key().to_bytes().into()
}
pub fn to_bytes(&self) -> ([u8; PUBLIC_KEY_BYTES], Zeroizing<[u8; SECRET_KEY_BYTES]>) {
(
self.pub_key_bytes(),
Zeroizing::new(self.sec_key.to_bytes().into()),
)
}
pub fn to_bytes_uniform(&self) -> Zeroizing<Vec<u8>> {
let (pub_key, sec_key) = self.to_bytes();
Zeroizing::new([&pub_key[..], &sec_key[..]].concat())
}
pub fn from_bytes_uniform(bytes: &[u8]) -> Result<Self, CryptoError> {
if bytes.len() != PUBLIC_KEY_BYTES + SECRET_KEY_BYTES {
return Err(CryptoError::IncongruentLength(
PUBLIC_KEY_BYTES + SECRET_KEY_BYTES,
bytes.len(),
));
}
let (pub_key, sec_key) = bytes.split_at(PUBLIC_KEY_BYTES);
Self::from_bytes(pub_key, sec_key)
}
pub fn encapsulate(
receiver_pubkey: &[u8; PUBLIC_KEY_BYTES],
) -> Result<(SharedSecret, [u8; CIPHERTEXT_BYTES]), CryptoError> {
let ek = EncapsulationKey1024::new(receiver_pubkey.into())
.map_err(|_| CryptoError::InvalidKey)?;
let (ciphertext, shared_secret) = ek.encapsulate();
Ok((Zeroizing::new(shared_secret.into()), ciphertext.into()))
}
pub fn decapsulate(&self, ciphertext: &[u8; CIPHERTEXT_BYTES]) -> SharedSecret {
Zeroizing::new(self.sec_key.decapsulate(ciphertext.into()).into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_keypair() {
let keypair = KEMPair::create();
let (pub_key, sec_key) = keypair.to_bytes();
let new_keypair = KEMPair::from_bytes(&pub_key, &sec_key[..]).unwrap();
assert_eq!(keypair.to_bytes_uniform(), new_keypair.to_bytes_uniform());
let uniform = KEMPair::from_bytes_uniform(&keypair.to_bytes_uniform()).unwrap();
assert_eq!(keypair.to_bytes_uniform(), uniform.to_bytes_uniform());
}
#[test]
fn test_from_bytes_rejects_foreign_pubkey() {
let keypair = KEMPair::create();
let other = KEMPair::create();
let result = KEMPair::from_bytes(&other.pub_key_bytes(), &keypair.to_bytes().1[..]);
assert!(matches!(result, Err(CryptoError::InvalidKey)));
assert!(matches!(
KEMPair::from_bytes(&keypair.pub_key_bytes(), &[0u8; 10]),
Err(CryptoError::IncongruentLength(SECRET_KEY_BYTES, 10))
));
}
#[test]
fn test_invalid_inputs() {
let keypair = KEMPair::create();
assert!(matches!(
KEMPair::from_bytes_uniform(&[0u8; 10]),
Err(CryptoError::IncongruentLength(n, 10)) if n == PUBLIC_KEY_BYTES + SECRET_KEY_BYTES
));
assert!(matches!(
KEMPair::from_bytes(&keypair.pub_key_bytes()[1..], &keypair.to_bytes().1[..]),
Err(CryptoError::InvalidKey)
));
assert!(matches!(
KEMPair::encapsulate(&[0xFF; PUBLIC_KEY_BYTES]),
Err(CryptoError::InvalidKey)
));
}
#[test]
fn test_encapsulate_decapsulate() {
let receiver = KEMPair::create();
let (shared_secret, ciphertext) = KEMPair::encapsulate(&receiver.pub_key_bytes()).unwrap();
let dec_shared_secret = receiver.decapsulate(&ciphertext);
assert_eq!(
shared_secret, dec_shared_secret,
"Difference in shared secrets!"
);
assert_ne!(shared_secret, KEMPair::create().decapsulate(&ciphertext));
}
}