From 7716e96895c89f879b9ac2e45227a977a78b7746 Mon Sep 17 00:00:00 2001 From: thanos Date: Fri, 9 Jan 2026 21:15:38 +0200 Subject: [PATCH] Issues Closed - closed #226 the fast (f) variants - closed #227 SHA-2 variants. - closed #228 SLH-DSA variants - closed #229 fast SHAKE-256 variants --- COMPARISON.md | 4 +- POST_QUANTUM.md | 20 +- PQC_FEATURE_FLAG.md | 4 +- README.md | 10 +- libcheck_sphincs.rlib | Bin 0 -> 5216 bytes src/keys.rs | 743 +++++++++++++++++++++++++++++ src/lib.rs | 6 +- src/pkix.rs | 36 +- tests/pqc_test.rs | 107 ++++- tests/test_sphincs_availability.rs | 18 + 10 files changed, 935 insertions(+), 13 deletions(-) create mode 100644 libcheck_sphincs.rlib create mode 100644 tests/test_sphincs_availability.rs diff --git a/COMPARISON.md b/COMPARISON.md index 66e53e3..ae04a41 100644 --- a/COMPARISON.md +++ b/COMPARISON.md @@ -39,7 +39,7 @@ This document provides a comprehensive comparison between the Rust `rust-bottle` | **ML-DSA-44** | Yes | Yes | Implemented in rust-bottle | | **ML-DSA-65** | Yes | Yes | Implemented in rust-bottle | | **ML-DSA-87** | Yes | Yes | Implemented in rust-bottle | -| **SLH-DSA (12 variants)** | Yes (3 variants) | Yes | rust-bottle implements 3 SLH-DSA variants (128s, 192s, 256s) | +| **SLH-DSA (12 variants)** | Yes (12 variants) | Yes | rust-bottle implements all 12 SLH-DSA variants (SHAKE-256 and SHA-2, s and f variants) | **Analysis**: Both libraries have comprehensive post-quantum cryptography support. rust-bottle implements ML-KEM (encryption), ML-DSA (signatures), and SLH-DSA (hash-based signatures) via optional feature flags. @@ -147,7 +147,7 @@ Both libraries have comprehensive PQC support: **SLH-DSA (Hash-Based Signatures)**: - gobottle: 12 variants (SHA2/SHAKE, 128/192/256, s/f variants) -- rust-bottle: 3 variants (128s, 192s, 256s - SHAKE-256 robust) +- rust-bottle: 12 variants (SHA2/SHAKE, 128/192/256, s/f variants) - Full FIPS 205 compliance - Stateless hash-based signatures - Both support **Implementation Differences**: diff --git a/POST_QUANTUM.md b/POST_QUANTUM.md index 900f34b..a535755 100644 --- a/POST_QUANTUM.md +++ b/POST_QUANTUM.md @@ -64,17 +64,33 @@ ML-DSA provides post-quantum digital signatures based on lattice cryptography. T ### SLH-DSA (Hash-Based Signatures) -SLH-DSA provides post-quantum signatures based on hash functions. Three security levels are available: +SLH-DSA provides post-quantum signatures based on hash functions. Twelve variants are available, matching the FIPS 205 standard: +**SHAKE-256 Variants:** | Variant | Security Level | Public Key Size | Secret Key Size | Signature Size | Implementation | |---------|---------------|-----------------|-----------------|----------------|----------------| | SLH-DSA-128s | 128-bit | 32 bytes | 64 bytes | ~7856 bytes | sphincsshake256128srobust | +| SLH-DSA-128f | 128-bit | 32 bytes | 64 bytes | ~17088 bytes | sphincsshake256128frobust | | SLH-DSA-192s | 192-bit | 48 bytes | 96 bytes | ~16224 bytes | sphincsshake256192srobust | +| SLH-DSA-192f | 192-bit | 48 bytes | 96 bytes | ~35664 bytes | sphincsshake256192frobust | | SLH-DSA-256s | 256-bit | 64 bytes | 128 bytes | ~29792 bytes | sphincsshake256256srobust | +| SLH-DSA-256f | 256-bit | 64 bytes | 128 bytes | ~49856 bytes | sphincsshake256256frobust | + +**SHA-2 Variants:** +| Variant | Security Level | Public Key Size | Secret Key Size | Signature Size | Implementation | +|---------|---------------|-----------------|-----------------|----------------|----------------| +| SLH-DSA-SHA2-128s | 128-bit | 32 bytes | 64 bytes | ~7856 bytes | sphincssha256128srobust | +| SLH-DSA-SHA2-128f | 128-bit | 32 bytes | 64 bytes | ~17088 bytes | sphincssha256128frobust | +| SLH-DSA-SHA2-192s | 192-bit | 48 bytes | 96 bytes | ~16224 bytes | sphincssha256192srobust | +| SLH-DSA-SHA2-192f | 192-bit | 48 bytes | 96 bytes | ~35664 bytes | sphincssha256192frobust | +| SLH-DSA-SHA2-256s | 256-bit | 64 bytes | 128 bytes | ~29792 bytes | sphincssha256256srobust | +| SLH-DSA-SHA2-256f | 256-bit | 64 bytes | 128 bytes | ~49856 bytes | sphincssha256256frobust | **Implementation Details:** - Uses `pqcrypto-sphincsplus` v0.5.3 -- Uses "robust" variants with SHAKE-256 +- Uses "robust" variants with SHAKE-256 or SHA-2 +- "s" variants: smaller signatures, slower signing +- "f" variants: faster signing, larger signatures - Very large signatures but simple hash-based security model - Works on all platforms including macOS/ARM diff --git a/PQC_FEATURE_FLAG.md b/PQC_FEATURE_FLAG.md index 72ac31a..deef4f1 100644 --- a/PQC_FEATURE_FLAG.md +++ b/PQC_FEATURE_FLAG.md @@ -46,7 +46,9 @@ cargo build --features post-quantum,ml-kem When the `post-quantum` feature is enabled: - **ML-DSA-44**, **ML-DSA-65**, **ML-DSA-87** signatures -- **SLH-DSA-128s**, **SLH-DSA-192s**, **SLH-DSA-256s** signatures +- **SLH-DSA-128s/128f**, **SLH-DSA-192s/192f**, **SLH-DSA-256s/256f** signatures (SHAKE-256) +- **SLH-DSA-SHA2-128s/128f**, **SLH-DSA-SHA2-192s/192f**, **SLH-DSA-SHA2-256s/256f** signatures (SHA-2) +- Total: 12 SLH-DSA variants matching FIPS 205 standard When the `ml-kem` feature is enabled (requires `post-quantum` for full functionality): - **ML-KEM-768** and **ML-KEM-1024** encryption diff --git a/README.md b/README.md index c2075a6..4a70a05 100644 --- a/README.md +++ b/README.md @@ -534,9 +534,13 @@ Comprehensive post-quantum cryptography support is available via feature flags. - **ML-DSA-44**: Post-quantum signatures (128-bit security) - Uses dilithium2 - **ML-DSA-65**: Post-quantum signatures (192-bit security) - Uses dilithium3 - **ML-DSA-87**: Post-quantum signatures (256-bit security) - Uses dilithium5 -- **SLH-DSA-128s**: Hash-based signatures (128-bit security) - Uses sphincsshake256128srobust -- **SLH-DSA-192s**: Hash-based signatures (192-bit security) - Uses sphincsshake256192srobust -- **SLH-DSA-256s**: Hash-based signatures (256-bit security) - Uses sphincsshake256256srobust +- **SLH-DSA-128s/128f**: Hash-based signatures (128-bit security) - SHAKE-256 variants +- **SLH-DSA-192s/192f**: Hash-based signatures (192-bit security) - SHAKE-256 variants +- **SLH-DSA-256s/256f**: Hash-based signatures (256-bit security) - SHAKE-256 variants +- **SLH-DSA-SHA2-128s/128f**: Hash-based signatures (128-bit security) - SHA-2 variants +- **SLH-DSA-SHA2-192s/192f**: Hash-based signatures (192-bit security) - SHA-2 variants +- **SLH-DSA-SHA2-256s/256f**: Hash-based signatures (256-bit security) - SHA-2 variants +- Total: 12 SLH-DSA variants matching FIPS 205 standard **Hybrid Encryption (requires `ml-kem` feature):** - **ML-KEM-768 + X25519**: Combines post-quantum and classical security diff --git a/libcheck_sphincs.rlib b/libcheck_sphincs.rlib new file mode 100644 index 0000000000000000000000000000000000000000..6111f2bec783f6aa961033b9120337dc3de9d50d GIT binary patch literal 5216 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 W8ii&^Y3t!bg^stTcngm$l>Y(%1Q2ro literal 0 HcmV?d00001 diff --git a/src/keys.rs b/src/keys.rs index 5285063..316815d 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1601,3 +1601,746 @@ impl SignerKey for SlhDsa256sKey { self.public_key_bytes() } } + +#[cfg(feature = "post-quantum")] +/// SLH-DSA-128f key pair for post-quantum hash-based signatures. +/// +/// SLH-DSA-128f provides 128-bit security with faster signing but larger signatures +/// compared to the "s" (small) variant. +pub struct SlhDsa128fKey { + public_key: pqcrypto_sphincsplus::sphincsshake256128frobust::PublicKey, + secret_key: pqcrypto_sphincsplus::sphincsshake256128frobust::SecretKey, +} + +#[cfg(feature = "post-quantum")] +impl SlhDsa128fKey { + /// Generate a new SLH-DSA-128f key pair. + pub fn generate(_rng: &mut R) -> Self { + let (public_key, secret_key) = pqcrypto_sphincsplus::sphincsshake256128frobust::keypair(); + Self { + public_key, + secret_key, + } + } + + /// Get the public key bytes. + pub fn public_key_bytes(&self) -> Vec { + ::as_bytes( + &self.public_key, + ) + .to_vec() + } + + /// Get the private key bytes. + pub fn private_key_bytes(&self) -> Vec { + ::as_bytes( + &self.secret_key, + ) + .to_vec() + } + + /// Create from private key bytes. + pub fn from_private_key_bytes(bytes: &[u8]) -> Result { + let _secret_key = ::from_bytes(bytes) + .map_err(|_| BottleError::InvalidKeyType)?; + // Cannot derive public key from secret key in this API + Err(BottleError::InvalidKeyType) + } +} + +#[cfg(feature = "post-quantum")] +impl Sign for SlhDsa128fKey { + fn sign(&self, _rng: &mut dyn RngCore, message: &[u8]) -> Result> { + let detached_sig = pqcrypto_sphincsplus::sphincsshake256128frobust::detached_sign( + message, + &self.secret_key, + ); + Ok(::as_bytes(&detached_sig).to_vec()) + } +} + +#[cfg(feature = "post-quantum")] +impl Verify for SlhDsa128fKey { + fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> { + let detached_sig = ::from_bytes(signature) + .map_err(|_| BottleError::VerifyFailed)?; + pqcrypto_sphincsplus::sphincsshake256128frobust::verify_detached_signature( + &detached_sig, + message, + &self.public_key, + ) + .map_err(|_| BottleError::VerifyFailed)?; + Ok(()) + } +} + +#[cfg(feature = "post-quantum")] +impl SignerKey for SlhDsa128fKey { + fn fingerprint(&self) -> Vec { + crate::hash::sha256(&self.public_key_bytes()) + } + + fn public_key(&self) -> Vec { + self.public_key_bytes() + } +} + +#[cfg(feature = "post-quantum")] +/// SLH-DSA-192f key pair for post-quantum hash-based signatures. +/// +/// SLH-DSA-192f provides 192-bit security with faster signing but larger signatures. +pub struct SlhDsa192fKey { + public_key: pqcrypto_sphincsplus::sphincsshake256192frobust::PublicKey, + secret_key: pqcrypto_sphincsplus::sphincsshake256192frobust::SecretKey, +} + +#[cfg(feature = "post-quantum")] +impl SlhDsa192fKey { + /// Generate a new SLH-DSA-192f key pair. + pub fn generate(_rng: &mut R) -> Self { + let (public_key, secret_key) = pqcrypto_sphincsplus::sphincsshake256192frobust::keypair(); + Self { + public_key, + secret_key, + } + } + + /// Get the public key bytes. + pub fn public_key_bytes(&self) -> Vec { + ::as_bytes( + &self.public_key, + ) + .to_vec() + } + + /// Get the private key bytes. + pub fn private_key_bytes(&self) -> Vec { + ::as_bytes( + &self.secret_key, + ) + .to_vec() + } + + /// Create from private key bytes. + pub fn from_private_key_bytes(bytes: &[u8]) -> Result { + let _secret_key = ::from_bytes(bytes) + .map_err(|_| BottleError::InvalidKeyType)?; + // Cannot derive public key from secret key in this API + Err(BottleError::InvalidKeyType) + } +} + +#[cfg(feature = "post-quantum")] +impl Sign for SlhDsa192fKey { + fn sign(&self, _rng: &mut dyn RngCore, message: &[u8]) -> Result> { + let detached_sig = pqcrypto_sphincsplus::sphincsshake256192frobust::detached_sign( + message, + &self.secret_key, + ); + Ok(::as_bytes(&detached_sig).to_vec()) + } +} + +#[cfg(feature = "post-quantum")] +impl Verify for SlhDsa192fKey { + fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> { + let detached_sig = ::from_bytes(signature) + .map_err(|_| BottleError::VerifyFailed)?; + pqcrypto_sphincsplus::sphincsshake256192frobust::verify_detached_signature( + &detached_sig, + message, + &self.public_key, + ) + .map_err(|_| BottleError::VerifyFailed)?; + Ok(()) + } +} + +#[cfg(feature = "post-quantum")] +impl SignerKey for SlhDsa192fKey { + fn fingerprint(&self) -> Vec { + crate::hash::sha256(&self.public_key_bytes()) + } + + fn public_key(&self) -> Vec { + self.public_key_bytes() + } +} + +#[cfg(feature = "post-quantum")] +/// SLH-DSA-256f key pair for post-quantum hash-based signatures. +/// +/// SLH-DSA-256f provides 256-bit security with faster signing but larger signatures. +pub struct SlhDsa256fKey { + public_key: pqcrypto_sphincsplus::sphincsshake256256frobust::PublicKey, + secret_key: pqcrypto_sphincsplus::sphincsshake256256frobust::SecretKey, +} + +#[cfg(feature = "post-quantum")] +impl SlhDsa256fKey { + /// Generate a new SLH-DSA-256f key pair. + pub fn generate(_rng: &mut R) -> Self { + let (public_key, secret_key) = pqcrypto_sphincsplus::sphincsshake256256frobust::keypair(); + Self { + public_key, + secret_key, + } + } + + /// Get the public key bytes. + pub fn public_key_bytes(&self) -> Vec { + ::as_bytes( + &self.public_key, + ) + .to_vec() + } + + /// Get the private key bytes. + pub fn private_key_bytes(&self) -> Vec { + ::as_bytes( + &self.secret_key, + ) + .to_vec() + } + + /// Create from private key bytes. + pub fn from_private_key_bytes(bytes: &[u8]) -> Result { + let _secret_key = ::from_bytes(bytes) + .map_err(|_| BottleError::InvalidKeyType)?; + // Cannot derive public key from secret key in this API + Err(BottleError::InvalidKeyType) + } +} + +#[cfg(feature = "post-quantum")] +impl Sign for SlhDsa256fKey { + fn sign(&self, _rng: &mut dyn RngCore, message: &[u8]) -> Result> { + let detached_sig = pqcrypto_sphincsplus::sphincsshake256256frobust::detached_sign( + message, + &self.secret_key, + ); + Ok(::as_bytes(&detached_sig).to_vec()) + } +} + +#[cfg(feature = "post-quantum")] +impl Verify for SlhDsa256fKey { + fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> { + let detached_sig = ::from_bytes(signature) + .map_err(|_| BottleError::VerifyFailed)?; + pqcrypto_sphincsplus::sphincsshake256256frobust::verify_detached_signature( + &detached_sig, + message, + &self.public_key, + ) + .map_err(|_| BottleError::VerifyFailed)?; + Ok(()) + } +} + +#[cfg(feature = "post-quantum")] +impl SignerKey for SlhDsa256fKey { + fn fingerprint(&self) -> Vec { + crate::hash::sha256(&self.public_key_bytes()) + } + + fn public_key(&self) -> Vec { + self.public_key_bytes() + } +} + +// SHA-2 based SLH-DSA variants + +#[cfg(feature = "post-quantum")] +/// SLH-DSA-SHA2-128s key pair for post-quantum hash-based signatures. +/// +/// SLH-DSA-SHA2-128s provides 128-bit security using SHA-2 hash function +/// with smaller signatures but slower signing compared to the "f" variant. +pub struct SlhDsaSha2_128sKey { + public_key: pqcrypto_sphincsplus::sphincssha256128srobust::PublicKey, + secret_key: pqcrypto_sphincsplus::sphincssha256128srobust::SecretKey, +} + +#[cfg(feature = "post-quantum")] +impl SlhDsaSha2_128sKey { + /// Generate a new SLH-DSA-SHA2-128s key pair. + pub fn generate(_rng: &mut R) -> Self { + let (public_key, secret_key) = pqcrypto_sphincsplus::sphincssha256128srobust::keypair(); + Self { + public_key, + secret_key, + } + } + + /// Get the public key bytes. + pub fn public_key_bytes(&self) -> Vec { + ::as_bytes( + &self.public_key, + ) + .to_vec() + } + + /// Get the private key bytes. + pub fn private_key_bytes(&self) -> Vec { + ::as_bytes( + &self.secret_key, + ) + .to_vec() + } + + /// Create from private key bytes. + pub fn from_private_key_bytes(bytes: &[u8]) -> Result { + let _secret_key = ::from_bytes(bytes) + .map_err(|_| BottleError::InvalidKeyType)?; + // Cannot derive public key from secret key in this API + Err(BottleError::InvalidKeyType) + } +} + +#[cfg(feature = "post-quantum")] +impl Sign for SlhDsaSha2_128sKey { + fn sign(&self, _rng: &mut dyn RngCore, message: &[u8]) -> Result> { + let detached_sig = pqcrypto_sphincsplus::sphincssha256128srobust::detached_sign( + message, + &self.secret_key, + ); + Ok(::as_bytes(&detached_sig).to_vec()) + } +} + +#[cfg(feature = "post-quantum")] +impl Verify for SlhDsaSha2_128sKey { + fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> { + let detached_sig = ::from_bytes(signature) + .map_err(|_| BottleError::VerifyFailed)?; + pqcrypto_sphincsplus::sphincssha256128srobust::verify_detached_signature( + &detached_sig, + message, + &self.public_key, + ) + .map_err(|_| BottleError::VerifyFailed)?; + Ok(()) + } +} + +#[cfg(feature = "post-quantum")] +impl SignerKey for SlhDsaSha2_128sKey { + fn fingerprint(&self) -> Vec { + crate::hash::sha256(&self.public_key_bytes()) + } + + fn public_key(&self) -> Vec { + self.public_key_bytes() + } +} + +#[cfg(feature = "post-quantum")] +/// SLH-DSA-SHA2-128f key pair for post-quantum hash-based signatures. +/// +/// SLH-DSA-SHA2-128f provides 128-bit security using SHA-2 hash function +/// with faster signing but larger signatures. +pub struct SlhDsaSha2_128fKey { + public_key: pqcrypto_sphincsplus::sphincssha256128frobust::PublicKey, + secret_key: pqcrypto_sphincsplus::sphincssha256128frobust::SecretKey, +} + +#[cfg(feature = "post-quantum")] +impl SlhDsaSha2_128fKey { + /// Generate a new SLH-DSA-SHA2-128f key pair. + pub fn generate(_rng: &mut R) -> Self { + let (public_key, secret_key) = pqcrypto_sphincsplus::sphincssha256128frobust::keypair(); + Self { + public_key, + secret_key, + } + } + + /// Get the public key bytes. + pub fn public_key_bytes(&self) -> Vec { + ::as_bytes( + &self.public_key, + ) + .to_vec() + } + + /// Get the private key bytes. + pub fn private_key_bytes(&self) -> Vec { + ::as_bytes( + &self.secret_key, + ) + .to_vec() + } + + /// Create from private key bytes. + pub fn from_private_key_bytes(bytes: &[u8]) -> Result { + let _secret_key = ::from_bytes(bytes) + .map_err(|_| BottleError::InvalidKeyType)?; + // Cannot derive public key from secret key in this API + Err(BottleError::InvalidKeyType) + } +} + +#[cfg(feature = "post-quantum")] +impl Sign for SlhDsaSha2_128fKey { + fn sign(&self, _rng: &mut dyn RngCore, message: &[u8]) -> Result> { + let detached_sig = pqcrypto_sphincsplus::sphincssha256128frobust::detached_sign( + message, + &self.secret_key, + ); + Ok(::as_bytes(&detached_sig).to_vec()) + } +} + +#[cfg(feature = "post-quantum")] +impl Verify for SlhDsaSha2_128fKey { + fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> { + let detached_sig = ::from_bytes(signature) + .map_err(|_| BottleError::VerifyFailed)?; + pqcrypto_sphincsplus::sphincssha256128frobust::verify_detached_signature( + &detached_sig, + message, + &self.public_key, + ) + .map_err(|_| BottleError::VerifyFailed)?; + Ok(()) + } +} + +#[cfg(feature = "post-quantum")] +impl SignerKey for SlhDsaSha2_128fKey { + fn fingerprint(&self) -> Vec { + crate::hash::sha256(&self.public_key_bytes()) + } + + fn public_key(&self) -> Vec { + self.public_key_bytes() + } +} + +#[cfg(feature = "post-quantum")] +/// SLH-DSA-SHA2-192s key pair for post-quantum hash-based signatures. +/// +/// SLH-DSA-SHA2-192s provides 192-bit security using SHA-2 hash function. +pub struct SlhDsaSha2_192sKey { + public_key: pqcrypto_sphincsplus::sphincssha256192srobust::PublicKey, + secret_key: pqcrypto_sphincsplus::sphincssha256192srobust::SecretKey, +} + +#[cfg(feature = "post-quantum")] +impl SlhDsaSha2_192sKey { + /// Generate a new SLH-DSA-SHA2-192s key pair. + pub fn generate(_rng: &mut R) -> Self { + let (public_key, secret_key) = pqcrypto_sphincsplus::sphincssha256192srobust::keypair(); + Self { + public_key, + secret_key, + } + } + + /// Get the public key bytes. + pub fn public_key_bytes(&self) -> Vec { + ::as_bytes( + &self.public_key, + ) + .to_vec() + } + + /// Get the private key bytes. + pub fn private_key_bytes(&self) -> Vec { + ::as_bytes( + &self.secret_key, + ) + .to_vec() + } + + /// Create from private key bytes. + pub fn from_private_key_bytes(bytes: &[u8]) -> Result { + let _secret_key = ::from_bytes(bytes) + .map_err(|_| BottleError::InvalidKeyType)?; + // Cannot derive public key from secret key in this API + Err(BottleError::InvalidKeyType) + } +} + +#[cfg(feature = "post-quantum")] +impl Sign for SlhDsaSha2_192sKey { + fn sign(&self, _rng: &mut dyn RngCore, message: &[u8]) -> Result> { + let detached_sig = pqcrypto_sphincsplus::sphincssha256192srobust::detached_sign( + message, + &self.secret_key, + ); + Ok(::as_bytes(&detached_sig).to_vec()) + } +} + +#[cfg(feature = "post-quantum")] +impl Verify for SlhDsaSha2_192sKey { + fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> { + let detached_sig = ::from_bytes(signature) + .map_err(|_| BottleError::VerifyFailed)?; + pqcrypto_sphincsplus::sphincssha256192srobust::verify_detached_signature( + &detached_sig, + message, + &self.public_key, + ) + .map_err(|_| BottleError::VerifyFailed)?; + Ok(()) + } +} + +#[cfg(feature = "post-quantum")] +impl SignerKey for SlhDsaSha2_192sKey { + fn fingerprint(&self) -> Vec { + crate::hash::sha256(&self.public_key_bytes()) + } + + fn public_key(&self) -> Vec { + self.public_key_bytes() + } +} + +#[cfg(feature = "post-quantum")] +/// SLH-DSA-SHA2-192f key pair for post-quantum hash-based signatures. +/// +/// SLH-DSA-SHA2-192f provides 192-bit security using SHA-2 hash function. +pub struct SlhDsaSha2_192fKey { + public_key: pqcrypto_sphincsplus::sphincssha256192frobust::PublicKey, + secret_key: pqcrypto_sphincsplus::sphincssha256192frobust::SecretKey, +} + +#[cfg(feature = "post-quantum")] +impl SlhDsaSha2_192fKey { + /// Generate a new SLH-DSA-SHA2-192f key pair. + pub fn generate(_rng: &mut R) -> Self { + let (public_key, secret_key) = pqcrypto_sphincsplus::sphincssha256192frobust::keypair(); + Self { + public_key, + secret_key, + } + } + + /// Get the public key bytes. + pub fn public_key_bytes(&self) -> Vec { + ::as_bytes( + &self.public_key, + ) + .to_vec() + } + + /// Get the private key bytes. + pub fn private_key_bytes(&self) -> Vec { + ::as_bytes( + &self.secret_key, + ) + .to_vec() + } + + /// Create from private key bytes. + pub fn from_private_key_bytes(bytes: &[u8]) -> Result { + let _secret_key = ::from_bytes(bytes) + .map_err(|_| BottleError::InvalidKeyType)?; + // Cannot derive public key from secret key in this API + Err(BottleError::InvalidKeyType) + } +} + +#[cfg(feature = "post-quantum")] +impl Sign for SlhDsaSha2_192fKey { + fn sign(&self, _rng: &mut dyn RngCore, message: &[u8]) -> Result> { + let detached_sig = pqcrypto_sphincsplus::sphincssha256192frobust::detached_sign( + message, + &self.secret_key, + ); + Ok(::as_bytes(&detached_sig).to_vec()) + } +} + +#[cfg(feature = "post-quantum")] +impl Verify for SlhDsaSha2_192fKey { + fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> { + let detached_sig = ::from_bytes(signature) + .map_err(|_| BottleError::VerifyFailed)?; + pqcrypto_sphincsplus::sphincssha256192frobust::verify_detached_signature( + &detached_sig, + message, + &self.public_key, + ) + .map_err(|_| BottleError::VerifyFailed)?; + Ok(()) + } +} + +#[cfg(feature = "post-quantum")] +impl SignerKey for SlhDsaSha2_192fKey { + fn fingerprint(&self) -> Vec { + crate::hash::sha256(&self.public_key_bytes()) + } + + fn public_key(&self) -> Vec { + self.public_key_bytes() + } +} + +#[cfg(feature = "post-quantum")] +/// SLH-DSA-SHA2-256s key pair for post-quantum hash-based signatures. +/// +/// SLH-DSA-SHA2-256s provides 256-bit security using SHA-2 hash function. +pub struct SlhDsaSha2_256sKey { + public_key: pqcrypto_sphincsplus::sphincssha256256srobust::PublicKey, + secret_key: pqcrypto_sphincsplus::sphincssha256256srobust::SecretKey, +} + +#[cfg(feature = "post-quantum")] +impl SlhDsaSha2_256sKey { + /// Generate a new SLH-DSA-SHA2-256s key pair. + pub fn generate(_rng: &mut R) -> Self { + let (public_key, secret_key) = pqcrypto_sphincsplus::sphincssha256256srobust::keypair(); + Self { + public_key, + secret_key, + } + } + + /// Get the public key bytes. + pub fn public_key_bytes(&self) -> Vec { + ::as_bytes( + &self.public_key, + ) + .to_vec() + } + + /// Get the private key bytes. + pub fn private_key_bytes(&self) -> Vec { + ::as_bytes( + &self.secret_key, + ) + .to_vec() + } + + /// Create from private key bytes. + pub fn from_private_key_bytes(bytes: &[u8]) -> Result { + let _secret_key = ::from_bytes(bytes) + .map_err(|_| BottleError::InvalidKeyType)?; + // Cannot derive public key from secret key in this API + Err(BottleError::InvalidKeyType) + } +} + +#[cfg(feature = "post-quantum")] +impl Sign for SlhDsaSha2_256sKey { + fn sign(&self, _rng: &mut dyn RngCore, message: &[u8]) -> Result> { + let detached_sig = pqcrypto_sphincsplus::sphincssha256256srobust::detached_sign( + message, + &self.secret_key, + ); + Ok(::as_bytes(&detached_sig).to_vec()) + } +} + +#[cfg(feature = "post-quantum")] +impl Verify for SlhDsaSha2_256sKey { + fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> { + let detached_sig = ::from_bytes(signature) + .map_err(|_| BottleError::VerifyFailed)?; + pqcrypto_sphincsplus::sphincssha256256srobust::verify_detached_signature( + &detached_sig, + message, + &self.public_key, + ) + .map_err(|_| BottleError::VerifyFailed)?; + Ok(()) + } +} + +#[cfg(feature = "post-quantum")] +impl SignerKey for SlhDsaSha2_256sKey { + fn fingerprint(&self) -> Vec { + crate::hash::sha256(&self.public_key_bytes()) + } + + fn public_key(&self) -> Vec { + self.public_key_bytes() + } +} + +#[cfg(feature = "post-quantum")] +/// SLH-DSA-SHA2-256f key pair for post-quantum hash-based signatures. +/// +/// SLH-DSA-SHA2-256f provides 256-bit security using SHA-2 hash function. +pub struct SlhDsaSha2_256fKey { + public_key: pqcrypto_sphincsplus::sphincssha256256frobust::PublicKey, + secret_key: pqcrypto_sphincsplus::sphincssha256256frobust::SecretKey, +} + +#[cfg(feature = "post-quantum")] +impl SlhDsaSha2_256fKey { + /// Generate a new SLH-DSA-SHA2-256f key pair. + pub fn generate(_rng: &mut R) -> Self { + let (public_key, secret_key) = pqcrypto_sphincsplus::sphincssha256256frobust::keypair(); + Self { + public_key, + secret_key, + } + } + + /// Get the public key bytes. + pub fn public_key_bytes(&self) -> Vec { + ::as_bytes( + &self.public_key, + ) + .to_vec() + } + + /// Get the private key bytes. + pub fn private_key_bytes(&self) -> Vec { + ::as_bytes( + &self.secret_key, + ) + .to_vec() + } + + /// Create from private key bytes. + pub fn from_private_key_bytes(bytes: &[u8]) -> Result { + let _secret_key = ::from_bytes(bytes) + .map_err(|_| BottleError::InvalidKeyType)?; + // Cannot derive public key from secret key in this API + Err(BottleError::InvalidKeyType) + } +} + +#[cfg(feature = "post-quantum")] +impl Sign for SlhDsaSha2_256fKey { + fn sign(&self, _rng: &mut dyn RngCore, message: &[u8]) -> Result> { + let detached_sig = pqcrypto_sphincsplus::sphincssha256256frobust::detached_sign( + message, + &self.secret_key, + ); + Ok(::as_bytes(&detached_sig).to_vec()) + } +} + +#[cfg(feature = "post-quantum")] +impl Verify for SlhDsaSha2_256fKey { + fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> { + let detached_sig = ::from_bytes(signature) + .map_err(|_| BottleError::VerifyFailed)?; + pqcrypto_sphincsplus::sphincssha256256frobust::verify_detached_signature( + &detached_sig, + message, + &self.public_key, + ) + .map_err(|_| BottleError::VerifyFailed)?; + Ok(()) + } +} + +#[cfg(feature = "post-quantum")] +impl SignerKey for SlhDsaSha2_256fKey { + fn fingerprint(&self) -> Vec { + crate::hash::sha256(&self.public_key_bytes()) + } + + fn public_key(&self) -> Vec { + self.public_key_bytes() + } +} diff --git a/src/lib.rs b/src/lib.rs index e36863a..9d6302f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,7 +90,11 @@ pub use keys::{EcdsaP256Key, Ed25519Key, RsaKey, X25519Key}; /// Post-quantum signature key types (requires `post-quantum` feature) #[cfg(feature = "post-quantum")] -pub use keys::{MlDsa44Key, MlDsa65Key, MlDsa87Key, SlhDsa128sKey, SlhDsa192sKey, SlhDsa256sKey}; +pub use keys::{ + MlDsa44Key, MlDsa65Key, MlDsa87Key, + SlhDsa128sKey, SlhDsa128fKey, SlhDsa192sKey, SlhDsa192fKey, SlhDsa256sKey, SlhDsa256fKey, + SlhDsaSha2_128sKey, SlhDsaSha2_128fKey, SlhDsaSha2_192sKey, SlhDsaSha2_192fKey, SlhDsaSha2_256sKey, SlhDsaSha2_256fKey, +}; /// Post-quantum encryption key types (requires `ml-kem` feature) #[cfg(feature = "ml-kem")] diff --git a/src/pkix.rs b/src/pkix.rs index 90ec437..0e056b2 100644 --- a/src/pkix.rs +++ b/src/pkix.rs @@ -73,12 +73,39 @@ pub enum KeyType { /// SLH-DSA-128s (requires `post-quantum` feature) #[cfg(feature = "post-quantum")] SlhDsa128s, + /// SLH-DSA-128f (requires `post-quantum` feature) + #[cfg(feature = "post-quantum")] + SlhDsa128f, /// SLH-DSA-192s (requires `post-quantum` feature) #[cfg(feature = "post-quantum")] SlhDsa192s, + /// SLH-DSA-192f (requires `post-quantum` feature) + #[cfg(feature = "post-quantum")] + SlhDsa192f, /// SLH-DSA-256s (requires `post-quantum` feature) #[cfg(feature = "post-quantum")] SlhDsa256s, + /// SLH-DSA-256f (requires `post-quantum` feature) + #[cfg(feature = "post-quantum")] + SlhDsa256f, + /// SLH-DSA-SHA2-128s (requires `post-quantum` feature) + #[cfg(feature = "post-quantum")] + SlhDsaSha2_128s, + /// SLH-DSA-SHA2-128f (requires `post-quantum` feature) + #[cfg(feature = "post-quantum")] + SlhDsaSha2_128f, + /// SLH-DSA-SHA2-192s (requires `post-quantum` feature) + #[cfg(feature = "post-quantum")] + SlhDsaSha2_192s, + /// SLH-DSA-SHA2-192f (requires `post-quantum` feature) + #[cfg(feature = "post-quantum")] + SlhDsaSha2_192f, + /// SLH-DSA-SHA2-256s (requires `post-quantum` feature) + #[cfg(feature = "post-quantum")] + SlhDsaSha2_256s, + /// SLH-DSA-SHA2-256f (requires `post-quantum` feature) + #[cfg(feature = "post-quantum")] + SlhDsaSha2_256f, } impl KeyType { @@ -105,7 +132,8 @@ impl KeyType { ObjectIdentifier::new("1.3.6.1.4.1.2.267.7.4.4").expect("Invalid ML-DSA OID") } #[cfg(feature = "post-quantum")] - KeyType::SlhDsa128s | KeyType::SlhDsa192s | KeyType::SlhDsa256s => { + KeyType::SlhDsa128s | KeyType::SlhDsa128f | KeyType::SlhDsa192s | KeyType::SlhDsa192f | KeyType::SlhDsa256s | KeyType::SlhDsa256f + | KeyType::SlhDsaSha2_128s | KeyType::SlhDsaSha2_128f | KeyType::SlhDsaSha2_192s | KeyType::SlhDsaSha2_192f | KeyType::SlhDsaSha2_256s | KeyType::SlhDsaSha2_256f => { // SLH-DSA OID (NIST standard) - placeholder, actual OID may differ // Note: This is a placeholder OID - update when NIST publishes official OIDs ObjectIdentifier::new("1.3.6.1.4.1.2.267.1.16.7").expect("Invalid SLH-DSA OID") @@ -186,7 +214,8 @@ pub fn marshal_pkix_public_key_with_type( marshal_mldsa_pkix(public_key_bytes, key_type) } #[cfg(feature = "post-quantum")] - KeyType::SlhDsa128s | KeyType::SlhDsa192s | KeyType::SlhDsa256s => { + KeyType::SlhDsa128s | KeyType::SlhDsa128f | KeyType::SlhDsa192s | KeyType::SlhDsa192f | KeyType::SlhDsa256s | KeyType::SlhDsa256f + | KeyType::SlhDsaSha2_128s | KeyType::SlhDsaSha2_128f | KeyType::SlhDsaSha2_192s | KeyType::SlhDsaSha2_192f | KeyType::SlhDsaSha2_256s | KeyType::SlhDsaSha2_256f => { marshal_slhdsa_pkix(public_key_bytes, key_type) } } @@ -317,7 +346,8 @@ pub fn marshal_pkcs8_private_key(private_key_bytes: &[u8], key_type: KeyType) -> marshal_mldsa_pkcs8(private_key_bytes, key_type) } #[cfg(feature = "post-quantum")] - KeyType::SlhDsa128s | KeyType::SlhDsa192s | KeyType::SlhDsa256s => { + KeyType::SlhDsa128s | KeyType::SlhDsa128f | KeyType::SlhDsa192s | KeyType::SlhDsa192f | KeyType::SlhDsa256s | KeyType::SlhDsa256f + | KeyType::SlhDsaSha2_128s | KeyType::SlhDsaSha2_128f | KeyType::SlhDsaSha2_192s | KeyType::SlhDsaSha2_192f | KeyType::SlhDsaSha2_256s | KeyType::SlhDsaSha2_256f => { marshal_slhdsa_pkcs8(private_key_bytes, key_type) } } diff --git a/tests/pqc_test.rs b/tests/pqc_test.rs index 0f13688..8239603 100644 --- a/tests/pqc_test.rs +++ b/tests/pqc_test.rs @@ -10,7 +10,9 @@ use rust_bottle::{ #[cfg(feature = "post-quantum")] use rust_bottle::{ - MlDsa44Key, MlDsa65Key, MlDsa87Key, SlhDsa128sKey, SlhDsa192sKey, SlhDsa256sKey, + MlDsa44Key, MlDsa65Key, MlDsa87Key, + SlhDsa128sKey, SlhDsa128fKey, SlhDsa192sKey, SlhDsa192fKey, SlhDsa256sKey, SlhDsa256fKey, + SlhDsaSha2_128sKey, SlhDsaSha2_128fKey, SlhDsaSha2_192sKey, SlhDsaSha2_192fKey, SlhDsaSha2_256sKey, SlhDsaSha2_256fKey, }; // Import common types @@ -275,6 +277,109 @@ fn test_slhdsa256s_key_sizes() { assert_eq!(key.private_key_bytes().len(), 128); } +// SLH-DSA Fast Variants (SHAKE-256) + +#[cfg(feature = "post-quantum")] +#[test] +fn test_slhdsa128f_signing() { + let rng = &mut OsRng; + let key = SlhDsa128fKey::generate(rng); + let message = b"SLH-DSA-128f signed message"; + + let signature = key.sign(rng, message).unwrap(); + assert!(key.verify(message, &signature).is_ok()); +} + +#[cfg(feature = "post-quantum")] +#[test] +fn test_slhdsa192f_signing() { + let rng = &mut OsRng; + let key = SlhDsa192fKey::generate(rng); + let message = b"SLH-DSA-192f signed message"; + + let signature = key.sign(rng, message).unwrap(); + assert!(key.verify(message, &signature).is_ok()); +} + +#[cfg(feature = "post-quantum")] +#[test] +fn test_slhdsa256f_signing() { + let rng = &mut OsRng; + let key = SlhDsa256fKey::generate(rng); + let message = b"SLH-DSA-256f signed message"; + + let signature = key.sign(rng, message).unwrap(); + assert!(key.verify(message, &signature).is_ok()); +} + +// SLH-DSA SHA-2 Variants + +#[cfg(feature = "post-quantum")] +#[test] +fn test_slhdsa_sha2_128s_signing() { + let rng = &mut OsRng; + let key = SlhDsaSha2_128sKey::generate(rng); + let message = b"SLH-DSA-SHA2-128s signed message"; + + let signature = key.sign(rng, message).unwrap(); + assert!(key.verify(message, &signature).is_ok()); +} + +#[cfg(feature = "post-quantum")] +#[test] +fn test_slhdsa_sha2_128f_signing() { + let rng = &mut OsRng; + let key = SlhDsaSha2_128fKey::generate(rng); + let message = b"SLH-DSA-SHA2-128f signed message"; + + let signature = key.sign(rng, message).unwrap(); + assert!(key.verify(message, &signature).is_ok()); +} + +#[cfg(feature = "post-quantum")] +#[test] +fn test_slhdsa_sha2_192s_signing() { + let rng = &mut OsRng; + let key = SlhDsaSha2_192sKey::generate(rng); + let message = b"SLH-DSA-SHA2-192s signed message"; + + let signature = key.sign(rng, message).unwrap(); + assert!(key.verify(message, &signature).is_ok()); +} + +#[cfg(feature = "post-quantum")] +#[test] +fn test_slhdsa_sha2_192f_signing() { + let rng = &mut OsRng; + let key = SlhDsaSha2_192fKey::generate(rng); + let message = b"SLH-DSA-SHA2-192f signed message"; + + let signature = key.sign(rng, message).unwrap(); + assert!(key.verify(message, &signature).is_ok()); +} + +#[cfg(feature = "post-quantum")] +#[test] +fn test_slhdsa_sha2_256s_signing() { + let rng = &mut OsRng; + let key = SlhDsaSha2_256sKey::generate(rng); + let message = b"SLH-DSA-SHA2-256s signed message"; + + let signature = key.sign(rng, message).unwrap(); + assert!(key.verify(message, &signature).is_ok()); +} + +#[cfg(feature = "post-quantum")] +#[test] +fn test_slhdsa_sha2_256f_signing() { + let rng = &mut OsRng; + let key = SlhDsaSha2_256fKey::generate(rng); + let message = b"SLH-DSA-SHA2-256f signed message"; + + let signature = key.sign(rng, message).unwrap(); + assert!(key.verify(message, &signature).is_ok()); +} + // ============================================================================ // Hybrid Encryption Tests // ============================================================================ diff --git a/tests/test_sphincs_availability.rs b/tests/test_sphincs_availability.rs new file mode 100644 index 0000000..75201f6 --- /dev/null +++ b/tests/test_sphincs_availability.rs @@ -0,0 +1,18 @@ +#[cfg(feature = "post-quantum")] +#[test] +fn test_sphincs_variants_available() { + use pqcrypto_sphincsplus as sp; + + // Test if fast variants are available + let _ = sp::sphincsshake256128frobust::keypair; + let _ = sp::sphincsshake256192frobust::keypair; + let _ = sp::sphincsshake256256frobust::keypair; + + // Test if SHA-2 variants are available + let _ = sp::sphincssha256128srobust::keypair; + let _ = sp::sphincssha256128frobust::keypair; + let _ = sp::sphincssha256192srobust::keypair; + let _ = sp::sphincssha256192frobust::keypair; + let _ = sp::sphincssha256256srobust::keypair; + let _ = sp::sphincssha256256frobust::keypair; +}