#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(all(feature = "alloc", any(feature = "aes-gcm")))]
use alloc::vec::Vec;
pub mod error;
pub mod rng;
pub mod cast;
pub mod state;
pub mod pct;
pub mod preop;
#[cfg(feature = "fips_140_3")]
pub mod csp;
#[cfg(all(feature = "ml-kem", feature = "fips_140_3"))]
pub(crate) mod kat_kyber;
#[cfg(all(feature = "ml-dsa", feature = "fips_140_3"))]
pub(crate) mod kat_dilithium;
pub use error::{PqcError, Result};
pub use state::{FipsState, get_fips_state, is_operational, reset_fips_state};
pub use preop::{run_post, run_post_or_panic};
#[cfg(feature = "fips_140_3")]
pub use csp::{CspExportPolicy, get_csp_export_policy};
pub const ML_KEM_1024_PK_BYTES: usize = 1568;
pub const ML_KEM_1024_SK_BYTES: usize = 3168;
pub const ML_KEM_1024_CT_BYTES: usize = 1568;
pub const ML_KEM_1024_SS_BYTES: usize = 32;
pub const ML_KEM_KEYGEN_SEED_BYTES: usize = 64;
pub const ML_KEM_ENCAP_SEED_BYTES: usize = 32;
pub const ML_DSA_65_PK_BYTES: usize = 1952;
pub const ML_DSA_65_SK_BYTES: usize = 4032; pub const ML_DSA_65_SIG_BYTES: usize = 3309; pub const ML_DSA_KEYGEN_SEED_BYTES: usize = 32; pub const ML_DSA_SIGN_SEED_BYTES: usize = 32;
#[cfg(feature = "aes-gcm")]
pub const AES_KEY_BYTES: usize = 32;
#[cfg(feature = "aes-gcm")]
pub const AES_NONCE_BYTES: usize = 12;
#[cfg(feature = "ml-kem")]
use libcrux_ml_kem::mlkem1024::{
MlKem1024Ciphertext, MlKem1024PrivateKey, MlKem1024PublicKey,
generate_key_pair, encapsulate, decapsulate,
};
#[cfg(feature = "ml-kem")]
pub type KyberPublicKey = MlKem1024PublicKey;
#[cfg(feature = "ml-kem")]
pub type KyberSecretKey = MlKem1024PrivateKey;
#[cfg(feature = "ml-kem")]
pub type KyberCiphertext = MlKem1024Ciphertext;
#[cfg(feature = "ml-kem")]
pub type KyberSharedSecret = [u8; 32];
#[cfg(feature = "ml-kem")]
pub struct KyberKeys {
pub pk: KyberPublicKey,
pub sk: KyberSecretKey,
}
#[cfg(feature = "ml-kem")]
impl KyberKeys {
#[cfg(feature = "std")]
pub fn generate_key_pair() -> Self {
let seed = rng::generate_seed_64();
Self::generate_key_pair_with_seed(seed)
}
pub fn generate_key_pair_with_seed(seed: [u8; ML_KEM_KEYGEN_SEED_BYTES]) -> Self {
rng::validate_seed_64(&seed);
let _secure = rng::SecureSeed(seed);
let keypair = generate_key_pair(seed);
Self {
pk: keypair.pk().clone().into(),
sk: keypair.sk().clone().into(),
}
}
#[cfg(feature = "std")]
pub fn generate_key_pair_with_pct() -> Result<Self> {
let keys = Self::generate_key_pair();
pct::kyber_pct(&keys)?;
Ok(keys)
}
pub fn generate_key_pair_with_seed_and_pct(
seed: [u8; ML_KEM_KEYGEN_SEED_BYTES]
) -> Result<Self> {
let keys = Self::generate_key_pair_with_seed(seed);
pct::kyber_pct(&keys)?;
Ok(keys)
}
}
#[cfg(feature = "ml-dsa")]
use libcrux_ml_dsa::ml_dsa_65::{
MLDSA65SigningKey, MLDSA65VerificationKey, MLDSA65Signature,
generate_key_pair as dsa_generate_key_pair,
sign as dsa_sign,
verify as dsa_verify,
};
#[cfg(feature = "ml-dsa")]
pub type DilithiumPublicKey = MLDSA65VerificationKey;
#[cfg(feature = "ml-dsa")]
pub type DilithiumSecretKey = MLDSA65SigningKey;
#[cfg(feature = "ml-dsa")]
pub type DilithiumSignature = MLDSA65Signature;
#[cfg(feature = "ml-kem")]
pub fn encapsulate_shared_secret(
_pk: &KyberPublicKey
) -> (KyberCiphertext, KyberSharedSecret) {
#[cfg(feature = "std")]
{
let randomness = rng::generate_seed_32();
encapsulate_shared_secret_with_randomness(_pk, randomness)
}
#[cfg(not(feature = "std"))]
{
panic!("encapsulate_shared_secret requires std feature or use encapsulate_shared_secret_with_randomness");
}
}
#[cfg(feature = "ml-kem")]
pub fn encapsulate_shared_secret_with_randomness(
pk: &KyberPublicKey,
randomness: [u8; ML_KEM_ENCAP_SEED_BYTES]
) -> (KyberCiphertext, KyberSharedSecret) {
rng::validate_seed_32(&randomness);
let _secure = rng::SecureSeed32(randomness);
encapsulate(pk, randomness)
}
#[cfg(feature = "ml-kem")]
pub fn decapsulate_shared_secret(
sk: &KyberSecretKey,
ct: &KyberCiphertext
) -> KyberSharedSecret {
decapsulate(sk, ct)
}
#[cfg(feature = "ml-dsa")]
pub fn generate_dilithium_keypair() -> (DilithiumPublicKey, DilithiumSecretKey) {
#[cfg(feature = "std")]
{
let seed = rng::generate_seed_32(); generate_dilithium_keypair_with_seed(seed)
}
#[cfg(not(feature = "std"))]
{
panic!("generate_dilithium_keypair requires std feature or use generate_dilithium_keypair_with_seed");
}
}
#[cfg(feature = "ml-dsa")]
pub fn generate_dilithium_keypair_with_seed(
seed: [u8; ML_DSA_KEYGEN_SEED_BYTES]
) -> (DilithiumPublicKey, DilithiumSecretKey) {
rng::validate_seed_32(&seed);
let _secure = rng::SecureSeed32(seed);
let keypair = dsa_generate_key_pair(seed);
(keypair.verification_key, keypair.signing_key)
}
#[cfg(feature = "ml-dsa")]
pub fn generate_dilithium_keypair_with_pct() -> Result<(DilithiumPublicKey, DilithiumSecretKey)> {
let (pk, sk) = generate_dilithium_keypair();
pct::dilithium_pct(&pk, &sk)?;
Ok((pk, sk))
}
#[cfg(feature = "ml-dsa")]
pub fn sign_message(_sk: &DilithiumSecretKey, _msg: &[u8]) -> DilithiumSignature {
#[cfg(feature = "std")]
{
let randomness = rng::generate_seed_32();
sign_message_with_randomness(_sk, _msg, randomness)
}
#[cfg(not(feature = "std"))]
{
panic!("sign_message requires std feature or use sign_message_with_randomness");
}
}
#[cfg(feature = "ml-dsa")]
pub fn sign_message_with_randomness(
sk: &DilithiumSecretKey,
msg: &[u8],
randomness: [u8; ML_DSA_SIGN_SEED_BYTES]
) -> DilithiumSignature {
rng::validate_seed_32(&randomness);
let _secure = rng::SecureSeed32(randomness);
dsa_sign(sk, msg, &[], randomness)
.expect("Signing failed - this should not happen with valid keys")
}
#[cfg(feature = "ml-dsa")]
pub fn verify_signature(
pk: &DilithiumPublicKey,
msg: &[u8],
sig: &DilithiumSignature
) -> bool {
dsa_verify(pk, msg, &[], sig).is_ok()
}
#[cfg(feature = "aes-gcm")]
use aes_gcm::{
aead::{Aead, KeyInit},
Aes256Gcm, Key, Nonce,
};
#[cfg(feature = "aes-gcm")]
pub fn encrypt_aes_gcm(
key_bytes: &[u8; AES_KEY_BYTES],
nonce_bytes: &[u8; AES_NONCE_BYTES],
plaintext: &[u8],
) -> Result<Vec<u8>> {
let key = Key::<Aes256Gcm>::from_slice(key_bytes);
let cipher = Aes256Gcm::new(key);
let nonce = Nonce::from_slice(nonce_bytes);
cipher.encrypt(nonce, plaintext)
.map_err(|_| PqcError::AesGcmOperationFailed)
}
#[cfg(feature = "aes-gcm")]
pub fn decrypt_aes_gcm(
key_bytes: &[u8; AES_KEY_BYTES],
nonce_bytes: &[u8; AES_NONCE_BYTES],
ciphertext: &[u8],
) -> Result<Vec<u8>> {
let key = Key::<Aes256Gcm>::from_slice(key_bytes);
let cipher = Aes256Gcm::new(key);
let nonce = Nonce::from_slice(nonce_bytes);
cipher.decrypt(nonce, ciphertext)
.map_err(|_| PqcError::AesGcmOperationFailed)
}
#[cfg(test)]
mod tests {
#[cfg(any(feature = "ml-kem", feature = "ml-dsa", feature = "aes-gcm"))]
use super::*;
#[test]
#[cfg(all(feature = "ml-kem", feature = "std"))]
fn test_kyber_roundtrip() {
let keys = KyberKeys::generate_key_pair();
let (ct, ss1) = encapsulate_shared_secret(&keys.pk);
let ss2 = decapsulate_shared_secret(&keys.sk, &ct);
assert_eq!(ss1, ss2);
}
#[test]
#[cfg(all(feature = "ml-dsa", feature = "std"))]
fn test_dilithium_sign_verify() {
let (pk, sk) = generate_dilithium_keypair();
let msg = b"test message";
let sig = sign_message(&sk, msg);
assert!(verify_signature(&pk, msg, &sig));
}
#[test]
#[cfg(all(feature = "aes-gcm", feature = "alloc"))]
fn test_aes_gcm_roundtrip() {
let key = [1u8; 32];
let nonce = [2u8; 12];
let plaintext = b"secret data";
let ciphertext = encrypt_aes_gcm(&key, &nonce, plaintext).unwrap();
let decrypted = decrypt_aes_gcm(&key, &nonce, &ciphertext).unwrap();
assert_eq!(plaintext, &decrypted[..]);
}
}