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 0000000..6111f2b Binary files /dev/null and b/libcheck_sphincs.rlib differ 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; +}