use alloc::vec::Vec;
use core::fmt;
use ed25519_dalek::{Signer as Ed25519Signer, Verifier as Ed25519Verifier};
use ed25519_dalek::{SigningKey as Ed25519SigningKey, VerifyingKey as Ed25519VerifyingKey};
use hybrid_array;
use ml_dsa::signature::{Keypair, Signer as MlDsaSigner, Verifier as MlDsaVerifier};
use ml_dsa::{B32, KeyGen as MlDsaKeyGen, KeyPair as MlDsaKeyPair, MlDsa65};
use ml_kem::kem::{Decapsulate, Encapsulate};
use ml_kem::{Ciphertext, EncodedSizeUser, KemCore, MlKem1024, SharedKey};
use rand_core::CryptoRngCore;
use sha2::{Digest, Sha256};
use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret as X25519SecretKey};
use zeroize::Zeroize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PqcError {
EncapsulationFailed,
DecapsulationFailed,
InvalidCiphertextSize,
InvalidKeySize,
SeedConversionFailed,
}
impl fmt::Display for PqcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PqcError::EncapsulationFailed => write!(f, "ML-KEM encapsulation failed"),
PqcError::DecapsulationFailed => write!(f, "ML-KEM decapsulation failed"),
PqcError::InvalidCiphertextSize => write!(f, "Invalid ciphertext size"),
PqcError::InvalidKeySize => write!(f, "Invalid key size"),
PqcError::SeedConversionFailed => write!(f, "Seed conversion failed"),
}
}
}
type MlKem1024Ek = <MlKem1024 as KemCore>::EncapsulationKey;
type MlKem1024Dk = <MlKem1024 as KemCore>::DecapsulationKey;
type MlKem1024Ct = Ciphertext<MlKem1024>;
type MlKem1024Ss = SharedKey<MlKem1024>;
type MlDsa65Kp = MlDsaKeyPair<MlDsa65>;
type MlDsa65SigningKey = ml_dsa::SigningKey<MlDsa65>;
type MlDsa65VerifyingKey = ml_dsa::VerifyingKey<MlDsa65>;
type MlDsa65Signature = ml_dsa::Signature<MlDsa65>;
pub const MLKEM_EK_BYTES: usize = 1568;
pub const MLKEM_DK_BYTES: usize = 3168;
pub const MLKEM_CT_BYTES: usize = 1568;
pub const SHARED_SECRET_BYTES: usize = 32;
pub const X25519_KEY_BYTES: usize = 32;
pub const HYBRID_PK_BYTES: usize = MLKEM_EK_BYTES + X25519_KEY_BYTES;
pub const HYBRID_SK_BYTES: usize = MLKEM_DK_BYTES + X25519_KEY_BYTES;
pub const HYBRID_CT_BYTES: usize = MLKEM_CT_BYTES + X25519_KEY_BYTES;
pub const MLDSA_VK_BYTES: usize = 1952;
pub const MLDSA_SK_BYTES: usize = 4032;
pub const MLDSA_SIG_BYTES: usize = 3309;
pub const ED25519_KEY_BYTES: usize = 32;
pub const ED25519_SIG_BYTES: usize = 64;
pub const HYBRID_SIG_PK_BYTES: usize = MLDSA_VK_BYTES + ED25519_KEY_BYTES;
pub const HYBRID_SIG_SK_BYTES: usize = MLDSA_SK_BYTES + ED25519_KEY_BYTES;
pub const HYBRID_SIG_BYTES: usize = MLDSA_SIG_BYTES + ED25519_SIG_BYTES;
#[derive(Clone)]
pub struct MlKemEncapsulationKey {
inner: MlKem1024Ek,
}
pub struct MlKemDecapsulationKey {
inner: MlKem1024Dk,
}
#[derive(Clone)]
pub struct MlKemCiphertext {
pub bytes: [u8; MLKEM_CT_BYTES],
}
pub fn mlkem_keygen(
rng: &mut impl CryptoRngCore,
) -> (MlKemEncapsulationKey, MlKemDecapsulationKey) {
let (dk, ek) = MlKem1024::generate(rng);
(
MlKemEncapsulationKey { inner: ek },
MlKemDecapsulationKey { inner: dk },
)
}
pub fn mlkem_encaps(
ek: &MlKemEncapsulationKey,
rng: &mut impl CryptoRngCore,
) -> Result<(MlKemCiphertext, [u8; SHARED_SECRET_BYTES]), PqcError> {
let (ct, ss): (MlKem1024Ct, MlKem1024Ss) = ek
.inner
.encapsulate(rng)
.map_err(|_| PqcError::EncapsulationFailed)?;
let mut ct_bytes = [0u8; MLKEM_CT_BYTES];
ct_bytes.copy_from_slice(ct.as_slice());
let mut ss_bytes = [0u8; SHARED_SECRET_BYTES];
ss_bytes.copy_from_slice(ss.as_slice());
Ok((MlKemCiphertext { bytes: ct_bytes }, ss_bytes))
}
pub fn mlkem_decaps(
ct: &MlKemCiphertext,
dk: &MlKemDecapsulationKey,
) -> Result<[u8; SHARED_SECRET_BYTES], PqcError> {
let ct_arr =
MlKem1024Ct::try_from(ct.bytes.as_slice()).map_err(|_| PqcError::InvalidCiphertextSize)?;
let ss: MlKem1024Ss = dk
.inner
.decapsulate(&ct_arr)
.map_err(|_| PqcError::DecapsulationFailed)?;
let mut ss_bytes = [0u8; SHARED_SECRET_BYTES];
ss_bytes.copy_from_slice(ss.as_slice());
Ok(ss_bytes)
}
impl MlKemEncapsulationKey {
pub fn to_bytes(&self) -> [u8; MLKEM_EK_BYTES] {
let mut out = [0u8; MLKEM_EK_BYTES];
out.copy_from_slice(&self.inner.as_bytes());
out
}
pub fn from_bytes(data: &[u8; MLKEM_EK_BYTES]) -> Self {
use ml_kem::kem::EncapsulationKey;
Self {
inner: EncapsulationKey::from_bytes(data.into()),
}
}
}
impl MlKemDecapsulationKey {
pub fn to_bytes(&self) -> [u8; MLKEM_DK_BYTES] {
let mut out = [0u8; MLKEM_DK_BYTES];
out.copy_from_slice(&self.inner.as_bytes());
out
}
pub fn from_bytes(data: &[u8; MLKEM_DK_BYTES]) -> Self {
use ml_kem::kem::DecapsulationKey;
Self {
inner: DecapsulationKey::from_bytes(data.into()),
}
}
}
pub fn x25519_keygen(rng: &mut impl CryptoRngCore) -> (X25519PublicKey, X25519SecretKey) {
let secret = X25519SecretKey::random_from_rng(rng);
let public = X25519PublicKey::from(&secret);
(public, secret)
}
pub fn x25519_dh(our_secret: &X25519SecretKey, their_public: &X25519PublicKey) -> [u8; 32] {
our_secret.diffie_hellman(their_public).to_bytes()
}
#[derive(Clone)]
pub struct HybridPublicKey {
pub mlkem: MlKemEncapsulationKey,
pub x25519: X25519PublicKey,
}
impl HybridPublicKey {
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(HYBRID_PK_BYTES);
out.extend_from_slice(&self.mlkem.to_bytes());
out.extend_from_slice(self.x25519.as_bytes());
out
}
pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() != HYBRID_PK_BYTES {
return None;
}
let mut mlkem_bytes = [0u8; MLKEM_EK_BYTES];
mlkem_bytes.copy_from_slice(&data[..MLKEM_EK_BYTES]);
let mut x25519_bytes = [0u8; X25519_KEY_BYTES];
x25519_bytes.copy_from_slice(&data[MLKEM_EK_BYTES..]);
Some(Self {
mlkem: MlKemEncapsulationKey::from_bytes(&mlkem_bytes),
x25519: X25519PublicKey::from(x25519_bytes),
})
}
}
pub struct HybridSecretKey {
pub mlkem: MlKemDecapsulationKey,
pub x25519: X25519SecretKey,
}
impl HybridSecretKey {
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(HYBRID_SK_BYTES);
out.extend_from_slice(&self.mlkem.to_bytes());
out.extend_from_slice(self.x25519.as_bytes());
out
}
pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() != HYBRID_SK_BYTES {
return None;
}
let mut mlkem_bytes = [0u8; MLKEM_DK_BYTES];
mlkem_bytes.copy_from_slice(&data[..MLKEM_DK_BYTES]);
let mut x25519_bytes = [0u8; X25519_KEY_BYTES];
x25519_bytes.copy_from_slice(&data[MLKEM_DK_BYTES..]);
Some(Self {
mlkem: MlKemDecapsulationKey::from_bytes(&mlkem_bytes),
x25519: X25519SecretKey::from(x25519_bytes),
})
}
}
#[derive(Clone)]
pub struct HybridCiphertext {
pub mlkem_ct: MlKemCiphertext,
pub x25519_eph_pk: X25519PublicKey,
}
impl HybridCiphertext {
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(HYBRID_CT_BYTES);
out.extend_from_slice(&self.mlkem_ct.bytes);
out.extend_from_slice(self.x25519_eph_pk.as_bytes());
out
}
pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() != HYBRID_CT_BYTES {
return None;
}
let mut mlkem_bytes = [0u8; MLKEM_CT_BYTES];
mlkem_bytes.copy_from_slice(&data[..MLKEM_CT_BYTES]);
let mut x25519_bytes = [0u8; X25519_KEY_BYTES];
x25519_bytes.copy_from_slice(&data[MLKEM_CT_BYTES..]);
Some(Self {
mlkem_ct: MlKemCiphertext { bytes: mlkem_bytes },
x25519_eph_pk: X25519PublicKey::from(x25519_bytes),
})
}
}
pub fn hybrid_keygen(rng: &mut impl CryptoRngCore) -> (HybridPublicKey, HybridSecretKey) {
let (mlkem_ek, mlkem_dk) = mlkem_keygen(rng);
let (x25519_pk, x25519_sk) = x25519_keygen(rng);
(
HybridPublicKey {
mlkem: mlkem_ek,
x25519: x25519_pk,
},
HybridSecretKey {
mlkem: mlkem_dk,
x25519: x25519_sk,
},
)
}
pub fn hybrid_encaps(
pk: &HybridPublicKey,
rng: &mut impl CryptoRngCore,
) -> Result<(HybridCiphertext, [u8; SHARED_SECRET_BYTES]), PqcError> {
let (mlkem_ct, mlkem_ss) = mlkem_encaps(&pk.mlkem, rng)?;
let (x25519_eph_pk, x25519_eph_sk) = x25519_keygen(rng);
let x25519_ss = x25519_dh(&x25519_eph_sk, &pk.x25519);
let combined_ss = combine_shared_secrets(&mlkem_ss, &x25519_ss);
let ct = HybridCiphertext {
mlkem_ct,
x25519_eph_pk,
};
Ok((ct, combined_ss))
}
pub fn hybrid_decaps(
ct: &HybridCiphertext,
sk: &HybridSecretKey,
) -> Result<[u8; SHARED_SECRET_BYTES], PqcError> {
let mlkem_ss = mlkem_decaps(&ct.mlkem_ct, &sk.mlkem)?;
let x25519_ss = x25519_dh(&sk.x25519, &ct.x25519_eph_pk);
Ok(combine_shared_secrets(&mlkem_ss, &x25519_ss))
}
fn combine_shared_secrets(ss1: &[u8; 32], ss2: &[u8; 32]) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(b"LCPFS_HYBRID_KEM_V1"); hasher.update(ss1);
hasher.update(ss2);
let result = hasher.finalize();
let mut out = [0u8; 32];
out.copy_from_slice(&result);
out
}
pub type KyberCiphertext = MlKemCiphertext;
pub const KYBER_PK_BYTES: usize = MLKEM_EK_BYTES;
pub const KYBER_SK_BYTES: usize = MLKEM_DK_BYTES;
pub const KYBER_CT_BYTES: usize = MLKEM_CT_BYTES;
#[derive(Clone)]
pub struct KyberPublicKey {
pub data: [u8; MLKEM_EK_BYTES],
inner: MlKemEncapsulationKey,
}
impl KyberPublicKey {
pub fn inner(&self) -> &MlKemEncapsulationKey {
&self.inner
}
}
pub struct KyberSecretKey {
inner: MlKemDecapsulationKey,
}
impl Clone for KyberSecretKey {
fn clone(&self) -> Self {
Self {
inner: MlKemDecapsulationKey::from_bytes(&self.inner.to_bytes()),
}
}
}
impl KyberSecretKey {
pub fn inner(&self) -> &MlKemDecapsulationKey {
&self.inner
}
pub fn clone_key(&self) -> Self {
Self {
inner: MlKemDecapsulationKey::from_bytes(&self.inner.to_bytes()),
}
}
}
pub struct KyberEngine;
impl KyberEngine {
pub fn keypair(seed: &[u8; 32]) -> (KyberPublicKey, KyberSecretKey) {
use rand_core::{CryptoRng, RngCore};
struct SeededRng {
state: [u8; 32],
counter: u64,
}
impl SeededRng {
fn new(seed: &[u8; 32]) -> Self {
Self {
state: *seed,
counter: 0,
}
}
}
impl RngCore for SeededRng {
fn next_u32(&mut self) -> u32 {
self.next_u64() as u32
}
fn next_u64(&mut self) -> u64 {
let mut hasher = Sha256::new();
hasher.update(self.state);
hasher.update(self.counter.to_le_bytes());
let hash = hasher.finalize();
self.counter += 1;
self.state.copy_from_slice(&hash);
u64::from_le_bytes([
hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7],
])
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
for chunk in dest.chunks_mut(8) {
let val = self.next_u64().to_le_bytes();
let len = chunk.len().min(8);
chunk.copy_from_slice(&val[..len]);
}
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
self.fill_bytes(dest);
Ok(())
}
}
impl CryptoRng for SeededRng {}
let mut rng = SeededRng::new(seed);
let (ek, dk) = mlkem_keygen(&mut rng);
let data = ek.to_bytes();
(
KyberPublicKey { data, inner: ek },
KyberSecretKey { inner: dk },
)
}
}
#[derive(Clone)]
pub struct MlDsaVerifyingKey {
inner: MlDsa65VerifyingKey,
}
pub struct MlDsaSigningKey {
inner: MlDsa65SigningKey,
}
#[derive(Clone)]
pub struct MlDsaSignature {
inner: MlDsa65Signature,
}
pub fn mldsa_keygen(
rng: &mut impl CryptoRngCore,
) -> Result<(MlDsaVerifyingKey, MlDsaSigningKey), PqcError> {
let mut seed = [0u8; 32];
rng.fill_bytes(&mut seed);
let seed_arr: B32 = hybrid_array::Array::try_from(seed.as_slice())
.map_err(|_| PqcError::SeedConversionFailed)?;
let kp: MlDsa65Kp = MlDsa65::from_seed(&seed_arr);
Ok((
MlDsaVerifyingKey {
inner: kp.verifying_key().clone(),
},
MlDsaSigningKey {
inner: kp.signing_key().clone(),
},
))
}
pub fn mldsa_sign(sk: &MlDsaSigningKey, message: &[u8]) -> MlDsaSignature {
let sig = sk.inner.sign(message);
MlDsaSignature { inner: sig }
}
pub fn mldsa_verify(vk: &MlDsaVerifyingKey, message: &[u8], sig: &MlDsaSignature) -> bool {
vk.inner.verify(message, &sig.inner).is_ok()
}
impl MlDsaVerifyingKey {
pub fn to_bytes(&self) -> Vec<u8> {
self.inner.encode().to_vec()
}
pub fn from_bytes(data: &[u8]) -> Option<Self> {
use ml_dsa::VerifyingKey;
if data.len() != MLDSA_VK_BYTES {
return None;
}
let arr: ml_dsa::EncodedVerifyingKey<MlDsa65> = hybrid_array::Array::try_from(data).ok()?;
Some(Self {
inner: VerifyingKey::decode(&arr),
})
}
}
impl MlDsaSigningKey {
pub fn to_bytes(&self) -> Vec<u8> {
self.inner.encode().to_vec()
}
pub fn from_bytes(data: &[u8]) -> Option<Self> {
use ml_dsa::SigningKey;
if data.len() != MLDSA_SK_BYTES {
return None;
}
let arr: ml_dsa::EncodedSigningKey<MlDsa65> = hybrid_array::Array::try_from(data).ok()?;
Some(Self {
inner: SigningKey::decode(&arr),
})
}
}
impl MlDsaSignature {
pub fn to_bytes(&self) -> Vec<u8> {
self.inner.encode().to_vec()
}
pub fn from_bytes(data: &[u8]) -> Option<Self> {
use ml_dsa::Signature;
if data.len() != MLDSA_SIG_BYTES {
return None;
}
let arr: ml_dsa::EncodedSignature<MlDsa65> = hybrid_array::Array::try_from(data).ok()?;
let sig = Signature::decode(&arr)?;
Some(Self { inner: sig })
}
}
pub fn ed25519_keygen(rng: &mut impl CryptoRngCore) -> (Ed25519VerifyingKey, Ed25519SigningKey) {
let mut secret_bytes = [0u8; 32];
rng.fill_bytes(&mut secret_bytes);
let signing_key = Ed25519SigningKey::from_bytes(&secret_bytes);
let verifying_key = signing_key.verifying_key();
(verifying_key, signing_key)
}
pub fn ed25519_sign(sk: &Ed25519SigningKey, message: &[u8]) -> ed25519_dalek::Signature {
sk.sign(message)
}
pub fn ed25519_verify(
vk: &Ed25519VerifyingKey,
message: &[u8],
sig: &ed25519_dalek::Signature,
) -> bool {
vk.verify(message, sig).is_ok()
}
#[derive(Clone)]
pub struct HybridSigningPublicKey {
pub mldsa: MlDsaVerifyingKey,
pub ed25519: Ed25519VerifyingKey,
}
impl HybridSigningPublicKey {
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(HYBRID_SIG_PK_BYTES);
out.extend_from_slice(&self.mldsa.to_bytes());
out.extend_from_slice(self.ed25519.as_bytes());
out
}
pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() != HYBRID_SIG_PK_BYTES {
return None;
}
let mldsa = MlDsaVerifyingKey::from_bytes(&data[..MLDSA_VK_BYTES])?;
let mut ed25519_bytes = [0u8; ED25519_KEY_BYTES];
ed25519_bytes.copy_from_slice(&data[MLDSA_VK_BYTES..]);
let ed25519 = Ed25519VerifyingKey::from_bytes(&ed25519_bytes).ok()?;
Some(Self { mldsa, ed25519 })
}
}
pub struct HybridSigningSecretKey {
pub mldsa: MlDsaSigningKey,
pub ed25519: Ed25519SigningKey,
}
impl HybridSigningSecretKey {
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(HYBRID_SIG_SK_BYTES);
out.extend_from_slice(&self.mldsa.to_bytes());
out.extend_from_slice(self.ed25519.as_bytes());
out
}
pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() != HYBRID_SIG_SK_BYTES {
return None;
}
let mldsa = MlDsaSigningKey::from_bytes(&data[..MLDSA_SK_BYTES])?;
let mut ed25519_bytes = [0u8; ED25519_KEY_BYTES];
ed25519_bytes.copy_from_slice(&data[MLDSA_SK_BYTES..]);
let ed25519 = Ed25519SigningKey::from_bytes(&ed25519_bytes);
Some(Self { mldsa, ed25519 })
}
}
#[derive(Clone)]
pub struct HybridSignature {
pub mldsa_sig: MlDsaSignature,
pub ed25519_sig: ed25519_dalek::Signature,
}
impl HybridSignature {
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(HYBRID_SIG_BYTES);
out.extend_from_slice(&self.mldsa_sig.to_bytes());
out.extend_from_slice(&self.ed25519_sig.to_bytes());
out
}
pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() != HYBRID_SIG_BYTES {
return None;
}
let mldsa_sig = MlDsaSignature::from_bytes(&data[..MLDSA_SIG_BYTES])?;
let mut ed25519_bytes = [0u8; ED25519_SIG_BYTES];
ed25519_bytes.copy_from_slice(&data[MLDSA_SIG_BYTES..]);
let ed25519_sig = ed25519_dalek::Signature::from_bytes(&ed25519_bytes);
Some(Self {
mldsa_sig,
ed25519_sig,
})
}
}
pub fn hybrid_sign_keygen(
rng: &mut impl CryptoRngCore,
) -> Result<(HybridSigningPublicKey, HybridSigningSecretKey), PqcError> {
let (mldsa_vk, mldsa_sk) = mldsa_keygen(rng)?;
let (ed25519_vk, ed25519_sk) = ed25519_keygen(rng);
Ok((
HybridSigningPublicKey {
mldsa: mldsa_vk,
ed25519: ed25519_vk,
},
HybridSigningSecretKey {
mldsa: mldsa_sk,
ed25519: ed25519_sk,
},
))
}
pub fn hybrid_sign(sk: &HybridSigningSecretKey, message: &[u8]) -> HybridSignature {
let mldsa_sig = mldsa_sign(&sk.mldsa, message);
let ed25519_sig = ed25519_sign(&sk.ed25519, message);
HybridSignature {
mldsa_sig,
ed25519_sig,
}
}
pub fn hybrid_verify(pk: &HybridSigningPublicKey, message: &[u8], sig: &HybridSignature) -> bool {
let mldsa_ok = mldsa_verify(&pk.mldsa, message, &sig.mldsa_sig);
let ed25519_ok = ed25519_verify(&pk.ed25519, message, &sig.ed25519_sig);
mldsa_ok && ed25519_ok
}
pub type DilithiumVerifyingKey = MlDsaVerifyingKey;
pub type DilithiumSigningKey = MlDsaSigningKey;
pub type DilithiumSignature = MlDsaSignature;
pub struct DilithiumEngine;
impl DilithiumEngine {
pub fn keypair(seed: &[u8; 32]) -> Result<(MlDsaVerifyingKey, MlDsaSigningKey), PqcError> {
use rand_core::{CryptoRng, RngCore};
struct SeededRng {
state: [u8; 32],
counter: u64,
}
impl SeededRng {
fn new(seed: &[u8; 32]) -> Self {
Self {
state: *seed,
counter: 0,
}
}
}
impl RngCore for SeededRng {
fn next_u32(&mut self) -> u32 {
self.next_u64() as u32
}
fn next_u64(&mut self) -> u64 {
let mut hasher = Sha256::new();
hasher.update(self.state);
hasher.update(self.counter.to_le_bytes());
let hash = hasher.finalize();
self.counter += 1;
self.state.copy_from_slice(&hash);
u64::from_le_bytes([
hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7],
])
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
for chunk in dest.chunks_mut(8) {
let val = self.next_u64().to_le_bytes();
let len = chunk.len().min(8);
chunk.copy_from_slice(&val[..len]);
}
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
self.fill_bytes(dest);
Ok(())
}
}
impl CryptoRng for SeededRng {}
let mut rng = SeededRng::new(seed);
mldsa_keygen(&mut rng)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand_core::{CryptoRng, RngCore};
struct TestRng {
state: u64,
}
impl TestRng {
fn new(seed: u64) -> Self {
Self { state: seed }
}
}
impl RngCore for TestRng {
fn next_u32(&mut self) -> u32 {
self.next_u64() as u32
}
fn next_u64(&mut self) -> u64 {
self.state ^= self.state << 13;
self.state ^= self.state >> 7;
self.state ^= self.state << 17;
self.state
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
for chunk in dest.chunks_mut(8) {
let val = self.next_u64().to_le_bytes();
let len = chunk.len().min(8);
chunk.copy_from_slice(&val[..len]);
}
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
self.fill_bytes(dest);
Ok(())
}
}
impl CryptoRng for TestRng {}
#[test]
fn test_mlkem_roundtrip() {
let mut rng = TestRng::new(0x12345678);
let (ek, dk) = mlkem_keygen(&mut rng);
let (ct, ss_enc) = mlkem_encaps(&ek, &mut rng).expect("encapsulation failed");
let ss_dec = mlkem_decaps(&ct, &dk).expect("decapsulation failed");
assert_eq!(ss_enc, ss_dec, "ML-KEM shared secrets must match");
}
#[test]
fn test_x25519_roundtrip() {
let mut rng = TestRng::new(0xABCDEF00);
let (pk_a, sk_a) = x25519_keygen(&mut rng);
let (pk_b, sk_b) = x25519_keygen(&mut rng);
let ss_ab = x25519_dh(&sk_a, &pk_b);
let ss_ba = x25519_dh(&sk_b, &pk_a);
assert_eq!(ss_ab, ss_ba, "X25519 DH must be symmetric");
}
#[test]
fn test_hybrid_roundtrip() {
let mut rng = TestRng::new(0xDEADBEEF);
let (pk, sk) = hybrid_keygen(&mut rng);
let (ct, ss_enc) = hybrid_encaps(&pk, &mut rng).expect("hybrid encapsulation failed");
let ss_dec = hybrid_decaps(&ct, &sk).expect("hybrid decapsulation failed");
assert_eq!(ss_enc, ss_dec, "Hybrid shared secrets must match");
}
#[test]
fn test_hybrid_serialization() {
let mut rng = TestRng::new(0xCAFEBABE);
let (pk, sk) = hybrid_keygen(&mut rng);
let pk_bytes = pk.to_bytes();
assert_eq!(pk_bytes.len(), HYBRID_PK_BYTES);
let pk_restored = HybridPublicKey::from_bytes(&pk_bytes).unwrap();
assert_eq!(pk.mlkem.to_bytes(), pk_restored.mlkem.to_bytes());
assert_eq!(pk.x25519.as_bytes(), pk_restored.x25519.as_bytes());
let sk_bytes = sk.to_bytes();
assert_eq!(sk_bytes.len(), HYBRID_SK_BYTES);
let sk_restored = HybridSecretKey::from_bytes(&sk_bytes).unwrap();
assert_eq!(sk.mlkem.to_bytes(), sk_restored.mlkem.to_bytes());
let (ct, _) = hybrid_encaps(&pk, &mut rng).expect("encapsulation failed");
let ct_bytes = ct.to_bytes();
assert_eq!(ct_bytes.len(), HYBRID_CT_BYTES);
let ct_restored = HybridCiphertext::from_bytes(&ct_bytes).unwrap();
assert_eq!(ct.mlkem_ct.bytes, ct_restored.mlkem_ct.bytes);
assert_eq!(
ct.x25519_eph_pk.as_bytes(),
ct_restored.x25519_eph_pk.as_bytes()
);
}
#[test]
fn test_different_keys_different_secrets() {
let mut rng = TestRng::new(0x11111111);
let (pk1, _sk1) = hybrid_keygen(&mut rng);
let (pk2, sk2) = hybrid_keygen(&mut rng);
let (ct, ss_enc) = hybrid_encaps(&pk1, &mut rng).expect("encapsulation failed");
let ss_wrong = hybrid_decaps(&ct, &sk2).expect("decapsulation failed");
assert_ne!(
ss_enc, ss_wrong,
"Wrong key must not produce correct secret"
);
}
#[test]
fn test_key_sizes() {
let mut rng = TestRng::new(0x99999999);
let (ek, dk) = mlkem_keygen(&mut rng);
assert_eq!(ek.to_bytes().len(), MLKEM_EK_BYTES);
assert_eq!(dk.to_bytes().len(), MLKEM_DK_BYTES);
let (ct, ss) = mlkem_encaps(&ek, &mut rng).expect("encapsulation failed");
assert_eq!(ct.bytes.len(), MLKEM_CT_BYTES);
assert_eq!(ss.len(), SHARED_SECRET_BYTES);
}
#[test]
fn test_mldsa_sign_verify() {
let mut rng = TestRng::new(0xA1A2A3A4);
let (vk, sk) = mldsa_keygen(&mut rng).expect("keygen failed");
let message = b"Hello, post-quantum world!";
let sig = mldsa_sign(&sk, message);
assert!(
mldsa_verify(&vk, message, &sig),
"Valid signature must verify"
);
}
#[test]
fn test_mldsa_wrong_message() {
let mut rng = TestRng::new(0xB1B2B3B4);
let (vk, sk) = mldsa_keygen(&mut rng).expect("keygen failed");
let message = b"Original message";
let sig = mldsa_sign(&sk, message);
let wrong_message = b"Tampered message";
assert!(
!mldsa_verify(&vk, wrong_message, &sig),
"Wrong message must not verify"
);
}
#[test]
fn test_mldsa_wrong_key() {
let mut rng = TestRng::new(0xC1C2C3C4);
let (vk1, sk1) = mldsa_keygen(&mut rng).expect("keygen failed");
let (vk2, _sk2) = mldsa_keygen(&mut rng).expect("keygen failed");
let message = b"Test message";
let sig = mldsa_sign(&sk1, message);
assert!(
!mldsa_verify(&vk2, message, &sig),
"Wrong key must not verify"
);
assert!(mldsa_verify(&vk1, message, &sig), "Correct key must verify");
}
#[test]
fn test_ed25519_sign_verify() {
let mut rng = TestRng::new(0xD1D2D3D4);
let (vk, sk) = ed25519_keygen(&mut rng);
let message = b"Ed25519 test message";
let sig = ed25519_sign(&sk, message);
assert!(
ed25519_verify(&vk, message, &sig),
"Valid signature must verify"
);
}
#[test]
fn test_hybrid_signature_roundtrip() {
let mut rng = TestRng::new(0xE1E2E3E4);
let (pk, sk) = hybrid_sign_keygen(&mut rng).expect("keygen failed");
let message = b"Hybrid signature test message";
let sig = hybrid_sign(&sk, message);
assert!(
hybrid_verify(&pk, message, &sig),
"Hybrid signature must verify"
);
}
#[test]
fn test_hybrid_signature_wrong_message() {
let mut rng = TestRng::new(0xF1F2F3F4);
let (pk, sk) = hybrid_sign_keygen(&mut rng).expect("keygen failed");
let message = b"Original hybrid message";
let sig = hybrid_sign(&sk, message);
let wrong_message = b"Wrong hybrid message";
assert!(
!hybrid_verify(&pk, wrong_message, &sig),
"Wrong message must fail verification"
);
}
#[test]
fn test_hybrid_signature_serialization() {
let mut rng = TestRng::new(0x12121212);
let (pk, sk) = hybrid_sign_keygen(&mut rng).expect("keygen failed");
let pk_bytes = pk.to_bytes();
assert_eq!(pk_bytes.len(), HYBRID_SIG_PK_BYTES);
let pk_restored = HybridSigningPublicKey::from_bytes(&pk_bytes).unwrap();
assert_eq!(pk.mldsa.to_bytes(), pk_restored.mldsa.to_bytes());
let sk_bytes = sk.to_bytes();
assert_eq!(sk_bytes.len(), HYBRID_SIG_SK_BYTES);
let message = b"Test serialization";
let sig = hybrid_sign(&sk, message);
let sig_bytes = sig.to_bytes();
assert_eq!(sig_bytes.len(), HYBRID_SIG_BYTES);
let sig_restored = HybridSignature::from_bytes(&sig_bytes).unwrap();
assert!(
hybrid_verify(&pk, message, &sig_restored),
"Restored signature must verify"
);
}
#[test]
fn test_dilithium_legacy_engine() {
let seed = [0x42u8; 32];
let (vk, sk) = DilithiumEngine::keypair(&seed).expect("legacy keygen failed");
let message = b"Legacy Dilithium test";
let sig = mldsa_sign(&sk, message);
assert!(
mldsa_verify(&vk, message, &sig),
"Legacy Dilithium signature must verify"
);
}
}