jacs 0.6.1

JACS JSON AI Communication Standard
Documentation
//! ML-DSA (FIPS-204) signature implementation for post-quantum security
//! Uses ML-DSA-87 (security level 5)

use super::constants::{
    ML_DSA_87_PRIVATE_KEY_SIZE, ML_DSA_87_PUBLIC_KEY_SIZE, ML_DSA_87_SIGNATURE_SIZE,
};
use base64::{Engine as _, engine::general_purpose::STANDARD as B64};
use fips204::ml_dsa_87;
use fips204::traits::{KeyGen, SerDes, Signer, Verifier};
use std::error::Error;
use tracing::{debug, trace, warn};

/// Generate ML-DSA-87 keypair
/// Returns (private_key_bytes, public_key_bytes)
#[must_use = "generated keys must be stored securely"]
pub fn generate_keys() -> Result<(Vec<u8>, Vec<u8>), Box<dyn std::error::Error>> {
    let (pk, sk) = ml_dsa_87::KG::try_keygen()?;
    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))
}

/// Sign string data with ML-DSA-87 private key
#[must_use = "signature must be stored or transmitted"]
pub fn sign_string(secret_key: Vec<u8>, data: &String) -> Result<String, Box<dyn Error>> {
    trace!(data_len = data.len(), "ML-DSA-87 signing starting");
    // Convert Vec<u8> to fixed-size array
    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)?;
    let sig = sk.try_sign(data.as_bytes(), b"")?; // empty context - returns [u8; 4627]
    let encoded = B64.encode(sig);
    trace!(signature_len = encoded.len(), "ML-DSA-87 signing completed");
    Ok(encoded)
}

/// Verify ML-DSA-87 signature
#[must_use = "signature verification result must be checked"]
pub fn verify_string(
    public_key: Vec<u8>,
    data: &str,
    signature_base64: &str,
) -> Result<(), Box<dyn Error>> {
    trace!(
        data_len = data.len(),
        public_key_len = public_key.len(),
        "ML-DSA-87 verification starting"
    );
    // Convert Vec<u8> to fixed-size array
    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)?;

    let sig_bytes = B64.decode(signature_base64)?;
    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()
            )
        })?;

    // verify() returns bool, not Result
    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())
    }
}