-
Notifications
You must be signed in to change notification settings - Fork 3
SLH DSA
Stateless Hash-Based Digital Signature Algorithm — formerly SPHINCS+,
standardized by NIST as
FIPS 205. SLH-DSA derives its
security entirely from hash-function assumptions — no lattices — which makes it
a conservative diversifier against any future lattice cryptanalysis.
pqcrypto supports all 12 parameter sets (both the SHAKE and SHA-2 hash
families) and is byte-exact on the 1,248-case official NIST ACVP sample corpus.
The trade-off is size and speed: tiny keys, but large signatures and slow
signing (especially the s sets). Verification stays reasonable. Favor SLH-DSA
for low-frequency, high-value, long-lived signatures — not per-request tokens.
import 'dart:convert';
import 'dart:typed_data';
import 'package:pqcrypto/pqcrypto.dart';
final params = SlhDsaParams.shake128f; // default; or any of the 12 sets
final (publicKey, secretKey) = SlhDsa.generateKeyPair(params);
final message = Uint8List.fromList(utf8.encode('message bytes'));
final ctx = Uint8List.fromList(utf8.encode('myapp/purpose/v1')); // <= 255 bytes
final signature = SlhDsa.sign(secretKey, message, params, context: ctx);
final ok = SlhDsa.verify(publicKey, message, signature, params, context: ctx);Signing is hedged by default. The context string (up to 255 bytes) gives
domain separation — use a distinct, versioned value per purpose, and pass the
same context to verify.
The small (s) sets sign in roughly tens of seconds in pure Dart, so they are
gated behind an explicit flag to prevent accidental use on a hot path:
final sig = SlhDsa.sign(
secretKey,
message,
SlhDsaParams.shake256s,
context: ctx,
allowSlowSigning: true, // required for every `s` set
);The fast (f) sets do not need this flag.
final signature = SlhDsa.hashSign(
secretKey,
largePayload,
SlhDsaPreHash.sha256,
params,
context: ctx,
);
final ok = SlhDsa.verify(publicKey, largePayload, signature, params, context: ctx);hashSign pre-hashes the message with an approved function (FIPS 205 §10.2)
before signing; choose a SlhDsaPreHash whose strength matches the parameter
set's security category (e.g. sha256/shake128 for category 1).
-
SlhDsa.signDeterministic(sk, m, params, context: ...)— deterministic (no fresh entropy). Explicit opt-in; the hedged defaultsignis preferred. -
SlhDsa.hashSignDeterministic(...)— deterministic HashSLH-DSA. - Pass
verifyAfterSign: trueto any signing call to verify the freshly produced signature before returning it (adds the full verification cost; defends against generation faults).
sequenceDiagram
participant Si as Signer
participant V as Verifier
Si->>Si: (pk, sk) = generateKeyPair(params)
Si->>Si: sig = sign(sk, msg, params, context)
Si->>V: send msg + sig (+ pk if not already trusted)
V->>V: verify(pk, msg, sig, params, context) -> bool
SLH-DSA composes a few-time FORS signature over the message with a hypertree of WOTS+ one-time signatures and XMSS layers, all built from a single hash family. There is no secret-key state to manage between signatures — it is stateless, unlike LMS/XMSS.
Each row exists in both the SHAKE and SHA-2 hash families
(SlhDsaParams.shake128s, SlhDsaParams.sha2128s, and so on). Sizes are in
bytes.
| Parameter set | NIST category | Public key | Secret key | Signature |
|---|---|---|---|---|
| *-128s | 1 | 32 | 64 | 7856 |
| *-128f | 1 | 32 | 64 | 17088 |
| *-192s | 3 | 48 | 96 | 16224 |
| *-192f | 3 | 48 | 96 | 35664 |
| *-256s | 5 | 64 | 128 | 29792 |
| *-256f | 5 | 64 | 128 | 49856 |
s = small signature / slow signing; f = fast signing / larger signature. The
default is SLH-DSA-SHAKE-128f. Validate exact lengths before verifying.
SLH-DSA's message-bound (BUFF) property is parameter-dependent. Only the
*-128f sets reach the category-1 message-binding bound (FIPS 205 §11); for the
other sets a signature may be valid for more than one message in adversarial
signer scenarios. When a signature authorizes an action, transfer, or consent,
bind it to its purpose with a unique context and a nonce or unique message
identity inside the signed payload. The library cannot enforce protocol-level
message binding.
-
verifyreturnsfalse(never throws) on any malformed, wrong-length, or forged input — branch on the boolean. - A public key is only an identity once you trust it (enrollment, attestation, certificate). A self-provided key proves nothing on its own.
- This is KAT/ACVP/regression evidence, not a CMVP/FIPS 140 validation; zeroization and constant-time behavior are best-effort in Dart. See Security Posture.
- Cryptographic Algorithms · ML-KEM · ML-DSA
- Cookbook — dual signatures, long-term/archival signing, firmware.
- Validation & Interoperability — ACVP and OpenSSL/liboqs interop evidence.
- Canonical: SLHDSA_FIPS205_RELEASE_GUIDE.md
pqcrypto — pure Dart, zero-dependency post-quantum cryptography (ML-KEM FIPS 203 · ML-DSA FIPS 204) for Dart, Flutter, and the web · MIT License · pub.dev · Repository · Documentation Index
Algorithm/KAT-conformance and interoperability evidence — not a CMVP/FIPS 140 module validation.
pqcrypto Wiki
Getting started
Algorithms
Design & internals
Assurance
Integration
Project
Links