SLH-DSA
Header: #include <cryptopp/slhdsa.h> | Namespace: CryptoPP
Since: cryptopp-modern 2026.3.0
Thread Safety: Not thread-safe if the same instance is shared across threads; use one instance per thread, or guard shared instances with a lock
SLH-DSA (Stateless Hash-Based Digital Signature Algorithm) is a NIST post-quantum cryptographic standard (FIPS 205) based on SPHINCS+. It provides digital signatures whose security relies solely on hash function security, making it a conservative choice for long-term security against quantum computers.
Key Features
- Post-quantum secure - Resistant to attacks by quantum computers
- Stateless - No state management required (unlike XMSS/LMS)
- Conservative security - Based only on hash function assumptions
- 12 parameter sets - SHA-256 and SHAKE variants at 3 security levels
- Two variants per level - “fast” (f) and “small” (s)
Parameter Sets
SHA-256 Based
| Parameter Set | Security Level | Public Key | Secret Key | Signature |
|---|---|---|---|---|
| SLH-DSA-SHA2-128f | 1 (128-bit) | 32 bytes | 64 bytes | 17,088 bytes |
| SLH-DSA-SHA2-128s | 1 (128-bit) | 32 bytes | 64 bytes | 7,856 bytes |
| SLH-DSA-SHA2-192f | 3 (192-bit) | 48 bytes | 96 bytes | 35,664 bytes |
| SLH-DSA-SHA2-192s | 3 (192-bit) | 48 bytes | 96 bytes | 16,224 bytes |
| SLH-DSA-SHA2-256f | 5 (256-bit) | 64 bytes | 128 bytes | 49,856 bytes |
| SLH-DSA-SHA2-256s | 5 (256-bit) | 64 bytes | 128 bytes | 29,792 bytes |
Type names follow the header naming (e.g., SLHDSA_SHA2_128f corresponds to “SLH-DSA-SHA2-128f”).
SHAKE Based
SHAKE parameter sets have identical sizes to their SHA-256 counterparts:
SLHDSA_SHAKE_128f,SLHDSA_SHAKE_128sSLHDSA_SHAKE_192f,SLHDSA_SHAKE_192sSLHDSA_SHAKE_256f,SLHDSA_SHAKE_256s
Variant guide:
- “f” (fast) - Larger signatures, faster signing
- “s” (small) - Smaller signatures, slower signing
Quick Example
#include <cryptopp/slhdsa.h>
#include <cryptopp/osrng.h>
#include <cryptopp/secblock.h>
#include <iostream>
#include <string>
int main() {
using namespace CryptoPP;
AutoSeededRandomPool rng;
// Generate key pair (using SHA2-128f - recommended default)
SLHDSASigner<SLHDSA_SHA2_128f> signer(rng);
SLHDSAVerifier<SLHDSA_SHA2_128f> verifier(signer);
// Sign a message
std::string message = "Hello, Post-Quantum World!";
SecByteBlock signature(signer.SignatureLength());
size_t sigLen = signer.SignMessage(rng,
(const byte*)message.data(), message.size(),
signature.begin());
std::cout << "Signature length: " << sigLen << " bytes" << std::endl;
// Verify the signature
bool valid = verifier.VerifyMessage(
(const byte*)message.data(), message.size(),
signature.begin(), sigLen);
std::cout << "Signature valid: " << (valid ? "YES" : "NO") << std::endl;
return 0;
}Usage Guidelines
Do:
- Use SLH-DSA for long-term security against quantum threats
- Start with
SLHDSA_SHA2_128ffor most applications (good balance) - Store private keys securely (e.g., OS key store, HSM);
SecByteBlockhelps with in-memory buffers but is not secure storage - Verify signatures before trusting signed data
- Consider SLH-DSA as a conservative backup to lattice-based schemes (ML-DSA)
Avoid:
- Using SLH-DSA for high-throughput applications (signatures are large and slow)
- Using 256-bit parameter sets unless you truly need NIST Level 5 security
- Storing signatures inline with small data (signatures are 8–50 KB; plan storage/bandwidth accordingly)
- Using for real-time or embedded systems with memory constraints
- Streaming very large messages without sufficient memory (this implementation buffers full messages)
When to Use SLH-DSA
Use SLH-DSA when:
- You need the most conservative security assumptions (hash-only)
- Long-term document signing (10+ year validity)
- Code signing for software with long support cycles
- Certificate signing for CAs
- As a backup signature in hybrid schemes
- You distrust lattice-based cryptography assumptions
Consider ML-DSA instead when:
- High-throughput applications need smaller signatures
- Size-constrained environments (3.3 KB vs 17+ KB signatures)
- Real-time signing requirements
- Lattice security assumptions are acceptable
Consider Ed25519 instead when:
- Quantum computers are not a concern for your threat model
- Minimum signature size is critical (64 bytes)
- Maximum verification speed is required
Class: SLHDSASigner
Sign messages with SLH-DSA private key.
Template Parameter
template <class PARAMS>
struct SLHDSASigner;PARAMS is one of:
SLHDSA_SHA2_128f,SLHDSA_SHA2_128sSLHDSA_SHA2_192f,SLHDSA_SHA2_192sSLHDSA_SHA2_256f,SLHDSA_SHA2_256sSLHDSA_SHAKE_128f,SLHDSA_SHAKE_128sSLHDSA_SHAKE_192f,SLHDSA_SHAKE_192sSLHDSA_SHAKE_256f,SLHDSA_SHAKE_256s
Constants
CRYPTOPP_CONSTANT(SECRET_KEYLENGTH = PARAMS::SECRET_KEY_SIZE); // 64-128 bytes
CRYPTOPP_CONSTANT(PUBLIC_KEYLENGTH = PARAMS::PUBLIC_KEY_SIZE); // 32-64 bytes
CRYPTOPP_CONSTANT(SIGNATURE_LENGTH = PARAMS::SIGNATURE_SIZE); // 7,856-49,856 bytes
Constructors
Default Constructor
SLHDSASigner();Create an uninitialized signer. Must call AccessPrivateKey().GenerateRandom() before use.
Constructor with RNG
SLHDSASigner(RandomNumberGenerator& rng);Generate a new key pair.
Example:
AutoSeededRandomPool rng;
SLHDSASigner<SLHDSA_SHA2_128f> signer(rng);Constructor with Private Key
SLHDSASigner(const byte* privateKey, size_t privateKeyLen);Load an existing private key.
Example:
SecByteBlock privateKey(64);
// ... load key from storage ...
SLHDSASigner<SLHDSA_SHA2_128f> signer(privateKey.begin(), privateKey.size());Methods
SignMessage
size_t SignMessage(RandomNumberGenerator& rng,
const byte* message, size_t messageLen,
byte* signature) const;Sign a message. This is a convenience method inherited from PK_Signer. The lower-level SignAndRestart() is also available.
Parameters:
rng- Random number generator (used for randomized signing)message- Message to signmessageLen- Message lengthsignature- Output buffer (must be at leastSignatureLength()bytes)
Returns: Signature length
Example:
SLHDSASigner<SLHDSA_SHA2_128f> signer(rng);
std::string message = "Document to sign";
SecByteBlock signature(signer.SignatureLength());
size_t sigLen = signer.SignMessage(rng,
(const byte*)message.data(), message.size(),
signature.begin());SignatureLength
size_t SignatureLength() const;Get the signature size in bytes.
AccessPrivateKey / GetPrivateKey
PrivateKey& AccessPrivateKey();
const PrivateKey& GetPrivateKey() const;Access the private key object for key generation or export.
GetKey / AccessKey
const SLHDSAPrivateKey<PARAMS>& GetKey() const;
SLHDSAPrivateKey<PARAMS>& AccessKey();Get the private key with full type information.
Example:
// Get public key from signer
const byte* pkBytes = signer.GetKey().GetPublicKeyBytePtr();
size_t pkLen = signer.GetKey().GetPublicKeySize();AlgorithmName
std::string AlgorithmName() const;Get the algorithm name (e.g., “SLH-DSA-SHA2-128f”). Useful for logging and runtime identification.
Class: SLHDSAVerifier
Verify SLH-DSA signatures.
Template Parameter
template <class PARAMS>
struct SLHDSAVerifier;Constants
CRYPTOPP_CONSTANT(PUBLIC_KEYLENGTH = PARAMS::PUBLIC_KEY_SIZE);
CRYPTOPP_CONSTANT(SIGNATURE_LENGTH = PARAMS::SIGNATURE_SIZE);Constructors
Default Constructor
SLHDSAVerifier();Create an uninitialized verifier. Must set public key before use.
Constructor with Public Key
SLHDSAVerifier(const byte* publicKey, size_t publicKeyLen);Load a public key for verification.
Example:
SecByteBlock publicKey(32);
// ... receive from sender ...
SLHDSAVerifier<SLHDSA_SHA2_128f> verifier(publicKey.begin(), publicKey.size());Constructor from Signer
SLHDSAVerifier(const SLHDSASigner<PARAMS>& signer);Extract public key from signer.
Example:
SLHDSASigner<SLHDSA_SHA2_128f> signer(rng);
SLHDSAVerifier<SLHDSA_SHA2_128f> verifier(signer);Methods
VerifyMessage
bool VerifyMessage(const byte* message, size_t messageLen,
const byte* signature, size_t signatureLen) const;Verify a signature on a message. This is a convenience method inherited from PK_Verifier. The lower-level VerifyAndRestart() is also available.
Parameters:
message- Message that was signedmessageLen- Message lengthsignature- Signature to verifysignatureLen- Signature length
Returns: true if valid, false if invalid
Example:
bool valid = verifier.VerifyMessage(
(const byte*)message.data(), message.size(),
signature.begin(), signature.size());
if (!valid) {
std::cerr << "Invalid signature!" << std::endl;
}AccessPublicKey / GetPublicKey
PublicKey& AccessPublicKey();
const PublicKey& GetPublicKey() const;Access the public key object to set or retrieve key bytes.
AccessKey / GetKey
SLHDSAPublicKey<PARAMS>& AccessKey();
const SLHDSAPublicKey<PARAMS>& GetKey() const;Access the public key with full type information.
AlgorithmName
std::string AlgorithmName() const;Get the algorithm name (e.g., “SLH-DSA-SHA2-128f”).
Class: SLHDSAPrivateKey
Holds the secret key material for signing.
Methods
GetAlgorithmID
OID GetAlgorithmID() const;Get the algorithm OID for this key type. Used in ASN.1/DER encoding.
GenerateRandom
void GenerateRandom(RandomNumberGenerator &rng, const NameValuePairs ¶ms);Generate a new random key pair.
Example:
SLHDSAPrivateKey<SLHDSA_SHA2_128f> privateKey;
privateKey.GenerateRandom(rng, g_nullNameValuePairs);GetPublicKeyBytePtr / GetPublicKeySize
const byte* GetPublicKeyBytePtr() const;
size_t GetPublicKeySize() const;Get the embedded public key.
GetPrivateKeyBytePtr / GetPrivateKeySize
const byte* GetPrivateKeyBytePtr() const;
size_t GetPrivateKeySize() const;Get the private key bytes.
Save / Load
void Save(BufferedTransformation &bt) const;
void Load(BufferedTransformation &bt);Serialize/deserialize in ASN.1 DER format (PKCS#8 OneAsymmetricKey).
Class: SLHDSAPublicKey
Holds the public key material for verification.
Methods
SetPublicKey
void SetPublicKey(const byte *key, size_t len);Set the public key bytes.
GetPublicKeyBytePtr / GetPublicKeySize
const byte* GetPublicKeyBytePtr() const;
size_t GetPublicKeySize() const;Get the public key bytes.
Save / Load
void Save(BufferedTransformation &bt) const;
void Load(BufferedTransformation &bt);Serialize/deserialize in ASN.1 DER format (X.509 SubjectPublicKeyInfo).
Key Serialization
Raw bytes are suitable for internal storage when you also store the parameter set alongside them. ASN.1 DER (PKCS#8/X.509) is the recommended interchange format for compatibility with other systems.
Saving Keys (Raw Bytes)
Requires: <cstring> for std::memcpy
// Generate key pair
SLHDSASigner<SLHDSA_SHA2_128f> signer(rng);
const auto& privKey = signer.GetKey();
// Save private key (KEEP SECURE!)
SecByteBlock privateKeyBytes(privKey.GetPrivateKeySize());
std::memcpy(privateKeyBytes.begin(),
privKey.GetPrivateKeyBytePtr(),
privateKeyBytes.size());
// Save public key (can be shared)
SecByteBlock publicKeyBytes(privKey.GetPublicKeySize());
std::memcpy(publicKeyBytes.begin(),
privKey.GetPublicKeyBytePtr(),
publicKeyBytes.size());Loading Keys (Raw Bytes)
// Load private key (for signing)
SLHDSASigner<SLHDSA_SHA2_128f> signer(
privateKeyBytes.begin(), privateKeyBytes.size());
// Load public key (for verification)
SLHDSAVerifier<SLHDSA_SHA2_128f> verifier(
publicKeyBytes.begin(), publicKeyBytes.size());Saving Keys (ASN.1 DER)
Requires: <cryptopp/files.h> for FileSink and FileSource
// Save private key to file
FileSink privFile("slhdsa_private.der");
signer.GetKey().Save(privFile);
// Save public key to file
FileSink pubFile("slhdsa_public.der");
SLHDSAPublicKey<SLHDSA_SHA2_128f> pubKey;
pubKey.SetPublicKey(signer.GetKey().GetPublicKeyBytePtr(),
signer.GetKey().GetPublicKeySize());
pubKey.Save(pubFile);Loading Keys (ASN.1 DER)
// Load private key from file
FileSource privFile("slhdsa_private.der", true);
SLHDSASigner<SLHDSA_SHA2_128f> signer;
signer.AccessKey().Load(privFile);
// Load public key from file
FileSource pubFile("slhdsa_public.der", true);
SLHDSAVerifier<SLHDSA_SHA2_128f> verifier;
verifier.AccessKey().Load(pubFile);Convenience Type Aliases
All 12 parameter sets have pre-defined signer and verifier types:
// SHA-256 based signers
typedef SLHDSASigner<SLHDSA_SHA2_128f> SLHDSA_SHA2_128f_Signer;
typedef SLHDSASigner<SLHDSA_SHA2_128s> SLHDSA_SHA2_128s_Signer;
typedef SLHDSASigner<SLHDSA_SHA2_192f> SLHDSA_SHA2_192f_Signer;
typedef SLHDSASigner<SLHDSA_SHA2_192s> SLHDSA_SHA2_192s_Signer;
typedef SLHDSASigner<SLHDSA_SHA2_256f> SLHDSA_SHA2_256f_Signer;
typedef SLHDSASigner<SLHDSA_SHA2_256s> SLHDSA_SHA2_256s_Signer;
// SHA-256 based verifiers
typedef SLHDSAVerifier<SLHDSA_SHA2_128f> SLHDSA_SHA2_128f_Verifier;
typedef SLHDSAVerifier<SLHDSA_SHA2_128s> SLHDSA_SHA2_128s_Verifier;
typedef SLHDSAVerifier<SLHDSA_SHA2_192f> SLHDSA_SHA2_192f_Verifier;
typedef SLHDSAVerifier<SLHDSA_SHA2_192s> SLHDSA_SHA2_192s_Verifier;
typedef SLHDSAVerifier<SLHDSA_SHA2_256f> SLHDSA_SHA2_256f_Verifier;
typedef SLHDSAVerifier<SLHDSA_SHA2_256s> SLHDSA_SHA2_256s_Verifier;
// SHAKE based (same pattern)
typedef SLHDSASigner<SLHDSA_SHAKE_128f> SLHDSA_SHAKE_128f_Signer;
typedef SLHDSASigner<SLHDSA_SHAKE_128s> SLHDSA_SHAKE_128s_Signer;
// ... etc
Example using convenience types:
SLHDSA_SHA2_128f_Signer signer(rng);
SLHDSA_SHA2_128f_Verifier verifier(signer);Choosing a Parameter Set
Recommended Defaults
| Use Case | Recommended Parameter Set |
|---|---|
| General purpose | SLHDSA_SHA2_128f |
| Smaller signatures | SLHDSA_SHA2_128s |
| Higher security | SLHDSA_SHA2_192f |
| Maximum security | SLHDSA_SHA2_256f |
| SHAKE preference | SLHDSA_SHAKE_128f |
Decision Factors
Choose “f” (fast) variant when:
- Signature generation time is important
- Storage/bandwidth is not severely constrained
- Signing happens infrequently
Choose “s” (small) variant when:
- Signature size is critical
- Many signatures need to be stored or transmitted
- Signing time is less important
Choose security level based on:
- Level 1 (128-bit): Adequate for most applications, matches AES-128
- Level 3 (192-bit): Higher security margin
- Level 5 (256-bit): Maximum security, required by some government standards
Indicative Performance
SLH-DSA is significantly slower than classical algorithms but provides quantum resistance with hash-only security assumptions. Performance varies heavily by CPU, compiler, and build settings—treat the following as illustrative only (not a benchmark) and measure in your target environment.
| Parameter Set | Sign Time | Verify Time | Signature Size |
|---|---|---|---|
| SLH-DSA-SHA2-128f | ~50 ms | ~3 ms | 17 KB |
| SLH-DSA-SHA2-128s | ~800 ms | ~5 ms | 7.7 KB |
| SLH-DSA-SHA2-192f | ~80 ms | ~4 ms | 35 KB |
| SLH-DSA-SHA2-256f | ~150 ms | ~6 ms | 49 KB |
If you need publishable numbers, benchmark the exact library version, CPU, and build flags.
Comparison with Other Signature Algorithms
| Algorithm | Public Key | Signature | Sign Speed | Quantum Safe |
|---|---|---|---|---|
| Ed25519 | 32 bytes | 64 bytes | Very fast | No |
| RSA-2048 | 256 bytes | 256 bytes | Slow | No |
| ECDSA P-256 | 64 bytes | 64 bytes | Fast | No |
| ML-DSA-65 | 1952 bytes | 3309 bytes | Medium | Yes |
| SLH-DSA-SHA2-128f | 32 bytes | 17 KB | Slow | Yes |
Security Considerations
Security Assumptions
SLH-DSA security relies only on:
- Hash function collision resistance
- Hash function preimage resistance
- Hash function second-preimage resistance
This is a conservative assumption compared to lattice-based schemes (ML-DSA) which rely on the hardness of lattice problems.
Security Levels (NIST Categories)
| Parameter | NIST Level | Equivalent Classical Security |
|---|---|---|
| 128-bit variants | Level 1 | AES-128 |
| 192-bit variants | Level 3 | AES-192 |
| 256-bit variants | Level 5 | AES-256 |
Security Notes
Stateless design - Unlike XMSS/LMS, no state management required. Safe to sign multiple messages without tracking.
Probabilistic signing - This implementation uses randomness during signing, so the same message will normally produce different signatures.
No state exhaustion - Unlike XMSS/LMS, there’s no per-signature state to track, so normal repeated use does not risk state-reuse failures.
Hash function choice - Both SHA-256 and SHAKE-256 variants are secure. SHA-256 may be faster on hardware with SHA extensions.
Thread Safety
Not thread-safe if the same instance is shared across threads. Use one instance per thread, or guard shared instances with a lock.
Object Identifiers (OIDs)
Each parameter set has a standardized NIST OID for use in ASN.1/DER encoding. OIDs are library-provided via accessors:
SHA-256 Variants
| Parameter Set | OID | Accessor |
|---|---|---|
| SLH-DSA-SHA2-128s | 2.16.840.1.101.3.4.3.20 | ASN1::id_slh_dsa_sha2_128s() |
| SLH-DSA-SHA2-128f | 2.16.840.1.101.3.4.3.21 | ASN1::id_slh_dsa_sha2_128f() |
| SLH-DSA-SHA2-192s | 2.16.840.1.101.3.4.3.22 | ASN1::id_slh_dsa_sha2_192s() |
| SLH-DSA-SHA2-192f | 2.16.840.1.101.3.4.3.23 | ASN1::id_slh_dsa_sha2_192f() |
| SLH-DSA-SHA2-256s | 2.16.840.1.101.3.4.3.24 | ASN1::id_slh_dsa_sha2_256s() |
| SLH-DSA-SHA2-256f | 2.16.840.1.101.3.4.3.25 | ASN1::id_slh_dsa_sha2_256f() |
SHAKE Variants
| Parameter Set | OID | Accessor |
|---|---|---|
| SLH-DSA-SHAKE-128s | 2.16.840.1.101.3.4.3.26 | ASN1::id_slh_dsa_shake_128s() |
| SLH-DSA-SHAKE-128f | 2.16.840.1.101.3.4.3.27 | ASN1::id_slh_dsa_shake_128f() |
| SLH-DSA-SHAKE-192s | 2.16.840.1.101.3.4.3.28 | ASN1::id_slh_dsa_shake_192s() |
| SLH-DSA-SHAKE-192f | 2.16.840.1.101.3.4.3.29 | ASN1::id_slh_dsa_shake_192f() |
| SLH-DSA-SHAKE-256s | 2.16.840.1.101.3.4.3.30 | ASN1::id_slh_dsa_shake_256s() |
| SLH-DSA-SHAKE-256f | 2.16.840.1.101.3.4.3.31 | ASN1::id_slh_dsa_shake_256f() |
Usage:
// Get OID from key object
OID oid = privateKey.GetAlgorithmID();
// Get OID statically from parameter set
OID oid = SLHDSA_SHA2_128f::StaticAlgorithmOID();
// Compare OIDs
if (oid == ASN1::id_slh_dsa_sha2_128f()) {
// SLH-DSA-SHA2-128f key
}Specification Compliance
This implementation targets NIST FIPS 205:
- Key Generation: Derives keys from random seed
- Signing: FORS + hypertree signature with randomization
- Verification: Reconstructs root from signature components
- Hash Functions: SHA-256 or SHAKE-256 as specified
- Parameters: All 12 parameter sets match FIPS 205 specifications
See Also
- FIPS 205 - SLH-DSA Standard
- ML-DSA - Lattice-based post-quantum signatures
- ML-KEM - Post-quantum key encapsulation
- X-Wing - Hybrid KEM (X25519 + ML-KEM-768)
- Ed25519 - Fast classical signatures
- PQC Overview - Algorithm comparison and guidance