Skip to content

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 SetSecurity LevelPublic KeySecret KeySignature
SLH-DSA-SHA2-128f1 (128-bit)32 bytes64 bytes17,088 bytes
SLH-DSA-SHA2-128s1 (128-bit)32 bytes64 bytes7,856 bytes
SLH-DSA-SHA2-192f3 (192-bit)48 bytes96 bytes35,664 bytes
SLH-DSA-SHA2-192s3 (192-bit)48 bytes96 bytes16,224 bytes
SLH-DSA-SHA2-256f5 (256-bit)64 bytes128 bytes49,856 bytes
SLH-DSA-SHA2-256s5 (256-bit)64 bytes128 bytes29,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_128s
  • SLHDSA_SHAKE_192f, SLHDSA_SHAKE_192s
  • SLHDSA_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_128f for most applications (good balance)
  • Store private keys securely (e.g., OS key store, HSM); SecByteBlock helps 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_128s
  • SLHDSA_SHA2_192f, SLHDSA_SHA2_192s
  • SLHDSA_SHA2_256f, SLHDSA_SHA2_256s
  • SLHDSA_SHAKE_128f, SLHDSA_SHAKE_128s
  • SLHDSA_SHAKE_192f, SLHDSA_SHAKE_192s
  • SLHDSA_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 sign
  • messageLen - Message length
  • signature - Output buffer (must be at least SignatureLength() 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 signed
  • messageLen - Message length
  • signature - Signature to verify
  • signatureLen - 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 &params);

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 CaseRecommended Parameter Set
General purposeSLHDSA_SHA2_128f
Smaller signaturesSLHDSA_SHA2_128s
Higher securitySLHDSA_SHA2_192f
Maximum securitySLHDSA_SHA2_256f
SHAKE preferenceSLHDSA_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 SetSign TimeVerify TimeSignature Size
SLH-DSA-SHA2-128f~50 ms~3 ms17 KB
SLH-DSA-SHA2-128s~800 ms~5 ms7.7 KB
SLH-DSA-SHA2-192f~80 ms~4 ms35 KB
SLH-DSA-SHA2-256f~150 ms~6 ms49 KB

If you need publishable numbers, benchmark the exact library version, CPU, and build flags.

Comparison with Other Signature Algorithms

AlgorithmPublic KeySignatureSign SpeedQuantum Safe
Ed2551932 bytes64 bytesVery fastNo
RSA-2048256 bytes256 bytesSlowNo
ECDSA P-25664 bytes64 bytesFastNo
ML-DSA-651952 bytes3309 bytesMediumYes
SLH-DSA-SHA2-128f32 bytes17 KBSlowYes

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)

ParameterNIST LevelEquivalent Classical Security
128-bit variantsLevel 1AES-128
192-bit variantsLevel 3AES-192
256-bit variantsLevel 5AES-256

Security Notes

  1. Stateless design - Unlike XMSS/LMS, no state management required. Safe to sign multiple messages without tracking.

  2. Probabilistic signing - This implementation uses randomness during signing, so the same message will normally produce different signatures.

  3. No state exhaustion - Unlike XMSS/LMS, there’s no per-signature state to track, so normal repeated use does not risk state-reuse failures.

  4. 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 SetOIDAccessor
SLH-DSA-SHA2-128s2.16.840.1.101.3.4.3.20ASN1::id_slh_dsa_sha2_128s()
SLH-DSA-SHA2-128f2.16.840.1.101.3.4.3.21ASN1::id_slh_dsa_sha2_128f()
SLH-DSA-SHA2-192s2.16.840.1.101.3.4.3.22ASN1::id_slh_dsa_sha2_192s()
SLH-DSA-SHA2-192f2.16.840.1.101.3.4.3.23ASN1::id_slh_dsa_sha2_192f()
SLH-DSA-SHA2-256s2.16.840.1.101.3.4.3.24ASN1::id_slh_dsa_sha2_256s()
SLH-DSA-SHA2-256f2.16.840.1.101.3.4.3.25ASN1::id_slh_dsa_sha2_256f()

SHAKE Variants

Parameter SetOIDAccessor
SLH-DSA-SHAKE-128s2.16.840.1.101.3.4.3.26ASN1::id_slh_dsa_shake_128s()
SLH-DSA-SHAKE-128f2.16.840.1.101.3.4.3.27ASN1::id_slh_dsa_shake_128f()
SLH-DSA-SHAKE-192s2.16.840.1.101.3.4.3.28ASN1::id_slh_dsa_shake_192s()
SLH-DSA-SHAKE-192f2.16.840.1.101.3.4.3.29ASN1::id_slh_dsa_shake_192f()
SLH-DSA-SHAKE-256s2.16.840.1.101.3.4.3.30ASN1::id_slh_dsa_shake_256s()
SLH-DSA-SHAKE-256f2.16.840.1.101.3.4.3.31ASN1::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