use super::constants::{
ML_DSA_87_PRIVATE_KEY_SIZE, ML_DSA_87_PUBLIC_KEY_SIZE, ML_DSA_87_SIGNATURE_SIZE,
};
use crate::error::JacsError;
use base64::{Engine as _, engine::general_purpose::STANDARD as B64};
use fips204::ml_dsa_87;
use fips204::traits::{KeyGen, SerDes, Signer, Verifier};
use tracing::{debug, trace, warn};
#[must_use = "generated keys must be stored securely"]
pub fn generate_keys() -> Result<(Vec<u8>, Vec<u8>), JacsError> {
let (pk, sk) = ml_dsa_87::KG::try_keygen()
.map_err(|e| JacsError::CryptoError(format!("ML-DSA-87 key generation failed: {}", e)))?;
let sk_bytes = sk.into_bytes().to_vec();
let pk_bytes = pk.into_bytes().to_vec();
trace!(
sk_len = sk_bytes.len(),
pk_len = pk_bytes.len(),
"ML-DSA-87 keypair generated"
);
Ok((sk_bytes, pk_bytes))
}
#[must_use = "signature must be stored or transmitted"]
pub fn sign_string(secret_key: Vec<u8>, data: &String) -> Result<String, JacsError> {
trace!(data_len = data.len(), "ML-DSA-87 signing starting");
let sk_array: [u8; ML_DSA_87_PRIVATE_KEY_SIZE] =
secret_key.try_into().map_err(|v: Vec<u8>| {
format!(
"Invalid private key length for ML-DSA-87: expected {} bytes, got {} bytes",
ML_DSA_87_PRIVATE_KEY_SIZE,
v.len()
)
})?;
let sk = ml_dsa_87::PrivateKey::try_from_bytes(sk_array).map_err(|e| {
JacsError::CryptoError(format!(
"ML-DSA-87 private key deserialization failed: {}",
e
))
})?;
let sig = sk
.try_sign(data.as_bytes(), b"")
.map_err(|e| JacsError::CryptoError(format!("ML-DSA-87 signing failed: {}", e)))?; let encoded = B64.encode(sig);
trace!(signature_len = encoded.len(), "ML-DSA-87 signing completed");
Ok(encoded)
}
#[must_use = "signature verification result must be checked"]
pub fn verify_string(
public_key: Vec<u8>,
data: &str,
signature_base64: &str,
) -> Result<(), JacsError> {
trace!(
data_len = data.len(),
public_key_len = public_key.len(),
"ML-DSA-87 verification starting"
);
let pk_array: [u8; ML_DSA_87_PUBLIC_KEY_SIZE] =
public_key.try_into().map_err(|v: Vec<u8>| {
format!(
"Invalid public key length for ML-DSA-87: expected {} bytes, got {} bytes",
ML_DSA_87_PUBLIC_KEY_SIZE,
v.len()
)
})?;
let pk = ml_dsa_87::PublicKey::try_from_bytes(pk_array).map_err(|e| {
JacsError::CryptoError(format!(
"ML-DSA-87 public key deserialization failed: {}",
e
))
})?;
let sig_bytes = B64
.decode(signature_base64)
.map_err(|e| JacsError::CryptoError(format!("Invalid base64 signature: {}", e)))?;
let sig_array: [u8; ML_DSA_87_SIGNATURE_SIZE] =
sig_bytes.try_into().map_err(|v: Vec<u8>| {
format!(
"Invalid signature length for ML-DSA-87: expected {} bytes, got {} bytes",
ML_DSA_87_SIGNATURE_SIZE,
v.len()
)
})?;
if pk.verify(data.as_bytes(), &sig_array, b"") {
debug!("ML-DSA-87 signature verification succeeded");
Ok(())
} else {
warn!("ML-DSA-87 signature verification failed");
Err("ML-DSA signature verification failed".into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sign_verify_roundtrip() {
let (private_key, public_key) = generate_keys().expect("key generation should succeed");
let data = "ml-dsa-87 test message".to_string();
let signature = sign_string(private_key, &data).expect("signing should succeed");
let result = verify_string(public_key, &data, &signature);
assert!(result.is_ok(), "valid signature should verify");
}
#[test]
fn test_verify_wrong_message_rejected() {
let (private_key, public_key) = generate_keys().expect("key generation should succeed");
let original_data = "original message".to_string();
let tampered_data = "tampered message";
let signature = sign_string(private_key, &original_data).expect("signing should succeed");
let result = verify_string(public_key, tampered_data, &signature);
assert!(result.is_err(), "tampered message should not verify");
}
#[test]
fn test_verify_malformed_base64_signature_rejected() {
let (_, public_key) = generate_keys().expect("key generation should succeed");
let data = "test data";
for invalid_sig in ["!!!not-base64!!!", "abc@#$%", "===="] {
let result = verify_string(public_key.clone(), data, invalid_sig);
assert!(result.is_err(), "malformed signature should be rejected");
}
}
#[test]
fn test_verify_wrong_signature_length_rejected() {
let (private_key, public_key) = generate_keys().expect("key generation should succeed");
let data = "test data".to_string();
let signature = sign_string(private_key, &data).expect("signing should succeed");
let mut sig_bytes = B64.decode(&signature).expect("decode should succeed");
sig_bytes.truncate(sig_bytes.len().saturating_sub(8));
let truncated = B64.encode(sig_bytes);
let result = verify_string(public_key, &data, &truncated);
assert!(
result.is_err(),
"signature with wrong length should be rejected"
);
}
#[test]
fn test_verify_invalid_public_key_length_rejected() {
let (private_key, _) = generate_keys().expect("key generation should succeed");
let data = "test data".to_string();
let signature = sign_string(private_key, &data).expect("signing should succeed");
let invalid_public_keys = vec![vec![], vec![0u8; 64], vec![0xFF; 1024]];
for invalid_key in invalid_public_keys {
let result = verify_string(invalid_key, &data, &signature);
assert!(result.is_err(), "invalid public key should be rejected");
}
}
#[test]
fn test_sign_invalid_private_key_length_rejected() {
let data = "test data".to_string();
for invalid_key in [vec![], vec![0u8; 64], vec![0xAA; 1024]] {
let result = sign_string(invalid_key, &data);
assert!(result.is_err(), "invalid private key should be rejected");
}
}
}