SLH-DSA (SPHINCS+) Security-Focused API Documentation
Version: 1.0
Last Updated: 2025-08-28
Security Classification: PUBLIC
Author: MetaMUI Security Team
Overview
SLH-DSA (Stateless Hash-Based Digital Signature Algorithm), formerly known as SPHINCS+, is a post-quantum digital signature scheme standardized by NIST. Unlike other signature schemes, SLH-DSA’s security relies solely on the security of hash functions rather than number-theoretic problems, making it quantum-resistant by design. It is stateless, eliminating the state management complexities of other hash-based schemes.
Security Level: Multiple parameter sets (128, 192, 256-bit)
Public Key Size: 32-64 bytes (depending on parameters)
Private Key Size: 64-128 bytes (depending on parameters)
Signature Size: 8-50 KB (depending on parameters and optimization)
Algorithm Family: Hash-based signatures
Security Warnings ⚠️
- Signature Size: Significantly larger than classical signatures (8-50 KB)
- Performance Trade-off: Size vs speed parameter sets available
- Stateless Design: No state synchronization issues, safe for concurrent use
- Hash Function Dependency: Security relies entirely on hash function strength
- Deterministic Option: Available for reproducible signatures
- No Key Recovery: Lost keys cannot be recovered from signatures
Parameter Sets
| Parameter Set | Security Level | Sig Size | Speed | Public Key | Private Key |
|---|---|---|---|---|---|
| SLH-DSA-128s | 128-bit | 8 KB | Slow | 32 bytes | 64 bytes |
| SLH-DSA-128f | 128-bit | 17 KB | Fast | 32 bytes | 64 bytes |
| SLH-DSA-192s | 192-bit | 16 KB | Slow | 48 bytes | 96 bytes |
| SLH-DSA-192f | 192-bit | 35 KB | Fast | 48 bytes | 96 bytes |
| SLH-DSA-256s | 256-bit | 30 KB | Slow | 64 bytes | 128 bytes |
| SLH-DSA-256f | 256-bit | 50 KB | Fast | 64 bytes | 128 bytes |
s = small signatures (slower), f = fast signing (larger signatures)
API Functions
slhdsa_keygen(parameter_set) -> (PublicKey, PrivateKey)
Security Contract:
- Preconditions:
- Valid parameter set selected
- Sufficient entropy available
- Hash function implementation secure
- Postconditions:
- Returns stateless key pair
- Keys can be used immediately
- No state initialization required
Attack Resistance: | Attack Type | Protected | Notes | |————-|———–|——-| | Quantum (Shor’s) | ✅ | Hash-based security | | Quantum (Grover’s) | ✅ | Security level maintained | | Collision Attacks | ✅ | Multiple hash function calls | | Preimage Attacks | ✅ | One-way hash security | | State Attacks | ✅ | Completely stateless | | Fault Injection | ⚠️ | Implementation dependent |
Security Requirements:
- Use cryptographically secure randomness
- Protect key generation environment
- Validate parameter set selection
- Secure storage for private keys
Secure Usage Example (Python):
from metamui_crypto import SLHDSA
import secrets
import hashlib
from enum import Enum
class SLHDSAParameterSet(Enum):
"""Security parameter sets"""
LEVEL1_FAST = "slhdsa-128f" # Fast, larger signatures
LEVEL1_SMALL = "slhdsa-128s" # Small signatures, slower
LEVEL3_FAST = "slhdsa-192f" # Balanced security/performance
LEVEL3_SMALL = "slhdsa-192s" # Smaller signatures
LEVEL5_FAST = "slhdsa-256f" # Maximum security, fast
LEVEL5_SMALL = "slhdsa-256s" # Maximum security, small
class SecureSLHDSA:
"""Secure SLH-DSA implementation with best practices"""
def __init__(self, parameter_set: SLHDSAParameterSet = SLHDSAParameterSet.LEVEL3_FAST):
self.slhdsa = SLHDSA(parameter_set.value)
self.parameter_set = parameter_set
def generate_keypair(self, deterministic_seed: bytes = None) -> tuple:
"""Generate SLH-DSA key pair"""
if deterministic_seed:
# Deterministic generation for key recovery
if len(deterministic_seed) < 48:
raise ValueError("Seed must be at least 48 bytes")
# Expand seed with KDF
expanded = hashlib.shake_256(
b"SLH-DSA-KEYGEN-v1:" + deterministic_seed
).digest(96)
public_key, private_key = self.slhdsa.keygen_deterministic(expanded)
# Clear sensitive data
expanded = bytes(96)
else:
# Random generation
entropy = secrets.token_bytes(96)
public_key, private_key = self.slhdsa.keygen_from_seed(entropy)
entropy = bytes(96)
# Validate generated keys
self._validate_keypair(public_key, private_key)
return public_key, private_key
def _validate_keypair(self, public_key: bytes, private_key: bytes):
"""Validate key pair integrity"""
# Test signature generation and verification
test_message = b"SLH-DSA-KEY-VALIDATION"
test_sig = self.slhdsa.sign(test_message, private_key)
if not self.slhdsa.verify(test_message, test_sig, public_key):
raise ValueError("Key pair validation failed")
Secure Usage Example (Rust):
use metamui_slhdsa::{
keypair, sign, verify,
ParameterSet, PublicKey, SecretKey, Signature
};
use zeroize::Zeroize;
pub enum SecurityLevel {
Fast128, // 17KB signatures, fast
Small128, // 8KB signatures, slower
Fast192, // 35KB signatures, balanced
Small192, // 16KB signatures, balanced
Fast256, // 50KB signatures, maximum security
Small256, // 30KB signatures, maximum security
}
pub struct SecureSLHDSA {
parameter_set: ParameterSet,
}
impl SecureSLHDSA {
pub fn new(level: SecurityLevel) -> Self {
let parameter_set = match level {
SecurityLevel::Fast128 => ParameterSet::SLHDSA128f,
SecurityLevel::Small128 => ParameterSet::SLHDSA128s,
SecurityLevel::Fast192 => ParameterSet::SLHDSA192f,
SecurityLevel::Small192 => ParameterSet::SLHDSA192s,
SecurityLevel::Fast256 => ParameterSet::SLHDSA256f,
SecurityLevel::Small256 => ParameterSet::SLHDSA256s,
};
Self { parameter_set }
}
pub fn generate_keypair(&self) -> Result<(PublicKey, SecretKey), Error> {
// Generate with system randomness
let (pk, sk) = keypair(&self.parameter_set)?;
// Keys are stateless - no initialization needed
Ok((pk, sk))
}
pub fn generate_deterministic(&self, seed: &[u8]) -> Result<(PublicKey, SecretKey), Error> {
// Deterministic generation for reproducibility
if seed.len() < 48 {
return Err(Error::InvalidSeedLength);
}
let (pk, sk) = keypair_from_seed(&self.parameter_set, seed)?;
Ok((pk, sk))
}
}
slhdsa_sign(Message, PrivateKey) -> Signature
Security Contract:
- Preconditions:
- Valid private key
- Message of any length
- No state requirements
- Postconditions:
- Returns valid signature
- Signature verifiable with public key
- No state updates needed
Attack Resistance: | Attack Type | Protected | Notes | |————-|———–|——-| | Forgery | ✅ | Hash-based security | | Quantum Attacks | ✅ | No algebraic structure | | Replay Attacks | ✅ | Include nonce/timestamp | | State Disclosure | ✅ | No state to disclose | | Collision Attacks | ✅ | Multiple hash layers | | Side-Channel | ⚠️ | Implementation dependent |
Security Requirements:
- Use randomized or deterministic signing as appropriate
- Protect private key during signing
- Clear sensitive intermediates
- Consider message preprocessing
Common Mistakes:
# ❌ INSECURE: Signing without context
signature = slhdsa_sign(message, private_key)
# ❌ INSECURE: Not handling large messages properly
huge_message = open('large_file.bin', 'rb').read() # Could be GB in size
signature = slhdsa_sign(huge_message, private_key) # Memory issues
# ✅ SECURE: Proper context and streaming
def secure_sign(message: bytes, private_key: bytes, context: str = "") -> bytes:
"""Sign with proper context and handling"""
# Add context to prevent cross-protocol attacks
contextualized = hashlib.sha3_256(
context.encode() + b":" + message
).digest()
# Sign the hash for large messages
if len(message) > 1024 * 1024: # 1MB threshold
message_hash = hashlib.shake_256(message).digest(64)
signature = slhdsa_sign_hash(message_hash, private_key)
else:
signature = slhdsa_sign(contextualized, private_key)
return signature
slhdsa_verify(Message, Signature, PublicKey) -> bool
Security Contract:
- Preconditions:
- Well-formed inputs (may be adversarial)
- No state requirements
- Constant-time execution desired
- Postconditions:
- Returns true only for valid signatures
- No information leakage
- No state modifications
Attack Resistance: | Attack Type | Protected | Notes | |————-|———–|——-| | Signature Forgery | ✅ | Computationally infeasible | | Malleability | ✅ | Unique signature format | | Timing Attacks | ⚠️ | Implementation dependent | | Fault Attacks | ⚠️ | Hardware dependent | | Resource Exhaustion | ⚠️ | Large signature processing |
Security Requirements:
- Validate input sizes before processing
- Implement timeout for verification
- Use constant-time comparisons where possible
- Handle malformed signatures gracefully
Secure Usage Example:
def secure_verify(message: bytes,
signature: bytes,
public_key: bytes,
context: str = "",
timeout_ms: int = 1000) -> bool:
"""Secure signature verification with bounds checking"""
import signal
import time
# Input validation
if len(public_key) not in [32, 48, 64]:
return False
if len(signature) < 1000 or len(signature) > 50000:
return False
# Set timeout for verification
def timeout_handler(signum, frame):
raise TimeoutError("Verification timeout")
signal.signal(signal.SIGALRM, timeout_handler)
signal.setitimer(signal.ITIMER_REAL, timeout_ms / 1000.0)
try:
# Add context
if context:
contextualized = hashlib.sha3_256(
context.encode() + b":" + message
).digest()
else:
contextualized = message
# Verify with timeout protection
result = slhdsa_verify(contextualized, signature, public_key)
# Cancel timeout
signal.setitimer(signal.ITIMER_REAL, 0)
return result
except TimeoutError:
# Verification took too long - possible DoS
log_security_event("SLH-DSA verification timeout")
return False
except Exception as e:
# Any error results in verification failure
log_security_event(f"SLH-DSA verification error: {e}")
return False
Security Best Practices
1. Parameter Set Selection
def select_parameter_set(use_case: str) -> SLHDSAParameterSet:
"""Select appropriate parameter set for use case"""
if use_case == "iot_device":
# Small signatures for bandwidth-constrained devices
return SLHDSAParameterSet.LEVEL1_SMALL
elif use_case == "high_volume_api":
# Fast signing for high throughput
return SLHDSAParameterSet.LEVEL3_FAST
elif use_case == "long_term_archive":
# Maximum security for long-term signatures
return SLHDSAParameterSet.LEVEL5_SMALL
elif use_case == "firmware_signing":
# Balanced for software distribution
return SLHDSAParameterSet.LEVEL3_SMALL
else:
# Default to balanced option
return SLHDSAParameterSet.LEVEL3_FAST
2. Hybrid Signing for Transition
class HybridSigner:
"""Combine classical and post-quantum signatures"""
def __init__(self):
self.slhdsa = SLHDSA("slhdsa-192f")
self.ecdsa = ECDSA("P-256")
def hybrid_sign(self, message: bytes,
slhdsa_sk: bytes,
ecdsa_sk: bytes) -> bytes:
"""Create hybrid signature for transition period"""
# Hash message once
message_hash = hashlib.sha3_256(message).digest()
# Generate both signatures
pq_sig = self.slhdsa.sign(message_hash, slhdsa_sk)
classical_sig = self.ecdsa.sign(message_hash, ecdsa_sk)
# Combine signatures
hybrid_sig = {
'version': 1,
'pq_algorithm': 'SLH-DSA-192f',
'classical_algorithm': 'ECDSA-P256',
'pq_signature': pq_sig.hex(),
'classical_signature': classical_sig.hex(),
'message_hash': message_hash.hex()
}
return json.dumps(hybrid_sig).encode()
def hybrid_verify(self, message: bytes,
hybrid_sig: bytes,
slhdsa_pk: bytes,
ecdsa_pk: bytes) -> bool:
"""Verify hybrid signature"""
sig_data = json.loads(hybrid_sig)
# Verify message hash
message_hash = hashlib.sha3_256(message).digest()
if message_hash.hex() != sig_data['message_hash']:
return False
# Verify both signatures
pq_valid = self.slhdsa.verify(
message_hash,
bytes.fromhex(sig_data['pq_signature']),
slhdsa_pk
)
classical_valid = self.ecdsa.verify(
message_hash,
bytes.fromhex(sig_data['classical_signature']),
ecdsa_pk
)
# Both must verify
return pq_valid and classical_valid
3. Stateless Advantage Usage
class StatelessSigningService:
"""Leverage stateless nature for scalability"""
def __init__(self, private_key: bytes):
self.slhdsa = SLHDSA("slhdsa-192f")
# Store encrypted private key
self.encrypted_sk = self.encrypt_key(private_key)
def parallel_sign_batch(self, messages: list) -> list:
"""Sign multiple messages in parallel"""
from concurrent.futures import ThreadPoolExecutor
# Decrypt key once
private_key = self.decrypt_key(self.encrypted_sk)
# Sign in parallel - safe due to stateless design
with ThreadPoolExecutor(max_workers=10) as executor:
signatures = list(executor.map(
lambda msg: self.slhdsa.sign(msg, private_key),
messages
))
# Clear key
private_key = bytes(len(private_key))
return signatures
def distributed_signing(self, message: bytes) -> bytes:
"""Safe for distributed systems - no state coordination"""
# Can run on multiple nodes simultaneously
# No risk of state collision
private_key = self.decrypt_key(self.encrypted_sk)
signature = self.slhdsa.sign(message, private_key)
private_key = bytes(len(private_key))
return signature
4. Message Preprocessing
def preprocess_message(message: bytes,
metadata: dict = None) -> bytes:
"""Preprocess message for signing"""
# Create signing structure
signing_input = {
'version': 1,
'timestamp': time.time(),
'message_hash': hashlib.sha3_256(message).hexdigest()
}
# Add metadata if provided
if metadata:
signing_input['metadata'] = metadata
# For large messages, sign hash
if len(message) > 10 * 1024 * 1024: # 10MB
signing_input['large_message'] = True
signing_input['message_size'] = len(message)
else:
signing_input['message'] = message.hex()
# Canonicalize for consistent signing
canonical = json.dumps(signing_input, sort_keys=True)
return canonical.encode()
5. Side-Channel Protection
// Constant-time operations for SLH-DSA
use subtle::{ConditionallySelectable, ConstantTimeEq};
impl SideChannelProtectedSLHDSA {
fn constant_time_verify(&self,
message: &[u8],
signature: &[u8],
public_key: &[u8]) -> bool {
// Process all signature components
let mut result = 1u8;
// Verify each layer of the hypertree
for i in 0..self.layers {
let layer_valid = self.verify_layer_constant_time(i, signature);
result &= layer_valid;
}
// Final comparison in constant time
result.ct_eq(&1u8).into()
}
fn randomized_signing(&self,
message: &[u8],
secret_key: &[u8]) -> Vec<u8> {
// Add randomization to prevent DPA
let mut rng = thread_rng();
let blinding = rng.gen::<[u8; 32]>();
// Sign with blinding
let blinded_sig = self.sign_with_blinding(message, secret_key, &blinding);
// Clear blinding
blinding.zeroize();
blinded_sig
}
}
Security Auditing
Verification Checklist
- Hash function implementations validated
- Tree traversal algorithms verified
- Winternitz chains correctly implemented
- FORS construction validated
- Hypertree structure correct
- Parameter sets properly configured
- Test vectors from NIST pass
- Stateless operation confirmed
- Memory usage within bounds
- Performance metrics acceptable
Common Vulnerabilities
- Incorrect tree construction: Wrong indices or heights
- Hash function weaknesses: Using broken hash functions
- Parameter confusion: Mixing parameter sets
- Memory exhaustion: Not limiting signature verification
- Implementation bugs: Incorrect Winternitz chains
- Timing leaks: Variable-time hash operations
Security Monitoring
class SLHDSASecurityMonitor:
def __init__(self):
self.metrics = {
'signatures_created': 0,
'signatures_verified': 0,
'average_sig_size': 0,
'parameter_sets_used': {}
}
def monitor_signing(self, parameter_set: str, sig_size: int):
"""Monitor signing operations"""
self.metrics['signatures_created'] += 1
self.metrics['parameter_sets_used'][parameter_set] = \
self.metrics['parameter_sets_used'].get(parameter_set, 0) + 1
# Update average
n = self.metrics['signatures_created']
avg = self.metrics['average_sig_size']
self.metrics['average_sig_size'] = (avg * (n-1) + sig_size) / n
# Alert on anomalies
if sig_size > 50000:
self.alert("Unusually large signature detected")
def monitor_verification(self, duration_ms: float):
"""Monitor verification performance"""
self.metrics['signatures_verified'] += 1
# Alert on slow verification (possible DoS)
if duration_ms > 1000:
self.alert("Slow signature verification detected")
Performance Metrics
| Operation | SLH-DSA-128f | SLH-DSA-192s | SLH-DSA-256f |
|---|---|---|---|
| Key Gen | 10 ms | 15 ms | 40 ms |
| Sign | 5 ms | 120 ms | 30 ms |
| Verify | 3 ms | 5 ms | 8 ms |
| Sig Size | 17 KB | 16 KB | 50 KB |
Measurements on modern x86-64 CPU
Compliance and Standards
- NIST FIPS 205: Stateless Hash-Based Digital Signature Standard
- Korean PQC Standards: Selected for KPQC standardization
- IETF RFC (draft): Internet standards track
- CNSA 2.0: Approved for quantum-resistant transitions
Security Analysis
Threat Model: SLH-DSA Threat Model
The comprehensive threat analysis covers:
- Algorithm-specific attack vectors
- Implementation vulnerabilities
- Side-channel considerations
- Quantum resistance analysis (where applicable)
- Deployment recommendations
For complete security analysis and risk assessment, see the dedicated threat model documentation.
References
- NIST FIPS 205: SLH-DSA Standard
- SPHINCS+ Specification
- Hash-Based Signatures Security Analysis
- Stateless Hash-Based Signatures
- Korean PQC Standards (KPQC)
- Post-Quantum Signature Schemes
Related Documentation
- Dilithium Security API - Lattice-based signatures
- Falcon-512 Security API - Compact lattice signatures
- Hash Function Security
- Signature Scheme Comparison
- Stateless Cryptography Guide