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 ⚠️

  1. Signature Size: Significantly larger than classical signatures (8-50 KB)
  2. Performance Trade-off: Size vs speed parameter sets available
  3. Stateless Design: No state synchronization issues, safe for concurrent use
  4. Hash Function Dependency: Security relies entirely on hash function strength
  5. Deterministic Option: Available for reproducible signatures
  6. 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:

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:

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:

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:

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:

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:

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

Common Vulnerabilities

  1. Incorrect tree construction: Wrong indices or heights
  2. Hash function weaknesses: Using broken hash functions
  3. Parameter confusion: Mixing parameter sets
  4. Memory exhaustion: Not limiting signature verification
  5. Implementation bugs: Incorrect Winternitz chains
  6. 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

Security Analysis

Threat Model: SLH-DSA Threat Model

The comprehensive threat analysis covers:

For complete security analysis and risk assessment, see the dedicated threat model documentation.

References

  1. NIST FIPS 205: SLH-DSA Standard
  2. SPHINCS+ Specification
  3. Hash-Based Signatures Security Analysis
  4. Stateless Hash-Based Signatures
  5. Korean PQC Standards (KPQC)
  6. Post-Quantum Signature Schemes