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.
This commit is contained in:
parent
e2a6a10bee
commit
9414ef5d99
5 changed files with 1034 additions and 5 deletions
|
|
@ -69,8 +69,10 @@ rand = "0.8"
|
||||||
rand_core = "0.6"
|
rand_core = "0.6"
|
||||||
|
|
||||||
# Cryptography - Post-Quantum (NIST standards)
|
# Cryptography - Post-Quantum (NIST standards)
|
||||||
pqcrypto-dilithium = "0.5"
|
pqcrypto-dilithium = "0.5" # FIPS 204 (ML-DSA) - primary signatures
|
||||||
pqcrypto-kyber = "0.8"
|
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"
|
pqcrypto-traits = "0.3"
|
||||||
|
|
||||||
# Hashing
|
# Hashing
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,11 @@ x25519-dalek = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
rand_core = { workspace = true }
|
rand_core = { workspace = true }
|
||||||
|
|
||||||
# Post-quantum cryptography
|
# Post-quantum cryptography (NIST FIPS 203-206)
|
||||||
pqcrypto-dilithium = { workspace = true }
|
pqcrypto-dilithium = { workspace = true } # FIPS 204 (ML-DSA)
|
||||||
pqcrypto-kyber = { workspace = true }
|
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 }
|
pqcrypto-traits = { workspace = true }
|
||||||
|
|
||||||
# Hashing
|
# Hashing
|
||||||
|
|
|
||||||
504
crates/synor-crypto/src/falcon.rs
Normal file
504
crates/synor-crypto/src/falcon.rs
Normal file
|
|
@ -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<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FalconPublicKey {
|
||||||
|
/// Creates a public key from bytes.
|
||||||
|
pub fn from_bytes(variant: FalconVariant, bytes: &[u8]) -> Result<Self, FalconError> {
|
||||||
|
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<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FalconSecretKey {
|
||||||
|
/// Creates a secret key from bytes.
|
||||||
|
pub fn from_bytes(variant: FalconVariant, bytes: &[u8]) -> Result<Self, FalconError> {
|
||||||
|
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<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FalconSignature {
|
||||||
|
/// Creates a signature from bytes.
|
||||||
|
pub fn from_bytes(variant: FalconVariant, bytes: &[u8]) -> Result<Self, FalconError> {
|
||||||
|
// 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<Self, FalconError> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -87,16 +87,29 @@
|
||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
pub mod falcon;
|
||||||
pub mod kdf;
|
pub mod kdf;
|
||||||
pub mod keypair;
|
pub mod keypair;
|
||||||
pub mod mnemonic;
|
pub mod mnemonic;
|
||||||
pub mod signature;
|
pub mod signature;
|
||||||
|
pub mod sphincs;
|
||||||
|
|
||||||
pub use kdf::{derive_key, DeriveKeyError};
|
pub use kdf::{derive_key, DeriveKeyError};
|
||||||
pub use keypair::{Ed25519Keypair, HybridKeypair, PublicKey, SecretKey};
|
pub use keypair::{Ed25519Keypair, HybridKeypair, PublicKey, SecretKey};
|
||||||
pub use mnemonic::{Mnemonic, MnemonicError};
|
pub use mnemonic::{Mnemonic, MnemonicError};
|
||||||
pub use signature::{HybridSignature, Signature, SignatureError};
|
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
|
/// Re-export common types
|
||||||
pub use synor_types::{Address, Hash256, Network};
|
pub use synor_types::{Address, Hash256, Network};
|
||||||
|
|
||||||
|
|
|
||||||
508
crates/synor-crypto/src/sphincs.rs
Normal file
508
crates/synor-crypto/src/sphincs.rs
Normal file
|
|
@ -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<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SphincsPublicKey {
|
||||||
|
/// Creates a public key from bytes.
|
||||||
|
pub fn from_bytes(variant: SphincsVariant, bytes: &[u8]) -> Result<Self, SphincsError> {
|
||||||
|
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<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SphincsSecretKey {
|
||||||
|
/// Creates a secret key from bytes.
|
||||||
|
pub fn from_bytes(variant: SphincsVariant, bytes: &[u8]) -> Result<Self, SphincsError> {
|
||||||
|
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<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SphincsSignature {
|
||||||
|
/// Creates a signature from bytes.
|
||||||
|
pub fn from_bytes(variant: SphincsVariant, bytes: &[u8]) -> Result<Self, SphincsError> {
|
||||||
|
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<Self, SphincsError> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue