use crate::unified_api::logging::{KeyPurpose, KeyType};
use tracing::debug;
use crate::primitives::{
ec::{
ed25519::{Ed25519KeyPair, Ed25519Signature as Ed25519SignatureOps},
traits::{EcKeyPair, EcSignature},
},
kem::ml_kem::{MlKem, MlKemSecurityLevel},
sig::{
fndsa::FnDsaSecurityLevel,
ml_dsa::{MlDsaParameterSet, generate_keypair as ml_dsa_generate_keypair},
slh_dsa::{SigningKey as SlhDsaSigningKey, SlhDsaSecurityLevel},
},
};
use crate::types::{PrivateKey, PublicKey};
use crate::unified_api::CoreConfig;
use crate::unified_api::error::{CoreError, Result};
#[must_use = "discarding a generated keypair wastes entropy and leaks key material"]
pub fn generate_keypair() -> Result<(PublicKey, PrivateKey)> {
super::api::fips_verify_operational()?;
debug!("Generating Ed25519 keypair");
let keypair = Ed25519KeyPair::generate().map_err(|e| CoreError::KeyGenerationFailed {
reason: format!("Ed25519 key generation failed: {e}"),
recovery: "Check RNG availability and retry".to_string(),
})?;
validate_ed25519_keypair(&keypair)?;
let public_key = PublicKey::new(keypair.public_key_bytes());
let mut sk_zeroizing = keypair.secret_key_bytes();
let sk_bytes = std::mem::take(&mut *sk_zeroizing);
let private_key = PrivateKey::new(sk_bytes);
crate::log_key_generated!("ed25519-keypair", "Ed25519", KeyType::KeyPair, KeyPurpose::Signing);
Ok((public_key, private_key))
}
#[must_use = "discarding a generated keypair wastes entropy and leaks key material"]
pub fn generate_keypair_with_config(config: &CoreConfig) -> Result<(PublicKey, PrivateKey)> {
config.validate()?;
generate_keypair()
}
fn validate_ed25519_keypair(keypair: &Ed25519KeyPair) -> Result<()> {
let public_bytes = keypair.public_key_bytes();
if public_bytes.iter().all(|&b| b == 0) {
return Err(CoreError::KeyGenerationFailed {
reason: "Public key is identity element".to_string(),
recovery: "Generate a new keypair, identity element is invalid".to_string(),
});
}
let private_bytes = keypair.secret_key_bytes();
if private_bytes.iter().all(|&b| b == 0) {
return Err(CoreError::KeyGenerationFailed {
reason: "Private key is zero".to_string(),
recovery: "Generate a new keypair, zero private key is invalid".to_string(),
});
}
let test_message = b"key_validation_test";
let signature = keypair.sign(test_message);
Ed25519SignatureOps::verify(&public_bytes, test_message, &signature).map_err(|e| {
CoreError::KeyGenerationFailed {
reason: format!("Keypair validation failed: {e}"),
recovery: "Regenerate keypair and retry validation".to_string(),
}
})?;
Ok(())
}
pub fn generate_ml_kem_keypair(
security_level: MlKemSecurityLevel,
) -> Result<(PublicKey, PrivateKey)> {
super::api::fips_verify_operational()?;
debug!(security_level = ?security_level, "Generating ML-KEM keypair");
let (pk, sk) =
MlKem::generate_keypair(security_level).map_err(|e| CoreError::KeyGenerationFailed {
reason: format!("ML-KEM key generation failed: {}", e),
recovery: "Check security level and RNG".to_string(),
})?;
let algorithm = format!("{:?}", security_level);
crate::log_key_generated!(
"ml-kem-keypair",
algorithm,
KeyType::KeyPair,
KeyPurpose::KeyExchange
);
let mut sk_bytes = sk.into_bytes();
let sk_data = std::mem::take(&mut *sk_bytes);
Ok((PublicKey::new(pk.into_bytes()), PrivateKey::new(sk_data)))
}
pub fn generate_ml_kem_keypair_with_config(
security_level: MlKemSecurityLevel,
config: &CoreConfig,
) -> Result<(PublicKey, PrivateKey)> {
config.validate()?;
generate_ml_kem_keypair(security_level)
}
pub fn generate_ml_dsa_keypair(
parameter_set: MlDsaParameterSet,
) -> Result<(PublicKey, PrivateKey)> {
debug!(parameter_set = ?parameter_set, "Generating ML-DSA keypair");
let (pk, sk) =
ml_dsa_generate_keypair(parameter_set).map_err(|e| CoreError::KeyGenerationFailed {
reason: format!("ML-DSA key generation failed: {}", e),
recovery: "Check parameter set".to_string(),
})?;
let algorithm = format!("{:?}", parameter_set);
crate::log_key_generated!("ml-dsa-keypair", algorithm, KeyType::KeyPair, KeyPurpose::Signing);
Ok((PublicKey::new(pk.as_bytes().to_vec()), PrivateKey::new(sk.as_bytes().to_vec())))
}
pub fn generate_ml_dsa_keypair_with_config(
parameter_set: MlDsaParameterSet,
config: &CoreConfig,
) -> Result<(PublicKey, PrivateKey)> {
config.validate()?;
generate_ml_dsa_keypair(parameter_set)
}
pub fn generate_slh_dsa_keypair(
security_level: SlhDsaSecurityLevel,
) -> Result<(PublicKey, PrivateKey)> {
debug!(security_level = ?security_level, "Generating SLH-DSA keypair");
let (sk, pk) =
SlhDsaSigningKey::generate(security_level).map_err(|e| CoreError::KeyGenerationFailed {
reason: format!("SLH-DSA key generation failed: {}", e),
recovery: "Check security level".to_string(),
})?;
let algorithm = format!("{:?}", security_level);
crate::log_key_generated!("slh-dsa-keypair", algorithm, KeyType::KeyPair, KeyPurpose::Signing);
Ok((PublicKey::new(pk.as_bytes().to_vec()), PrivateKey::new(sk.as_bytes().to_vec())))
}
pub fn generate_slh_dsa_keypair_with_config(
security_level: SlhDsaSecurityLevel,
config: &CoreConfig,
) -> Result<(PublicKey, PrivateKey)> {
config.validate()?;
generate_slh_dsa_keypair(security_level)
}
pub fn generate_fn_dsa_keypair() -> Result<(PublicKey, PrivateKey)> {
generate_fn_dsa_keypair_with_level(FnDsaSecurityLevel::Level512)
}
pub fn generate_fn_dsa_keypair_with_level(
level: FnDsaSecurityLevel,
) -> Result<(PublicKey, PrivateKey)> {
debug!("Generating FN-DSA keypair ({:?})", level);
let keypair = crate::primitives::sig::fndsa::KeyPair::generate(level).map_err(|e| {
CoreError::KeyGenerationFailed {
reason: format!("FN-DSA key generation failed: {}", e),
recovery: "Check RNG availability".to_string(),
}
})?;
let level_name = match level {
FnDsaSecurityLevel::Level512 => "FN-DSA-512",
FnDsaSecurityLevel::Level1024 => "FN-DSA-1024",
};
crate::log_key_generated!("fn-dsa-keypair", level_name, KeyType::KeyPair, KeyPurpose::Signing);
Ok((
PublicKey::new(keypair.verifying_key().to_bytes()),
PrivateKey::new((*keypair.signing_key().to_bytes()).clone()),
))
}
pub fn generate_fn_dsa_keypair_with_config(config: &CoreConfig) -> Result<(PublicKey, PrivateKey)> {
config.validate()?;
generate_fn_dsa_keypair()
}
#[cfg(test)]
#[allow(
clippy::panic,
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::arithmetic_side_effects,
clippy::panic_in_result_fn,
clippy::unnecessary_wraps,
clippy::redundant_clone,
clippy::useless_vec,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::clone_on_copy,
clippy::len_zero,
clippy::single_match,
clippy::unnested_or_patterns,
clippy::default_constructed_unit_structs,
clippy::redundant_closure_for_method_calls,
clippy::semicolon_if_nothing_returned,
clippy::unnecessary_unwrap,
clippy::redundant_pattern_matching,
clippy::missing_const_for_thread_local,
clippy::get_first,
clippy::float_cmp,
clippy::needless_borrows_for_generic_args,
unused_qualifications
)]
mod tests {
use super::*;
use crate::primitives::kem::ml_kem::MlKemSecurityLevel;
use crate::primitives::sig::ml_dsa::MlDsaParameterSet;
use crate::primitives::sig::slh_dsa::SlhDsaSecurityLevel;
use crate::unified_api::convenience::ed25519::{
sign_ed25519_unverified, verify_ed25519_unverified,
};
use crate::unified_api::convenience::pq_kem::encrypt_pq_ml_kem_unverified;
use crate::unified_api::convenience::pq_sig::{
sign_pq_ml_dsa_unverified, verify_pq_ml_dsa_unverified,
};
use crate::unified_api::convenience::pq_sig::{
sign_pq_slh_dsa_unverified, verify_pq_slh_dsa_unverified,
};
#[test]
fn test_ed25519_keypair_format_has_correct_sizes_has_correct_size() -> Result<()> {
let (pk, sk) = generate_keypair()?;
assert_eq!(pk.len(), 32, "Ed25519 public key must be exactly 32 bytes");
assert_eq!(sk.as_ref().len(), 32, "Ed25519 secret key must be exactly 32 bytes");
Ok(())
}
#[test]
fn test_ed25519_keypair_functionality_signs_and_verifies_succeeds() -> Result<()> {
let (pk, sk) = generate_keypair()?;
let message = b"Test message to verify key functionality";
let signature = sign_ed25519_unverified(message, sk.as_ref())?;
let is_valid = verify_ed25519_unverified(message, &signature, pk.as_slice())?;
assert!(is_valid, "Generated keypair should produce valid signatures");
Ok(())
}
#[test]
fn test_ed25519_keypair_uniqueness_produces_distinct_keys_are_unique() -> Result<()> {
let (pk1, sk1) = generate_keypair()?;
let (pk2, sk2) = generate_keypair()?;
let (pk3, sk3) = generate_keypair()?;
assert_ne!(pk1, pk2, "Public keys must be unique");
assert_ne!(pk1, pk3, "Public keys must be unique");
assert_ne!(pk2, pk3, "Public keys must be unique");
assert_ne!(sk1.as_ref(), sk2.as_ref(), "Secret keys must be unique");
assert_ne!(sk1.as_ref(), sk3.as_ref(), "Secret keys must be unique");
assert_ne!(sk2.as_ref(), sk3.as_ref(), "Secret keys must be unique");
Ok(())
}
#[test]
fn test_ed25519_keypair_with_config_succeeds() -> Result<()> {
let config = CoreConfig::default();
let (pk, sk) = generate_keypair_with_config(&config)?;
assert_eq!(pk.len(), 32);
assert_eq!(sk.as_ref().len(), 32);
let message = b"Config test";
let signature = sign_ed25519_unverified(message, sk.as_ref())?;
let is_valid = verify_ed25519_unverified(message, &signature, pk.as_slice())?;
assert!(is_valid);
Ok(())
}
#[test]
fn test_ed25519_cross_keypair_verification_fails() -> Result<()> {
let (_pk1, sk1) = generate_keypair()?;
let (pk2, _sk2) = generate_keypair()?;
let message = b"Cross validation test";
let signature = sign_ed25519_unverified(message, sk1.as_ref())?;
let result = verify_ed25519_unverified(message, &signature, pk2.as_slice());
assert!(
result.is_err(),
"Signature from one key should not verify with different public key"
);
Ok(())
}
#[test]
fn test_ml_kem_512_keypair_generation_produces_non_empty_keys_fails() -> Result<()> {
let (pk, sk) = generate_ml_kem_keypair(MlKemSecurityLevel::MlKem512)?;
assert!(!pk.is_empty(), "Public key should not be empty");
assert!(!sk.as_ref().is_empty(), "Secret key should not be empty");
let plaintext = b"Test data for ML-KEM-512";
let ciphertext =
encrypt_pq_ml_kem_unverified(plaintext, pk.as_slice(), MlKemSecurityLevel::MlKem512)?;
assert!(ciphertext.len() > plaintext.len(), "Ciphertext should be larger than plaintext");
Ok(())
}
#[test]
fn test_ml_kem_768_keypair_generation_produces_non_empty_keys_fails() -> Result<()> {
let (pk, sk) = generate_ml_kem_keypair(MlKemSecurityLevel::MlKem768)?;
assert!(!pk.is_empty());
assert!(!sk.as_ref().is_empty());
let plaintext = b"Test data";
let ciphertext =
encrypt_pq_ml_kem_unverified(plaintext, pk.as_slice(), MlKemSecurityLevel::MlKem768)?;
assert!(ciphertext.len() > plaintext.len());
Ok(())
}
#[test]
fn test_ml_kem_1024_keypair_generation_produces_non_empty_keys_fails() -> Result<()> {
let (pk, sk) = generate_ml_kem_keypair(MlKemSecurityLevel::MlKem1024)?;
assert!(!pk.is_empty());
assert!(!sk.as_ref().is_empty());
let plaintext = b"Test data";
let ciphertext =
encrypt_pq_ml_kem_unverified(plaintext, pk.as_slice(), MlKemSecurityLevel::MlKem1024)?;
assert!(ciphertext.len() > plaintext.len());
Ok(())
}
#[test]
fn test_ml_kem_keypair_uniqueness_produces_distinct_keys_are_unique() -> Result<()> {
let (pk1, sk1) = generate_ml_kem_keypair(MlKemSecurityLevel::MlKem768)?;
let (pk2, sk2) = generate_ml_kem_keypair(MlKemSecurityLevel::MlKem768)?;
assert_ne!(pk1, pk2, "ML-KEM public keys must be unique");
assert_ne!(sk1.as_ref(), sk2.as_ref(), "ML-KEM secret keys must be unique");
Ok(())
}
#[test]
fn test_ml_kem_with_config_succeeds() -> Result<()> {
let config = CoreConfig::default();
let (pk, sk) = generate_ml_kem_keypair_with_config(MlKemSecurityLevel::MlKem768, &config)?;
assert!(!pk.is_empty());
assert!(!sk.as_ref().is_empty());
let plaintext = b"Config test";
let ciphertext =
encrypt_pq_ml_kem_unverified(plaintext, pk.as_slice(), MlKemSecurityLevel::MlKem768)?;
assert!(ciphertext.len() > plaintext.len());
Ok(())
}
#[test]
fn test_ml_dsa_44_keypair_functionality_signs_and_verifies_succeeds() -> Result<()> {
let (pk, sk) = generate_ml_dsa_keypair(MlDsaParameterSet::MlDsa44)?;
let message = b"Test ML-DSA-44 signature";
let signature =
sign_pq_ml_dsa_unverified(message, sk.as_ref(), MlDsaParameterSet::MlDsa44)?;
let is_valid = verify_pq_ml_dsa_unverified(
message,
&signature,
pk.as_slice(),
MlDsaParameterSet::MlDsa44,
)?;
assert!(is_valid, "Generated ML-DSA-44 keys should produce valid signatures");
Ok(())
}
#[test]
fn test_ml_dsa_65_keypair_functionality_signs_and_verifies_succeeds() -> Result<()> {
let (pk, sk) = generate_ml_dsa_keypair(MlDsaParameterSet::MlDsa65)?;
let message = b"Test ML-DSA-65 signature";
let signature =
sign_pq_ml_dsa_unverified(message, sk.as_ref(), MlDsaParameterSet::MlDsa65)?;
let is_valid = verify_pq_ml_dsa_unverified(
message,
&signature,
pk.as_slice(),
MlDsaParameterSet::MlDsa65,
)?;
assert!(is_valid);
Ok(())
}
#[test]
fn test_ml_dsa_87_keypair_functionality_signs_and_verifies_succeeds() -> Result<()> {
let (pk, sk) = generate_ml_dsa_keypair(MlDsaParameterSet::MlDsa87)?;
let message = b"Test ML-DSA-87 signature";
let signature =
sign_pq_ml_dsa_unverified(message, sk.as_ref(), MlDsaParameterSet::MlDsa87)?;
let is_valid = verify_pq_ml_dsa_unverified(
message,
&signature,
pk.as_slice(),
MlDsaParameterSet::MlDsa87,
)?;
assert!(is_valid);
Ok(())
}
#[test]
fn test_ml_dsa_keypair_uniqueness_produces_distinct_keys_are_unique() -> Result<()> {
let (pk1, sk1) = generate_ml_dsa_keypair(MlDsaParameterSet::MlDsa65)?;
let (pk2, sk2) = generate_ml_dsa_keypair(MlDsaParameterSet::MlDsa65)?;
assert_ne!(pk1, pk2, "ML-DSA public keys must be unique");
assert_ne!(sk1.as_ref(), sk2.as_ref(), "ML-DSA secret keys must be unique");
Ok(())
}
#[test]
fn test_ml_dsa_with_config_succeeds() -> Result<()> {
let config = CoreConfig::default();
let (pk, sk) = generate_ml_dsa_keypair_with_config(MlDsaParameterSet::MlDsa65, &config)?;
let message = b"Config test";
let signature =
sign_pq_ml_dsa_unverified(message, sk.as_ref(), MlDsaParameterSet::MlDsa65)?;
let is_valid = verify_pq_ml_dsa_unverified(
message,
&signature,
pk.as_slice(),
MlDsaParameterSet::MlDsa65,
)?;
assert!(is_valid);
Ok(())
}
#[test]
fn test_ml_dsa_cross_keypair_verification_fails() -> Result<()> {
let (_pk1, sk1) = generate_ml_dsa_keypair(MlDsaParameterSet::MlDsa65)?;
let (pk2, _sk2) = generate_ml_dsa_keypair(MlDsaParameterSet::MlDsa65)?;
let message = b"Cross validation";
let signature =
sign_pq_ml_dsa_unverified(message, sk1.as_ref(), MlDsaParameterSet::MlDsa65)?;
let result = verify_pq_ml_dsa_unverified(
message,
&signature,
pk2.as_slice(),
MlDsaParameterSet::MlDsa65,
);
assert!(result.is_err(), "ML-DSA signature should not verify with different key");
Ok(())
}
#[test]
fn test_slh_dsa_128s_keypair_functionality_signs_and_verifies_succeeds() -> Result<()> {
let (pk, sk) = generate_slh_dsa_keypair(SlhDsaSecurityLevel::Shake128s)?;
let message = b"Test SLH-DSA-128s";
let signature =
sign_pq_slh_dsa_unverified(message, sk.as_ref(), SlhDsaSecurityLevel::Shake128s)?;
let is_valid = verify_pq_slh_dsa_unverified(
message,
&signature,
pk.as_slice(),
SlhDsaSecurityLevel::Shake128s,
)?;
assert!(is_valid, "Generated SLH-DSA keys should produce valid signatures");
Ok(())
}
#[test]
fn test_slh_dsa_keypair_uniqueness_produces_distinct_keys_are_unique() -> Result<()> {
let (pk1, sk1) = generate_slh_dsa_keypair(SlhDsaSecurityLevel::Shake128s)?;
let (pk2, sk2) = generate_slh_dsa_keypair(SlhDsaSecurityLevel::Shake128s)?;
assert_ne!(pk1, pk2, "SLH-DSA public keys must be unique");
assert_ne!(sk1.as_ref(), sk2.as_ref(), "SLH-DSA secret keys must be unique");
Ok(())
}
#[test]
fn test_slh_dsa_with_config_succeeds() -> Result<()> {
let config = CoreConfig::default();
let (pk, sk) =
generate_slh_dsa_keypair_with_config(SlhDsaSecurityLevel::Shake128s, &config)?;
let message = b"Config test";
let signature =
sign_pq_slh_dsa_unverified(message, sk.as_ref(), SlhDsaSecurityLevel::Shake128s)?;
let is_valid = verify_pq_slh_dsa_unverified(
message,
&signature,
pk.as_slice(),
SlhDsaSecurityLevel::Shake128s,
)?;
assert!(is_valid);
Ok(())
}
#[test]
fn test_fn_dsa_keypair_functionality_signs_and_verifies_succeeds() -> Result<()> {
use crate::unified_api::convenience::pq_sig::{
sign_pq_fn_dsa_unverified, verify_pq_fn_dsa_unverified,
};
let (pk, sk) = generate_fn_dsa_keypair()?;
let message = b"Test FN-DSA";
let signature =
sign_pq_fn_dsa_unverified(message, sk.as_ref(), FnDsaSecurityLevel::Level512)?;
let is_valid = verify_pq_fn_dsa_unverified(
message,
&signature,
pk.as_slice(),
FnDsaSecurityLevel::Level512,
)?;
assert!(is_valid);
Ok(())
}
#[test]
fn test_fn_dsa_with_config_succeeds() -> Result<()> {
use crate::unified_api::convenience::pq_sig::{
sign_pq_fn_dsa_unverified, verify_pq_fn_dsa_unverified,
};
let config = CoreConfig::default();
let (pk, sk) = generate_fn_dsa_keypair_with_config(&config)?;
let message = b"Config test";
let signature =
sign_pq_fn_dsa_unverified(message, sk.as_ref(), FnDsaSecurityLevel::Level512)?;
let is_valid = verify_pq_fn_dsa_unverified(
message,
&signature,
pk.as_slice(),
FnDsaSecurityLevel::Level512,
)?;
assert!(is_valid);
Ok(())
}
#[test]
fn test_validate_ed25519_keypair_success_succeeds() -> Result<()> {
let keypair = Ed25519KeyPair::generate().map_err(|e| CoreError::KeyGenerationFailed {
reason: format!("test keypair generation failed: {e}"),
recovery: String::new(),
})?;
assert!(validate_ed25519_keypair(&keypair).is_ok());
Ok(())
}
#[test]
fn test_generate_keypair_produces_valid_ed25519_succeeds() -> Result<()> {
let (pk, sk) = generate_keypair()?;
assert_eq!(pk.len(), 32, "Ed25519 public key should be 32 bytes");
assert_eq!(sk.as_ref().len(), 32, "Ed25519 secret key should be 32 bytes");
assert!(!pk.as_slice().iter().all(|&b| b == 0), "Public key should not be all zeros");
assert!(!sk.as_ref().iter().all(|&b| b == 0), "Secret key should not be all zeros");
Ok(())
}
#[test]
fn test_generate_keypair_with_config_validates_succeeds() -> Result<()> {
let config = CoreConfig::default();
let (pk, sk) = generate_keypair_with_config(&config)?;
assert_eq!(pk.len(), 32);
assert_eq!(sk.as_ref().len(), 32);
Ok(())
}
#[test]
fn test_ml_kem_keypair_768_with_config_succeeds() -> Result<()> {
let config = CoreConfig::default();
let (pk, sk) = generate_ml_kem_keypair_with_config(MlKemSecurityLevel::MlKem768, &config)?;
assert!(!pk.is_empty(), "ML-KEM-768 public key should not be empty");
assert!(!sk.as_ref().is_empty(), "ML-KEM-768 secret key should not be empty");
Ok(())
}
#[test]
fn test_ml_dsa_keypair_44_produces_non_empty_keys_fails() -> Result<()> {
let (pk, sk) = generate_ml_dsa_keypair(MlDsaParameterSet::MlDsa44)?;
assert!(!pk.is_empty());
assert!(!sk.as_ref().is_empty());
Ok(())
}
#[test]
fn test_ml_dsa_keypair_87_produces_non_empty_keys_fails() -> Result<()> {
let (pk, sk) = generate_ml_dsa_keypair(MlDsaParameterSet::MlDsa87)?;
assert!(!pk.is_empty());
assert!(!sk.as_ref().is_empty());
Ok(())
}
#[test]
fn test_ml_dsa_keypair_with_config_44_succeeds() -> Result<()> {
let config = CoreConfig::default();
let (pk, sk) = generate_ml_dsa_keypair_with_config(MlDsaParameterSet::MlDsa44, &config)?;
assert!(!pk.is_empty());
assert!(!sk.as_ref().is_empty());
Ok(())
}
#[test]
fn test_slh_dsa_keypair_192s_produces_non_empty_keys_fails() -> Result<()> {
let (pk, sk) = generate_slh_dsa_keypair(SlhDsaSecurityLevel::Shake192s)?;
assert!(!pk.is_empty());
assert!(!sk.as_ref().is_empty());
Ok(())
}
#[test]
fn test_slh_dsa_keypair_256s_produces_non_empty_keys_fails() -> Result<()> {
let (pk, sk) = generate_slh_dsa_keypair(SlhDsaSecurityLevel::Shake256s)?;
assert!(!pk.is_empty());
assert!(!sk.as_ref().is_empty());
Ok(())
}
}