use crate::error::{Error, Result};
use base64::engine::general_purpose::STANDARD as BASE64;
use base64::Engine;
use fips204::ml_dsa_87;
use fips204::traits::{SerDes, Signer, Verifier};
use serde::{Deserialize, Serialize};
pub const MLDSA87_PUBLIC_KEY_LEN: usize = ml_dsa_87::PK_LEN;
pub const MLDSA87_SECRET_KEY_LEN: usize = ml_dsa_87::SK_LEN;
pub const MLDSA87_SIGNATURE_LEN: usize = ml_dsa_87::SIG_LEN;
pub const ALGORITHM_UNSPECIFIED: u32 = 0;
pub const ALGORITHM_DILITHIUM5: u32 = 1;
pub const ALGORITHM_MLKEM1024: u32 = 2;
pub const HYBRID_SIG_TYPE_URL: &str = "/qorechain.pqc.v1.PQCHybridSignature";
pub fn algorithm_name(algorithm_id: u32) -> String {
match algorithm_id {
ALGORITHM_UNSPECIFIED => "unspecified".into(),
ALGORITHM_DILITHIUM5 => "dilithium5".into(),
ALGORITHM_MLKEM1024 => "mlkem1024".into(),
other => format!("algorithm_{other}"),
}
}
pub fn is_signature_algorithm(algorithm_id: u32) -> bool {
algorithm_id == ALGORITHM_DILITHIUM5
}
#[derive(Debug, Clone)]
pub struct PqcKeypair {
pub public_key: Vec<u8>,
pub secret_key: Vec<u8>,
}
pub fn generate_pqc_keypair() -> Result<PqcKeypair> {
let (pk, sk) =
ml_dsa_87::try_keygen().map_err(|e| Error::Pqc(format!("keygen failed: {e}")))?;
Ok(PqcKeypair {
public_key: pk.into_bytes().to_vec(),
secret_key: sk.into_bytes().to_vec(),
})
}
pub fn pqc_sign(secret_key: &[u8], message: &[u8]) -> Result<Vec<u8>> {
let bytes: [u8; MLDSA87_SECRET_KEY_LEN] = secret_key.try_into().map_err(|_| {
Error::Pqc(format!(
"invalid PQC secret key length: {}",
secret_key.len()
))
})?;
let sk = ml_dsa_87::PrivateKey::try_from_bytes(bytes)
.map_err(|e| Error::Pqc(format!("invalid PQC secret key: {e}")))?;
let sig = sk
.try_sign(message, &[])
.map_err(|e| Error::Pqc(format!("signing failed: {e}")))?;
Ok(sig.to_vec())
}
pub fn pqc_verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> bool {
let pk_bytes: [u8; MLDSA87_PUBLIC_KEY_LEN] = match public_key.try_into() {
Ok(b) => b,
Err(_) => return false,
};
let sig_bytes: [u8; MLDSA87_SIGNATURE_LEN] = match signature.try_into() {
Ok(b) => b,
Err(_) => return false,
};
let pk = match ml_dsa_87::PublicKey::try_from_bytes(pk_bytes) {
Ok(pk) => pk,
Err(_) => return false,
};
pk.verify(message, &sig_bytes, &[])
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HybridSignatureExtension {
pub algorithm_id: u32,
pub pqc_signature: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub pqc_public_key: Option<String>,
}
pub fn build_hybrid_signature_extension(
algorithm_id: u32,
signature: &[u8],
public_key: Option<&[u8]>,
) -> Result<HybridSignatureExtension> {
if !is_signature_algorithm(algorithm_id) {
return Err(Error::Pqc(format!(
"algorithm {} is not a PQC signature algorithm",
algorithm_name(algorithm_id)
)));
}
if signature.is_empty() {
return Err(Error::Pqc("PQC signature cannot be empty".into()));
}
if algorithm_id == ALGORITHM_DILITHIUM5 {
if signature.len() != MLDSA87_SIGNATURE_LEN {
return Err(Error::Pqc(format!(
"dilithium5 signature must be {MLDSA87_SIGNATURE_LEN} bytes, got {}",
signature.len()
)));
}
if let Some(pk) = public_key {
if pk.len() != MLDSA87_PUBLIC_KEY_LEN {
return Err(Error::Pqc(format!(
"dilithium5 public key must be {MLDSA87_PUBLIC_KEY_LEN} bytes, got {}",
pk.len()
)));
}
}
}
Ok(HybridSignatureExtension {
algorithm_id,
pqc_signature: BASE64.encode(signature),
pqc_public_key: public_key.map(|pk| BASE64.encode(pk)),
})
}