Skip to content

SLH-DSA

Header: #include <cryptopp/slhdsa.h> | Namespace: CryptoPP
Since: cryptopp-modern 2026.3.0; external pure interface since 2026.7.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.

Signature format changed in 2026.7.0. SLH-DSA now signs and verifies using the FIPS 205 external pure interface, so signatures interoperate with OpenSSL, X.509, and CMS. Signatures produced by 2026.3.0 through 2026.6.0 used the internal message form and will not verify under 2026.7.0, or vice versa. Re-sign stored SLH-DSA signatures with 2026.7.0.

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 to 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

ML-DSA is the better choice if you need smaller signatures (3.3 KB versus 17 KB or more), faster signing for real-time use, or your environment accepts lattice security assumptions.

If quantum attackers are not part of your threat model, Ed25519 remains the practical default: 64-byte signatures, fastest verification, no quantum resistance.


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());

Signing with a context

SignMessage signs with an empty context, which suits most callers. SLH-DSA signs over the FIPS 205 external message, and an optional context string (up to 255 bytes) can be bound into the signature. Set it on a message accumulator before signing:

SLHDSASigner<SLHDSA_SHA2_128f> signer(rng);

std::string context = "example-app-v1";
std::string message = "Document to sign";

member_ptr<PK_MessageAccumulator> acc(signer.NewSignatureAccumulator(rng));
static_cast<SLHDSA_MessageAccumulator<SLHDSA_SHA2_128f>&>(*acc)
    .SetContext((const byte*)context.data(), context.size());
acc->Update((const byte*)message.data(), message.size());

SecByteBlock signature(signer.SignatureLength());
signer.SignAndRestart(rng, *acc, signature.begin(), false);

The verifier must set the same context on its accumulator, otherwise verification fails.

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);

Loading Keys from a std::string (in-memory)

The StringSource overload trap that affects ML-KEM Save/Load applies here too. See the ML-KEM Loading Keys section for the safe forms. Substitute SLHDSAPublicKey or SLHDSAPrivateKey for the ML-KEM class names.


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