From 9414ef5d9909275829518a92eb78ccda48b25424 Mon Sep 17 00:00:00 2001 From: Gulshan Yadav Date: Mon, 19 Jan 2026 09:52:16 +0530 Subject: [PATCH] feat(crypto): add SPHINCS+ and FALCON post-quantum algorithms Phase 13 Milestone 2 - Enhanced Quantum Cryptography: SPHINCS+ (FIPS 205 / SLH-DSA): - Hash-based signatures as backup if lattice schemes are compromised - Three variants: 128s (~7.8KB), 192s (~16KB), 256s (~30KB) - Relies only on hash function security (conservative choice) - SphincsKeypair, SphincsPublicKey, SphincsSecretKey, SphincsSignature FALCON (FIPS 206 / FN-DSA): - Compact lattice signatures for bandwidth-constrained devices - FALCON-512: 128-bit security, ~690 byte signatures - FALCON-1024: 256-bit security, ~1,330 byte signatures - ~79% smaller than Dilithium3 signatures - Ideal for mobile wallets and L2 batch transactions Algorithm Comparison: | Algorithm | Security | Sig Size | Use Case | |-----------|----------|----------|----------| | Ed25519 | 128-bit | 64 B | Classical (fast) | | Dilithium3 | 192-bit | 3,293 B | Default PQ | | FALCON-512 | 128-bit | 690 B | Mobile/IoT | | SPHINCS+-128s | 128-bit | 7,856 B | Backup | All 40 unit tests + 5 doc tests passing. --- Cargo.toml | 6 +- crates/synor-crypto/Cargo.toml | 8 +- crates/synor-crypto/src/falcon.rs | 504 ++++++++++++++++++++++++++++ crates/synor-crypto/src/lib.rs | 13 + crates/synor-crypto/src/sphincs.rs | 508 +++++++++++++++++++++++++++++ 5 files changed, 1034 insertions(+), 5 deletions(-) create mode 100644 crates/synor-crypto/src/falcon.rs create mode 100644 crates/synor-crypto/src/sphincs.rs diff --git a/Cargo.toml b/Cargo.toml index af65ff4..79e2127 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,8 +69,10 @@ rand = "0.8" rand_core = "0.6" # Cryptography - Post-Quantum (NIST standards) -pqcrypto-dilithium = "0.5" -pqcrypto-kyber = "0.8" +pqcrypto-dilithium = "0.5" # FIPS 204 (ML-DSA) - primary signatures +pqcrypto-kyber = "0.8" # FIPS 203 (ML-KEM) - key encapsulation +pqcrypto-sphincsplus = "0.7" # FIPS 205 (SLH-DSA) - hash-based backup +pqcrypto-falcon = "0.3" # FIPS 206 (FN-DSA) - compact lattice pqcrypto-traits = "0.3" # Hashing diff --git a/crates/synor-crypto/Cargo.toml b/crates/synor-crypto/Cargo.toml index a4aa185..425496b 100644 --- a/crates/synor-crypto/Cargo.toml +++ b/crates/synor-crypto/Cargo.toml @@ -15,9 +15,11 @@ x25519-dalek = { workspace = true } rand = { workspace = true } rand_core = { workspace = true } -# Post-quantum cryptography -pqcrypto-dilithium = { workspace = true } -pqcrypto-kyber = { workspace = true } +# Post-quantum cryptography (NIST FIPS 203-206) +pqcrypto-dilithium = { workspace = true } # FIPS 204 (ML-DSA) +pqcrypto-kyber = { workspace = true } # FIPS 203 (ML-KEM) +pqcrypto-sphincsplus = { workspace = true } # FIPS 205 (SLH-DSA) +pqcrypto-falcon = { workspace = true } # FIPS 206 (FN-DSA) pqcrypto-traits = { workspace = true } # Hashing diff --git a/crates/synor-crypto/src/falcon.rs b/crates/synor-crypto/src/falcon.rs new file mode 100644 index 0000000..edc70f0 --- /dev/null +++ b/crates/synor-crypto/src/falcon.rs @@ -0,0 +1,504 @@ +//! FALCON (FN-DSA) compact lattice-based digital signatures. +//! +//! FALCON is a lattice-based signature scheme selected by NIST as FIPS 206 +//! (FN-DSA). It provides significantly smaller signatures than Dilithium, +//! making it ideal for bandwidth-constrained applications like mobile wallets +//! and IoT devices. +//! +//! # Key Properties +//! +//! - **Compact Signatures**: ~690 bytes (vs ~3,300 for Dilithium3) +//! - **Fast Verification**: Faster than Dilithium verification +//! - **NTRU Lattices**: Based on NTRU lattice problems with FFT optimization +//! +//! # Trade-offs +//! +//! | Variant | Security | Signature | Public Key | Private Key | +//! |---------|----------|-----------|------------|-------------| +//! | FALCON-512 | 128-bit | 690 bytes | 897 bytes | 1,281 bytes | +//! | FALCON-1024 | 256-bit | 1,330 bytes | 1,793 bytes | 2,305 bytes | +//! +//! Compared to Dilithium3 (192-bit, 3,293 byte signatures), FALCON-512 +//! achieves ~80% signature size reduction at 128-bit security. +//! +//! # Usage +//! +//! ```rust +//! use synor_crypto::falcon::{FalconKeypair, FalconVariant}; +//! +//! // Generate keypair (FALCON-512 for mobile devices) +//! let keypair = FalconKeypair::generate(FalconVariant::Falcon512); +//! +//! // Sign a message +//! let message = b"Transaction payload"; +//! let signature = keypair.sign(message); +//! +//! // Verify signature +//! assert!(keypair.public_key().verify(message, &signature).is_ok()); +//! +//! // Signature is only ~690 bytes! +//! println!("Signature size: {} bytes", signature.len()); +//! ``` +//! +//! # When to Use FALCON vs Dilithium +//! +//! | Use Case | Recommendation | +//! |----------|----------------| +//! | Desktop wallets | Dilithium3 (higher security) | +//! | Mobile wallets | FALCON-512 (smaller signatures) | +//! | IoT devices | FALCON-512 (bandwidth constrained) | +//! | Layer 2 transactions | FALCON-512 (batch efficiency) | +//! | High-value transactions | Dilithium3 (conservative choice) | + +use pqcrypto_falcon::falcon512; +use pqcrypto_falcon::falcon1024; +use pqcrypto_traits::sign::{ + DetachedSignature, PublicKey as PqPublicKey, SecretKey as PqSecretKey, +}; +use thiserror::Error; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +/// FALCON variant selection. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum FalconVariant { + /// 128-bit security, ~690 byte signatures + Falcon512, + /// 256-bit security, ~1,330 byte signatures + Falcon1024, +} + +impl FalconVariant { + /// Returns the expected signature size in bytes. + pub const fn signature_size(&self) -> usize { + match self { + FalconVariant::Falcon512 => 690, + FalconVariant::Falcon1024 => 1330, + } + } + + /// Returns the expected public key size in bytes. + pub const fn public_key_size(&self) -> usize { + match self { + FalconVariant::Falcon512 => 897, + FalconVariant::Falcon1024 => 1793, + } + } + + /// Returns the expected secret key size in bytes. + pub const fn secret_key_size(&self) -> usize { + match self { + FalconVariant::Falcon512 => 1281, + FalconVariant::Falcon1024 => 2305, + } + } + + /// Returns the security level in bits. + pub const fn security_level(&self) -> u16 { + match self { + FalconVariant::Falcon512 => 128, + FalconVariant::Falcon1024 => 256, + } + } + + /// Returns the NIST security category. + pub const fn nist_category(&self) -> u8 { + match self { + FalconVariant::Falcon512 => 1, + FalconVariant::Falcon1024 => 5, + } + } + + /// Returns the n parameter (lattice dimension). + pub const fn n(&self) -> u16 { + match self { + FalconVariant::Falcon512 => 512, + FalconVariant::Falcon1024 => 1024, + } + } +} + +impl Default for FalconVariant { + fn default() -> Self { + FalconVariant::Falcon512 + } +} + +/// FALCON public key. +#[derive(Clone)] +pub struct FalconPublicKey { + variant: FalconVariant, + bytes: Vec, +} + +impl FalconPublicKey { + /// Creates a public key from bytes. + pub fn from_bytes(variant: FalconVariant, bytes: &[u8]) -> Result { + if bytes.len() != variant.public_key_size() { + return Err(FalconError::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) -> FalconVariant { + self.variant + } + + /// Verifies a signature against this public key. + pub fn verify(&self, message: &[u8], signature: &FalconSignature) -> Result<(), FalconError> { + if signature.variant != self.variant { + return Err(FalconError::VariantMismatch); + } + + match self.variant { + FalconVariant::Falcon512 => { + let pk = falcon512::PublicKey::from_bytes(&self.bytes) + .map_err(|_| FalconError::InvalidPublicKey)?; + let sig = falcon512::DetachedSignature::from_bytes(&signature.bytes) + .map_err(|_| FalconError::InvalidSignature)?; + falcon512::verify_detached_signature(&sig, message, &pk) + .map_err(|_| FalconError::VerificationFailed) + } + FalconVariant::Falcon1024 => { + let pk = falcon1024::PublicKey::from_bytes(&self.bytes) + .map_err(|_| FalconError::InvalidPublicKey)?; + let sig = falcon1024::DetachedSignature::from_bytes(&signature.bytes) + .map_err(|_| FalconError::InvalidSignature)?; + falcon1024::verify_detached_signature(&sig, message, &pk) + .map_err(|_| FalconError::VerificationFailed) + } + } + } +} + +impl std::fmt::Debug for FalconPublicKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FalconPublicKey") + .field("variant", &self.variant) + .field("bytes", &hex::encode(&self.bytes[..8.min(self.bytes.len())])) + .finish() + } +} + +/// FALCON secret key (zeroized on drop). +#[derive(Zeroize, ZeroizeOnDrop)] +pub struct FalconSecretKey { + #[zeroize(skip)] + variant: FalconVariant, + bytes: Vec, +} + +impl FalconSecretKey { + /// Creates a secret key from bytes. + pub fn from_bytes(variant: FalconVariant, bytes: &[u8]) -> Result { + if bytes.len() != variant.secret_key_size() { + return Err(FalconError::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) -> FalconVariant { + self.variant + } +} + +/// FALCON signature. +#[derive(Clone)] +pub struct FalconSignature { + variant: FalconVariant, + bytes: Vec, +} + +impl FalconSignature { + /// Creates a signature from bytes. + pub fn from_bytes(variant: FalconVariant, bytes: &[u8]) -> Result { + // FALCON signatures have variable length, so we allow some flexibility + let expected = variant.signature_size(); + // Allow up to 10% variance in signature size + if bytes.len() > expected + expected / 10 { + return Err(FalconError::InvalidSignatureSize { + expected, + 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) -> FalconVariant { + self.variant + } +} + +impl std::fmt::Debug for FalconSignature { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FalconSignature") + .field("variant", &self.variant) + .field("size", &self.bytes.len()) + .finish() + } +} + +/// FALCON keypair. +pub struct FalconKeypair { + variant: FalconVariant, + public_key: FalconPublicKey, + secret_key: FalconSecretKey, +} + +impl FalconKeypair { + /// Generates a new random keypair. + pub fn generate(variant: FalconVariant) -> Self { + match variant { + FalconVariant::Falcon512 => { + let (pk, sk) = falcon512::keypair(); + Self { + variant, + public_key: FalconPublicKey { + variant, + bytes: pk.as_bytes().to_vec(), + }, + secret_key: FalconSecretKey { + variant, + bytes: sk.as_bytes().to_vec(), + }, + } + } + FalconVariant::Falcon1024 => { + let (pk, sk) = falcon1024::keypair(); + Self { + variant, + public_key: FalconPublicKey { + variant, + bytes: pk.as_bytes().to_vec(), + }, + secret_key: FalconSecretKey { + variant, + bytes: sk.as_bytes().to_vec(), + }, + } + } + } + } + + /// Creates a keypair from existing keys. + pub fn from_keys( + public_key: FalconPublicKey, + secret_key: FalconSecretKey, + ) -> Result { + if public_key.variant != secret_key.variant { + return Err(FalconError::VariantMismatch); + } + Ok(Self { + variant: public_key.variant, + public_key, + secret_key, + }) + } + + /// Signs a message. + pub fn sign(&self, message: &[u8]) -> FalconSignature { + let bytes = match self.variant { + FalconVariant::Falcon512 => { + let sk = falcon512::SecretKey::from_bytes(self.secret_key.as_bytes()) + .expect("valid secret key"); + let sig = falcon512::detached_sign(message, &sk); + sig.as_bytes().to_vec() + } + FalconVariant::Falcon1024 => { + let sk = falcon1024::SecretKey::from_bytes(self.secret_key.as_bytes()) + .expect("valid secret key"); + let sig = falcon1024::detached_sign(message, &sk); + sig.as_bytes().to_vec() + } + }; + + FalconSignature { + variant: self.variant, + bytes, + } + } + + /// Returns the public key. + pub fn public_key(&self) -> &FalconPublicKey { + &self.public_key + } + + /// Returns the secret key. + pub fn secret_key(&self) -> &FalconSecretKey { + &self.secret_key + } + + /// Returns the variant used. + pub fn variant(&self) -> FalconVariant { + self.variant + } +} + +impl std::fmt::Debug for FalconKeypair { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FalconKeypair") + .field("variant", &self.variant) + .field("public_key", &self.public_key) + .finish() + } +} + +/// Errors from FALCON operations. +#[derive(Debug, Error)] +pub enum FalconError { + #[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, +} + +/// Comparison of signature sizes for different algorithms. +pub mod comparison { + /// Ed25519 signature size (classical) + pub const ED25519_SIG_SIZE: usize = 64; + + /// Dilithium3 signature size (FIPS 204) + pub const DILITHIUM3_SIG_SIZE: usize = 3293; + + /// FALCON-512 signature size (FIPS 206) + pub const FALCON512_SIG_SIZE: usize = 690; + + /// SPHINCS+-128s signature size (FIPS 205) + pub const SPHINCS128S_SIG_SIZE: usize = 7856; + + /// Calculates bandwidth savings using FALCON instead of Dilithium. + pub fn bandwidth_savings_percent() -> f64 { + let savings = 1.0 - (FALCON512_SIG_SIZE as f64 / DILITHIUM3_SIG_SIZE as f64); + savings * 100.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generate_512() { + let keypair = FalconKeypair::generate(FalconVariant::Falcon512); + assert_eq!(keypair.variant(), FalconVariant::Falcon512); + assert_eq!(keypair.public_key().as_bytes().len(), 897); + } + + #[test] + fn test_sign_verify_512() { + let keypair = FalconKeypair::generate(FalconVariant::Falcon512); + let message = b"Test message for FALCON signing"; + + let signature = keypair.sign(message); + // FALCON signatures are variable length, but typically ~690 bytes + assert!(signature.len() <= 800); + + // Verify should succeed + assert!(keypair.public_key().verify(message, &signature).is_ok()); + } + + #[test] + fn test_sign_verify_1024() { + let keypair = FalconKeypair::generate(FalconVariant::Falcon1024); + let message = b"Test message for FALCON-1024"; + + let signature = keypair.sign(message); + // FALCON-1024 signatures are ~1330 bytes + assert!(signature.len() <= 1500); + + assert!(keypair.public_key().verify(message, &signature).is_ok()); + } + + #[test] + fn test_invalid_signature_fails() { + let keypair = FalconKeypair::generate(FalconVariant::Falcon512); + 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!(FalconVariant::Falcon512.signature_size(), 690); + assert_eq!(FalconVariant::Falcon512.public_key_size(), 897); + assert_eq!(FalconVariant::Falcon512.secret_key_size(), 1281); + + assert_eq!(FalconVariant::Falcon1024.signature_size(), 1330); + assert_eq!(FalconVariant::Falcon1024.public_key_size(), 1793); + } + + #[test] + fn test_security_levels() { + assert_eq!(FalconVariant::Falcon512.security_level(), 128); + assert_eq!(FalconVariant::Falcon1024.security_level(), 256); + } + + #[test] + fn test_bandwidth_savings() { + let savings = comparison::bandwidth_savings_percent(); + // FALCON-512 should save ~79% bandwidth vs Dilithium3 + assert!(savings > 75.0); + assert!(savings < 85.0); + } +} diff --git a/crates/synor-crypto/src/lib.rs b/crates/synor-crypto/src/lib.rs index 89a728b..703a06c 100644 --- a/crates/synor-crypto/src/lib.rs +++ b/crates/synor-crypto/src/lib.rs @@ -87,16 +87,29 @@ #![allow(dead_code)] +pub mod falcon; pub mod kdf; pub mod keypair; pub mod mnemonic; pub mod signature; +pub mod sphincs; pub use kdf::{derive_key, DeriveKeyError}; pub use keypair::{Ed25519Keypair, HybridKeypair, PublicKey, SecretKey}; pub use mnemonic::{Mnemonic, MnemonicError}; pub use signature::{HybridSignature, Signature, SignatureError}; +// FIPS 205 (SLH-DSA) - Hash-based backup signatures +pub use sphincs::{ + SphincsError, SphincsKeypair, SphincsPublicKey, SphincsSecretKey, SphincsSignature, + SphincsVariant, +}; + +// FIPS 206 (FN-DSA) - Compact lattice signatures +pub use falcon::{ + FalconError, FalconKeypair, FalconPublicKey, FalconSecretKey, FalconSignature, FalconVariant, +}; + /// Re-export common types pub use synor_types::{Address, Hash256, Network}; diff --git a/crates/synor-crypto/src/sphincs.rs b/crates/synor-crypto/src/sphincs.rs new file mode 100644 index 0000000..12609fe --- /dev/null +++ b/crates/synor-crypto/src/sphincs.rs @@ -0,0 +1,508 @@ +//! 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)] +pub enum SphincsVariant { + /// 128-bit security, small signatures (~7.8KB) + 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, + } + } +} + +impl Default for SphincsVariant { + fn default() -> Self { + SphincsVariant::Shake128s + } +} + +/// 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). +#[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 + } +} + +/// 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] + 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); + } +}