// Module-level allow for Zeroize macro false positives // The variant field IS used via variant() getter, but Zeroize macro generates // intermediate assignments that trigger "value assigned but never read" warnings. #![allow(unused_assignments)] //! SPHINCS+ (SLH-DSA) hash-based digital signatures. //! //! SPHINCS+ is a stateless hash-based signature scheme selected by NIST as //! FIPS 205 (SLH-DSA). Unlike lattice-based schemes like Dilithium, SPHINCS+ //! relies only on the security of hash functions, making it a conservative //! backup option. //! //! # Security Properties //! //! - **Hash-Based**: Security relies only on SHA3/SHAKE, no lattice assumptions //! - **Stateless**: No state management required (unlike XMSS/LMS) //! - **Conservative**: Backup algorithm if lattice schemes are compromised //! //! # Trade-offs //! //! | Variant | Security | Signature Size | Public Key | Speed | //! |---------|----------|----------------|------------|-------| //! | SPHINCS+-128s | 128-bit | 7,856 bytes | 32 bytes | Slow | //! | SPHINCS+-128f | 128-bit | 17,088 bytes | 32 bytes | Fast | //! | SPHINCS+-192s | 192-bit | 16,224 bytes | 48 bytes | Slow | //! | SPHINCS+-192f | 192-bit | 35,664 bytes | 48 bytes | Fast | //! | SPHINCS+-256s | 256-bit | 29,792 bytes | 64 bytes | Slow | //! | SPHINCS+-256f | 256-bit | 49,856 bytes | 64 bytes | Fast | //! //! # Usage //! //! ```rust //! use synor_crypto::sphincs::{SphincsKeypair, SphincsVariant}; //! //! // Generate keypair (128-bit security, small signatures) //! let keypair = SphincsKeypair::generate(SphincsVariant::Shake128s); //! //! // Sign a message //! let message = b"Hello, post-quantum world!"; //! let signature = keypair.sign(message); //! //! // Verify signature //! assert!(keypair.public_key().verify(message, &signature).is_ok()); //! //! // Signature is ~7.8KB for 128s variant //! println!("Signature size: {} bytes", signature.len()); //! ``` use pqcrypto_sphincsplus::sphincsshake128ssimple as sphincs128s; use pqcrypto_sphincsplus::sphincsshake192ssimple as sphincs192s; use pqcrypto_sphincsplus::sphincsshake256ssimple as sphincs256s; use pqcrypto_traits::sign::{ DetachedSignature, PublicKey as PqPublicKey, SecretKey as PqSecretKey, }; use thiserror::Error; use zeroize::{Zeroize, ZeroizeOnDrop}; /// SPHINCS+ variant selection. /// /// All variants use SHAKE (SHA3-based) for hashing. /// 's' variants have smaller signatures but are slower. /// 'f' variants are faster but have larger signatures. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Default)] pub enum SphincsVariant { /// 128-bit security, small signatures (~7.8KB) #[default] Shake128s, /// 192-bit security, small signatures (~16KB) Shake192s, /// 256-bit security, small signatures (~30KB) Shake256s, } impl SphincsVariant { /// Returns the expected signature size in bytes. pub const fn signature_size(&self) -> usize { match self { SphincsVariant::Shake128s => 7856, SphincsVariant::Shake192s => 16224, SphincsVariant::Shake256s => 29792, } } /// Returns the expected public key size in bytes. pub const fn public_key_size(&self) -> usize { match self { SphincsVariant::Shake128s => 32, SphincsVariant::Shake192s => 48, SphincsVariant::Shake256s => 64, } } /// Returns the expected secret key size in bytes. pub const fn secret_key_size(&self) -> usize { match self { SphincsVariant::Shake128s => 64, SphincsVariant::Shake192s => 96, SphincsVariant::Shake256s => 128, } } /// Returns the security level in bits. pub const fn security_level(&self) -> u16 { match self { SphincsVariant::Shake128s => 128, SphincsVariant::Shake192s => 192, SphincsVariant::Shake256s => 256, } } /// Returns the NIST security category. pub const fn nist_category(&self) -> u8 { match self { SphincsVariant::Shake128s => 1, SphincsVariant::Shake192s => 3, SphincsVariant::Shake256s => 5, } } } /// SPHINCS+ public key. #[derive(Clone)] pub struct SphincsPublicKey { variant: SphincsVariant, bytes: Vec, } impl SphincsPublicKey { /// Creates a public key from bytes. pub fn from_bytes(variant: SphincsVariant, bytes: &[u8]) -> Result { if bytes.len() != variant.public_key_size() { return Err(SphincsError::InvalidKeySize { expected: variant.public_key_size(), got: bytes.len(), }); } Ok(Self { variant, bytes: bytes.to_vec(), }) } /// Returns the public key bytes. pub fn as_bytes(&self) -> &[u8] { &self.bytes } /// Returns the variant used. pub fn variant(&self) -> SphincsVariant { self.variant } /// Verifies a signature against this public key. pub fn verify(&self, message: &[u8], signature: &SphincsSignature) -> Result<(), SphincsError> { if signature.variant != self.variant { return Err(SphincsError::VariantMismatch); } match self.variant { SphincsVariant::Shake128s => { let pk = sphincs128s::PublicKey::from_bytes(&self.bytes) .map_err(|_| SphincsError::InvalidPublicKey)?; let sig = sphincs128s::DetachedSignature::from_bytes(&signature.bytes) .map_err(|_| SphincsError::InvalidSignature)?; sphincs128s::verify_detached_signature(&sig, message, &pk) .map_err(|_| SphincsError::VerificationFailed) } SphincsVariant::Shake192s => { let pk = sphincs192s::PublicKey::from_bytes(&self.bytes) .map_err(|_| SphincsError::InvalidPublicKey)?; let sig = sphincs192s::DetachedSignature::from_bytes(&signature.bytes) .map_err(|_| SphincsError::InvalidSignature)?; sphincs192s::verify_detached_signature(&sig, message, &pk) .map_err(|_| SphincsError::VerificationFailed) } SphincsVariant::Shake256s => { let pk = sphincs256s::PublicKey::from_bytes(&self.bytes) .map_err(|_| SphincsError::InvalidPublicKey)?; let sig = sphincs256s::DetachedSignature::from_bytes(&signature.bytes) .map_err(|_| SphincsError::InvalidSignature)?; sphincs256s::verify_detached_signature(&sig, message, &pk) .map_err(|_| SphincsError::VerificationFailed) } } } } impl std::fmt::Debug for SphincsPublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SphincsPublicKey") .field("variant", &self.variant) .field("bytes", &hex::encode(&self.bytes[..8.min(self.bytes.len())])) .finish() } } /// SPHINCS+ secret key (zeroized on drop). // Allow needed: Zeroize macro generates code that triggers false positive warnings // about variant being assigned but never read. The variant field IS used via variant() getter. #[allow(unused_assignments)] #[derive(Zeroize, ZeroizeOnDrop)] pub struct SphincsSecretKey { #[zeroize(skip)] variant: SphincsVariant, bytes: Vec, } impl SphincsSecretKey { /// Creates a secret key from bytes. pub fn from_bytes(variant: SphincsVariant, bytes: &[u8]) -> Result { if bytes.len() != variant.secret_key_size() { return Err(SphincsError::InvalidKeySize { expected: variant.secret_key_size(), got: bytes.len(), }); } Ok(Self { variant, bytes: bytes.to_vec(), }) } /// Returns the secret key bytes. pub fn as_bytes(&self) -> &[u8] { &self.bytes } /// Returns the variant used. pub fn variant(&self) -> SphincsVariant { self.variant } } impl std::fmt::Debug for SphincsSecretKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SphincsSecretKey") .field("variant", &self.variant) .field("bytes", &"[REDACTED]") .finish() } } /// SPHINCS+ signature. #[derive(Clone)] pub struct SphincsSignature { variant: SphincsVariant, bytes: Vec, } impl SphincsSignature { /// Creates a signature from bytes. pub fn from_bytes(variant: SphincsVariant, bytes: &[u8]) -> Result { if bytes.len() != variant.signature_size() { return Err(SphincsError::InvalidSignatureSize { expected: variant.signature_size(), got: bytes.len(), }); } Ok(Self { variant, bytes: bytes.to_vec(), }) } /// Returns the signature bytes. pub fn as_bytes(&self) -> &[u8] { &self.bytes } /// Returns the signature length. pub fn len(&self) -> usize { self.bytes.len() } /// Returns true if the signature is empty. pub fn is_empty(&self) -> bool { self.bytes.is_empty() } /// Returns the variant used. pub fn variant(&self) -> SphincsVariant { self.variant } } impl std::fmt::Debug for SphincsSignature { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SphincsSignature") .field("variant", &self.variant) .field("size", &self.bytes.len()) .finish() } } /// SPHINCS+ keypair. pub struct SphincsKeypair { variant: SphincsVariant, public_key: SphincsPublicKey, secret_key: SphincsSecretKey, } impl SphincsKeypair { /// Generates a new random keypair. pub fn generate(variant: SphincsVariant) -> Self { match variant { SphincsVariant::Shake128s => { let (pk, sk) = sphincs128s::keypair(); Self { variant, public_key: SphincsPublicKey { variant, bytes: pk.as_bytes().to_vec(), }, secret_key: SphincsSecretKey { variant, bytes: sk.as_bytes().to_vec(), }, } } SphincsVariant::Shake192s => { let (pk, sk) = sphincs192s::keypair(); Self { variant, public_key: SphincsPublicKey { variant, bytes: pk.as_bytes().to_vec(), }, secret_key: SphincsSecretKey { variant, bytes: sk.as_bytes().to_vec(), }, } } SphincsVariant::Shake256s => { let (pk, sk) = sphincs256s::keypair(); Self { variant, public_key: SphincsPublicKey { variant, bytes: pk.as_bytes().to_vec(), }, secret_key: SphincsSecretKey { variant, bytes: sk.as_bytes().to_vec(), }, } } } } /// Creates a keypair from existing keys. pub fn from_keys( public_key: SphincsPublicKey, secret_key: SphincsSecretKey, ) -> Result { if public_key.variant != secret_key.variant { return Err(SphincsError::VariantMismatch); } Ok(Self { variant: public_key.variant, public_key, secret_key, }) } /// Signs a message. pub fn sign(&self, message: &[u8]) -> SphincsSignature { let bytes = match self.variant { SphincsVariant::Shake128s => { let sk = sphincs128s::SecretKey::from_bytes(self.secret_key.as_bytes()) .expect("valid secret key"); let sig = sphincs128s::detached_sign(message, &sk); sig.as_bytes().to_vec() } SphincsVariant::Shake192s => { let sk = sphincs192s::SecretKey::from_bytes(self.secret_key.as_bytes()) .expect("valid secret key"); let sig = sphincs192s::detached_sign(message, &sk); sig.as_bytes().to_vec() } SphincsVariant::Shake256s => { let sk = sphincs256s::SecretKey::from_bytes(self.secret_key.as_bytes()) .expect("valid secret key"); let sig = sphincs256s::detached_sign(message, &sk); sig.as_bytes().to_vec() } }; SphincsSignature { variant: self.variant, bytes, } } /// Returns the public key. pub fn public_key(&self) -> &SphincsPublicKey { &self.public_key } /// Returns the secret key. pub fn secret_key(&self) -> &SphincsSecretKey { &self.secret_key } /// Returns the variant used. pub fn variant(&self) -> SphincsVariant { self.variant } } impl std::fmt::Debug for SphincsKeypair { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SphincsKeypair") .field("variant", &self.variant) .field("public_key", &self.public_key) .finish() } } /// Errors from SPHINCS+ operations. #[derive(Debug, Error)] pub enum SphincsError { #[error("Invalid key size: expected {expected}, got {got}")] InvalidKeySize { expected: usize, got: usize }, #[error("Invalid signature size: expected {expected}, got {got}")] InvalidSignatureSize { expected: usize, got: usize }, #[error("Invalid public key")] InvalidPublicKey, #[error("Invalid secret key")] InvalidSecretKey, #[error("Invalid signature")] InvalidSignature, #[error("Signature verification failed")] VerificationFailed, #[error("Variant mismatch between key and signature")] VariantMismatch, } #[cfg(test)] mod tests { use super::*; #[test] fn test_generate_128s() { let keypair = SphincsKeypair::generate(SphincsVariant::Shake128s); assert_eq!(keypair.variant(), SphincsVariant::Shake128s); assert_eq!(keypair.public_key().as_bytes().len(), 32); // Test component variant accessors assert_eq!(keypair.secret_key().variant(), SphincsVariant::Shake128s); assert_eq!(keypair.public_key().variant(), SphincsVariant::Shake128s); } #[test] fn test_sign_verify_128s() { let keypair = SphincsKeypair::generate(SphincsVariant::Shake128s); let message = b"Test message for SPHINCS+ signing"; let signature = keypair.sign(message); assert_eq!(signature.len(), 7856); // Verify should succeed assert!(keypair.public_key().verify(message, &signature).is_ok()); } #[test] fn test_sign_verify_192s() { let keypair = SphincsKeypair::generate(SphincsVariant::Shake192s); let message = b"Test message for SPHINCS+ 192"; let signature = keypair.sign(message); assert_eq!(signature.len(), 16224); assert!(keypair.public_key().verify(message, &signature).is_ok()); } #[test] fn test_sign_verify_256s() { let keypair = SphincsKeypair::generate(SphincsVariant::Shake256s); let message = b"Test message for SPHINCS+ 256"; let signature = keypair.sign(message); assert_eq!(signature.len(), 29792); assert!(keypair.public_key().verify(message, &signature).is_ok()); } #[test] fn test_invalid_signature_fails() { let keypair = SphincsKeypair::generate(SphincsVariant::Shake128s); let message = b"Original message"; let signature = keypair.sign(message); // Verify with wrong message should fail let wrong_message = b"Wrong message"; assert!(keypair.public_key().verify(wrong_message, &signature).is_err()); } #[test] fn test_variant_sizes() { assert_eq!(SphincsVariant::Shake128s.signature_size(), 7856); assert_eq!(SphincsVariant::Shake128s.public_key_size(), 32); assert_eq!(SphincsVariant::Shake128s.secret_key_size(), 64); assert_eq!(SphincsVariant::Shake192s.signature_size(), 16224); assert_eq!(SphincsVariant::Shake192s.public_key_size(), 48); assert_eq!(SphincsVariant::Shake256s.signature_size(), 29792); assert_eq!(SphincsVariant::Shake256s.public_key_size(), 64); } #[test] fn test_security_levels() { assert_eq!(SphincsVariant::Shake128s.security_level(), 128); assert_eq!(SphincsVariant::Shake192s.security_level(), 192); assert_eq!(SphincsVariant::Shake256s.security_level(), 256); } }