use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PqcAlgorithm {
#[serde(rename = "SLH-DSA-SHA2-128s")]
SlhDsaSha2_128s,
#[serde(rename = "SLH-DSA-SHA2-128f")]
SlhDsaSha2_128f,
#[serde(rename = "SLH-DSA-SHA2-192s")]
SlhDsaSha2_192s,
#[serde(rename = "SLH-DSA-SHA2-256s")]
SlhDsaSha2_256s,
}
impl PqcAlgorithm {
pub fn security_level(&self) -> u8 {
match self {
Self::SlhDsaSha2_128s | Self::SlhDsaSha2_128f => 1,
Self::SlhDsaSha2_192s => 3,
Self::SlhDsaSha2_256s => 5,
}
}
pub fn max_signature_size(&self) -> usize {
match self {
Self::SlhDsaSha2_128s => 7_856,
Self::SlhDsaSha2_128f => 17_088,
Self::SlhDsaSha2_192s => 16_224,
Self::SlhDsaSha2_256s => 29_792,
}
}
pub fn public_key_size(&self) -> usize {
match self {
Self::SlhDsaSha2_128s | Self::SlhDsaSha2_128f => 32,
Self::SlhDsaSha2_192s => 48,
Self::SlhDsaSha2_256s => 64,
}
}
pub fn secret_key_size(&self) -> usize {
match self {
Self::SlhDsaSha2_128s | Self::SlhDsaSha2_128f => 64,
Self::SlhDsaSha2_192s => 96,
Self::SlhDsaSha2_256s => 128,
}
}
pub fn algorithm_id(&self) -> &'static str {
match self {
Self::SlhDsaSha2_128s => "SLH-DSA-SHA2-128s",
Self::SlhDsaSha2_128f => "SLH-DSA-SHA2-128f",
Self::SlhDsaSha2_192s => "SLH-DSA-SHA2-192s",
Self::SlhDsaSha2_256s => "SLH-DSA-SHA2-256s",
}
}
pub fn is_small(&self) -> bool {
matches!(
self,
Self::SlhDsaSha2_128s | Self::SlhDsaSha2_192s | Self::SlhDsaSha2_256s
)
}
pub fn recommended_embedded() -> Self {
Self::SlhDsaSha2_128s
}
pub fn recommended_cloud() -> Self {
Self::SlhDsaSha2_128f
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HybridSignature {
pub classical: Vec<u8>,
pub post_quantum: Vec<u8>,
pub pqc_algorithm: PqcAlgorithm,
pub message_hash: Vec<u8>,
}
impl HybridSignature {
pub fn is_complete(&self) -> bool {
!self.classical.is_empty()
&& !self.post_quantum.is_empty()
&& self.classical.len() == 64 && self.post_quantum.len() <= self.pqc_algorithm.max_signature_size()
&& !self.message_hash.is_empty()
}
pub fn domain_separator() -> &'static [u8] {
b"wsc-hybrid-v1"
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PqcConfig {
pub algorithm: PqcAlgorithm,
#[serde(default = "default_hybrid")]
pub hybrid: bool,
#[serde(default = "default_pqc_domain")]
pub domain: String,
}
fn default_hybrid() -> bool {
true
}
fn default_pqc_domain() -> String {
"wsc-pqc-v1".to_string()
}
impl Default for PqcConfig {
fn default() -> Self {
Self {
algorithm: PqcAlgorithm::recommended_embedded(),
hybrid: true,
domain: default_pqc_domain(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PqcStatus {
ParametersOnly,
Experimental,
Production,
}
pub fn implementation_status() -> PqcStatus {
PqcStatus::ParametersOnly
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_algorithm_properties() {
let alg = PqcAlgorithm::SlhDsaSha2_128s;
assert_eq!(alg.security_level(), 1);
assert_eq!(alg.max_signature_size(), 7_856);
assert_eq!(alg.public_key_size(), 32);
assert_eq!(alg.secret_key_size(), 64);
assert_eq!(alg.algorithm_id(), "SLH-DSA-SHA2-128s");
assert!(alg.is_small());
}
#[test]
fn test_algorithm_sizes_consistent() {
for alg in [
PqcAlgorithm::SlhDsaSha2_128s,
PqcAlgorithm::SlhDsaSha2_128f,
PqcAlgorithm::SlhDsaSha2_192s,
PqcAlgorithm::SlhDsaSha2_256s,
] {
assert!(alg.max_signature_size() > 0);
assert!(alg.public_key_size() > 0);
assert!(alg.secret_key_size() > 0);
assert_eq!(alg.secret_key_size(), alg.public_key_size() * 2);
}
}
#[test]
fn test_security_levels() {
assert_eq!(PqcAlgorithm::SlhDsaSha2_128s.security_level(), 1);
assert_eq!(PqcAlgorithm::SlhDsaSha2_128f.security_level(), 1);
assert_eq!(PqcAlgorithm::SlhDsaSha2_192s.security_level(), 3);
assert_eq!(PqcAlgorithm::SlhDsaSha2_256s.security_level(), 5);
}
#[test]
fn test_recommendations() {
let embedded = PqcAlgorithm::recommended_embedded();
assert!(embedded.is_small());
assert!(embedded.max_signature_size() < 10_000);
let cloud = PqcAlgorithm::recommended_cloud();
assert!(!cloud.is_small()); }
#[test]
fn test_algorithm_serialization() {
let alg = PqcAlgorithm::SlhDsaSha2_128s;
let json = serde_json::to_string(&alg).unwrap();
assert_eq!(json, "\"SLH-DSA-SHA2-128s\"");
let parsed: PqcAlgorithm = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, alg);
}
#[test]
fn test_hybrid_signature_serialization() {
let sig = HybridSignature {
classical: vec![0u8; 64],
post_quantum: vec![0u8; 100],
pqc_algorithm: PqcAlgorithm::SlhDsaSha2_128s,
message_hash: vec![0u8; 32],
};
let json = serde_json::to_string_pretty(&sig).unwrap();
assert!(json.contains("pqcAlgorithm"));
assert!(json.contains("SLH-DSA-SHA2-128s"));
let parsed: HybridSignature = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.pqc_algorithm, PqcAlgorithm::SlhDsaSha2_128s);
}
#[test]
fn test_pqc_config_default() {
let config = PqcConfig::default();
assert!(config.hybrid);
assert_eq!(config.algorithm, PqcAlgorithm::SlhDsaSha2_128s);
assert_eq!(config.domain, "wsc-pqc-v1");
}
#[test]
fn test_implementation_status() {
assert_eq!(implementation_status(), PqcStatus::ParametersOnly);
}
#[test]
fn test_all_algorithm_ids_unique() {
let ids: Vec<&str> = [
PqcAlgorithm::SlhDsaSha2_128s,
PqcAlgorithm::SlhDsaSha2_128f,
PqcAlgorithm::SlhDsaSha2_192s,
PqcAlgorithm::SlhDsaSha2_256s,
]
.iter()
.map(|a| a.algorithm_id())
.collect();
let mut deduped = ids.clone();
deduped.sort();
deduped.dedup();
assert_eq!(ids.len(), deduped.len());
}
#[test]
fn test_hybrid_signature_is_complete() {
let complete = HybridSignature {
classical: vec![0u8; 64],
post_quantum: vec![0u8; 100],
pqc_algorithm: PqcAlgorithm::SlhDsaSha2_128s,
message_hash: vec![0u8; 32],
};
assert!(complete.is_complete());
}
#[test]
fn test_hybrid_signature_incomplete_missing_pqc() {
let missing_pqc = HybridSignature {
classical: vec![0u8; 64],
post_quantum: vec![],
pqc_algorithm: PqcAlgorithm::SlhDsaSha2_128s,
message_hash: vec![0u8; 32],
};
assert!(!missing_pqc.is_complete());
}
#[test]
fn test_hybrid_signature_incomplete_missing_classical() {
let missing_classical = HybridSignature {
classical: vec![],
post_quantum: vec![0u8; 100],
pqc_algorithm: PqcAlgorithm::SlhDsaSha2_128s,
message_hash: vec![0u8; 32],
};
assert!(!missing_classical.is_complete());
}
#[test]
fn test_hybrid_signature_incomplete_wrong_classical_size() {
let wrong_size = HybridSignature {
classical: vec![0u8; 32], post_quantum: vec![0u8; 100],
pqc_algorithm: PqcAlgorithm::SlhDsaSha2_128s,
message_hash: vec![0u8; 32],
};
assert!(!wrong_size.is_complete());
}
#[test]
fn test_hybrid_signature_incomplete_empty_hash() {
let no_hash = HybridSignature {
classical: vec![0u8; 64],
post_quantum: vec![0u8; 100],
pqc_algorithm: PqcAlgorithm::SlhDsaSha2_128s,
message_hash: vec![],
};
assert!(!no_hash.is_complete());
}
#[test]
fn test_hybrid_domain_separator() {
assert_eq!(HybridSignature::domain_separator(), b"wsc-hybrid-v1");
assert!(!HybridSignature::domain_separator().is_empty());
}
}