+2026-06-30 Niels Möller <nisse@lysator.liu.se>
+
+ * slh-dsa.c (_slh_dsa_sign): Change return type from void to int.
+ After checking expected root hash with memeql_sec, return result
+ rather than asserting that it is true.
+ * slh-dsa-sha2-128f.c (slh_dsa_sha2_128f_sign): Add return value,
+ return success/failure from _slh_dsa_sign, and on failure, clear
+ output signature to all zeros.
+ * slh-dsa-sha2-128s.c (slh_dsa_sha2_128s_sign): Likewise.
+ * slh-dsa-shake-128f.c (slh_dsa_shake_128f_sign): Likewise.
+ * slh-dsa-shake-128s.c (slh_dsa_shake_128s_sign): Likewise.
+ * testsuite/slh-dsa-test.c (test_slh_dsa): Check that signing
+ function returns failure, if public or private key is corrupted.
+ * nettle.texinfo (SLH-DSA): Update documentation.
+
2026-06-05 Niels Möller <nisse@lysator.liu.se>
* ml-kem-internal.c (compress, reduce, mod_sub, mod_add): Use
uint8_t key[SLH_DSA_128_KEY_SIZE];
uint8_t msg[10];
uint8_t *sig;
- void (*sign)(const uint8_t *pub, const uint8_t *priv,
+ int (*sign)(const uint8_t *pub, const uint8_t *priv,
size_t length, const uint8_t *msg,
uint8_t *signature);
int (*verify)(const uint8_t *pub,
bench_slh_dsa_sign (void *p)
{
struct slh_dsa_ctx *ctx = p;
- ctx->sign (ctx->pub, ctx->key, sizeof (ctx->msg), ctx->msg, ctx->sig);
+ int res = ctx->sign (ctx->pub, ctx->key, sizeof (ctx->msg), ctx->msg, ctx->sig);
+ assert (res);
}
static void
several orders of magnitude slower than signing using elliptic curves or
RSA.
+The signing operation can recompute the top-level Merkle tree root
+almost for free. Nettle's SLH-DSA signing functions do that, and compare
+the resulting root hash to the provided public key. If the root hash is
+not as expected, the signing operation fails, and the returned signature
+is cleared to all zeros before return. This check is intended to detect
+invalid inputs, accidental corruption, and some types of software or
+hardware bugs that could affect the computation, but it is not a solid
+defense against deliberate fault injection attacks. Checking the return
+value from these signing functions is not strictly required; it's the
+application's choice if it prefers to handle this error, or pass on an
+invalid all-zero signature.
+
Nettle currently implements the four SLH-DSA variants designed for
``128-bit security'': There's the choice of either SHA256 (in the SHA2
family) or SHAKE256 (in the SHA3 family) as the underlying hash
@code{SLH_DSA_128_SEED_SIZE}.
@end deftypefun
-@deftypefun void slh_dsa_shake_128s_sign (const uint8_t *@var{pub}, const uint8_t *@var{priv}, size_t @var{length}, const uint8_t *@var{msg}, uint8_t *@var{signature})
-@deftypefunx void slh_dsa_shake_128f_sign (const uint8_t *@var{pub}, const uint8_t *@var{priv}, size_t @var{length}, const uint8_t *@var{msg}, uint8_t *@var{signature})
-@deftypefunx void slh_dsa_sha2_128s_sign (const uint8_t *@var{pub}, const uint8_t *@var{priv}, size_t @var{length}, const uint8_t *@var{msg}, uint8_t *@var{signature})
-@deftypefunx void slh_dsa_sha2_128f_sign (const uint8_t *@var{pub}, const uint8_t *@var{priv}, size_t @var{length}, const uint8_t *@var{msg}, uint8_t *@var{signature})
+@deftypefun int slh_dsa_shake_128s_sign (const uint8_t *@var{pub}, const uint8_t *@var{priv}, size_t @var{length}, const uint8_t *@var{msg}, uint8_t *@var{signature})
+@deftypefunx int slh_dsa_shake_128f_sign (const uint8_t *@var{pub}, const uint8_t *@var{priv}, size_t @var{length}, const uint8_t *@var{msg}, uint8_t *@var{signature})
+@deftypefunx int slh_dsa_sha2_128s_sign (const uint8_t *@var{pub}, const uint8_t *@var{priv}, size_t @var{length}, const uint8_t *@var{msg}, uint8_t *@var{signature})
+@deftypefunx int slh_dsa_sha2_128f_sign (const uint8_t *@var{pub}, const uint8_t *@var{priv}, size_t @var{length}, const uint8_t *@var{msg}, uint8_t *@var{signature})
Signs a message using the provided key pair. The size of the resulting
signature is @code{SLH_DSA_128S_SIGNATURE_SIZE} or
-@code{SLH_DSA_128F_SIGNATURE_SIZE} for respective functions.
+@code{SLH_DSA_128F_SIGNATURE_SIZE} for respective functions. Returns 1
+on success, 0 on failure (see above on what failure means).
@end deftypefun
@deftypefun int slh_dsa_shake_128s_verify (const uint8_t *@var{pub}, size_t @var{length}, const uint8_t *@var{msg}, const uint8_t *@var{signature})
size_t length, const uint8_t *msg,
uint8_t *randomizer, size_t digest_size, uint8_t *digest);
-void
+int
_slh_dsa_sign (const struct slh_dsa_params *params,
const struct slh_hash *hash,
const uint8_t *pub, const uint8_t *priv,
# include "config.h"
#endif
+#include <string.h>
+
#include "slh-dsa.h"
#include "slh-dsa-internal.h"
}
/* Only the "pure" and deterministic variant. */
-void
+int
slh_dsa_sha2_128f_sign (const uint8_t *pub, const uint8_t *priv,
size_t length, const uint8_t *msg,
uint8_t *signature)
{
struct sha256_ctx tree_ctx, scratch_ctx;
uint8_t digest[SLH_DSA_M];
+ int res;
+
_slh_dsa_pure_rdigest (&_slh_hash_sha256,
pub, priv + _SLH_DSA_128_SIZE, length, msg,
signature, sizeof (digest), digest);
- _slh_dsa_sign (&_slh_dsa_128f_params, &_slh_hash_sha256,
- pub, priv, digest, signature + _SLH_DSA_128_SIZE,
- &tree_ctx, &scratch_ctx);
+ res = _slh_dsa_sign (&_slh_dsa_128f_params, &_slh_hash_sha256,
+ pub, priv, digest, signature + _SLH_DSA_128_SIZE,
+ &tree_ctx, &scratch_ctx);
+ if (!res)
+ memset (signature, 0, SLH_DSA_128F_SIGNATURE_SIZE);
+
+ return res;
}
int
# include "config.h"
#endif
+#include <string.h>
+
#include "slh-dsa.h"
#include "slh-dsa-internal.h"
}
/* Only the "pure" and deterministic variant. */
-void
+int
slh_dsa_sha2_128s_sign (const uint8_t *pub, const uint8_t *priv,
size_t length, const uint8_t *msg,
uint8_t *signature)
{
struct sha256_ctx tree_ctx, scratch_ctx;
uint8_t digest[SLH_DSA_M];
+ int res;
+
_slh_dsa_pure_rdigest (&_slh_hash_sha256,
pub, priv + _SLH_DSA_128_SIZE, length, msg,
signature, sizeof (digest), digest);
- _slh_dsa_sign (&_slh_dsa_128s_params, &_slh_hash_sha256,
- pub, priv, digest, signature + _SLH_DSA_128_SIZE,
- &tree_ctx, &scratch_ctx);
+ res =_slh_dsa_sign (&_slh_dsa_128s_params, &_slh_hash_sha256,
+ pub, priv, digest, signature + _SLH_DSA_128_SIZE,
+ &tree_ctx, &scratch_ctx);
+ if (!res)
+ memset (signature, 0, SLH_DSA_128S_SIGNATURE_SIZE);
+
+ return res;
}
int
# include "config.h"
#endif
+#include <string.h>
+
#include "slh-dsa.h"
#include "slh-dsa-internal.h"
}
/* Only the "pure" and deterministic variant. */
-void
+int
slh_dsa_shake_128f_sign (const uint8_t *pub, const uint8_t *priv,
size_t length, const uint8_t *msg,
uint8_t *signature)
{
struct sha3_ctx tree_ctx, scratch_ctx;
uint8_t digest[SLH_DSA_M];
+ int res;
+
_slh_dsa_pure_rdigest (&_slh_hash_shake,
pub, priv + _SLH_DSA_128_SIZE, length, msg,
signature, sizeof (digest), digest);
- _slh_dsa_sign (&_slh_dsa_128f_params, &_slh_hash_shake,
- pub, priv, digest, signature + _SLH_DSA_128_SIZE,
- &tree_ctx, &scratch_ctx);
+ res = _slh_dsa_sign (&_slh_dsa_128f_params, &_slh_hash_shake,
+ pub, priv, digest, signature + _SLH_DSA_128_SIZE,
+ &tree_ctx, &scratch_ctx);
+ if (!res)
+ memset (signature, 0, SLH_DSA_128F_SIGNATURE_SIZE);
+
+ return res;
}
int
# include "config.h"
#endif
+#include <string.h>
+
#include "slh-dsa.h"
#include "slh-dsa-internal.h"
}
/* Only the "pure" and deterministic variant. */
-void
+int
slh_dsa_shake_128s_sign (const uint8_t *pub, const uint8_t *priv,
size_t length, const uint8_t *msg,
uint8_t *signature)
{
struct sha3_ctx tree_ctx, scratch_ctx;
uint8_t digest[SLH_DSA_M];
+ int res;
+
_slh_dsa_pure_rdigest (&_slh_hash_shake,
pub, priv + _SLH_DSA_128_SIZE, length, msg,
signature, sizeof (digest), digest);
- _slh_dsa_sign (&_slh_dsa_128s_params, &_slh_hash_shake,
- pub, priv, digest, signature + _SLH_DSA_128_SIZE,
- &tree_ctx, &scratch_ctx);
+ res = _slh_dsa_sign (&_slh_dsa_128s_params, &_slh_hash_shake,
+ pub, priv, digest, signature + _SLH_DSA_128_SIZE,
+ &tree_ctx, &scratch_ctx);
+ if (!res)
+ memset (signature, 0, SLH_DSA_128S_SIGNATURE_SIZE);
+
+ return res;
}
int
# include "config.h"
#endif
-#include <assert.h>
#include <string.h>
#include "memops.h"
_slh_dsa_pure_digest (hash, pub, length, msg, randomizer, digest_size, digest);
}
-void
+int
_slh_dsa_sign (const struct slh_dsa_params *params,
const struct slh_hash *hash,
const uint8_t *pub, const uint8_t *priv,
_xmss_sign (&merkle_ctx, params->xmss.h, leaf_idx, root, signature, root);
}
- assert (memeql_sec (root, pub + _SLH_DSA_128_SIZE, sizeof (root)));
+ return memeql_sec (root, pub + _SLH_DSA_128_SIZE, sizeof (root));
}
int
void *random_ctx, nettle_random_func *random);
/* Only the "pure" and deterministic variant. */
-void
+int
slh_dsa_shake_128s_sign (const uint8_t *pub, const uint8_t *priv,
size_t length, const uint8_t *msg,
uint8_t *signature);
-void
+int
slh_dsa_shake_128f_sign (const uint8_t *pub, const uint8_t *priv,
size_t length, const uint8_t *msg,
uint8_t *signature);
-void
+int
slh_dsa_sha2_128s_sign (const uint8_t *pub, const uint8_t *priv,
size_t length, const uint8_t *msg,
uint8_t *signature);
-void
+int
slh_dsa_sha2_128f_sign (const uint8_t *pub, const uint8_t *priv,
size_t length, const uint8_t *msg,
uint8_t *signature);
ASSERT (MEMEQ (sizeof (pub), pub, exp_pub->data));
}
-typedef void sign_func (const uint8_t *pub, const uint8_t *priv,
- size_t length, const uint8_t *msg,
- uint8_t *signature);
+typedef int sign_func (const uint8_t *pub, const uint8_t *priv,
+ size_t length, const uint8_t *msg,
+ uint8_t *signature);
typedef int verify_func (const uint8_t *pub,
size_t length, const uint8_t *msg,
const uint8_t *signature);
struct slh_dsa_alg
{
const char *name;
- size_t key_size;
size_t signature_size;
sign_func *sign;
verify_func *verify;
static const struct slh_dsa_alg
slh_dsa_shake_128s = {
"slh_dsa_shake_128s",
- SLH_DSA_128_KEY_SIZE,
SLH_DSA_128S_SIGNATURE_SIZE,
slh_dsa_shake_128s_sign,
slh_dsa_shake_128s_verify,
static const struct slh_dsa_alg
slh_dsa_shake_128f = {
"slh_dsa_shake_128f",
- SLH_DSA_128_KEY_SIZE,
SLH_DSA_128F_SIGNATURE_SIZE,
slh_dsa_shake_128f_sign,
slh_dsa_shake_128f_verify,
static const struct slh_dsa_alg
slh_dsa_sha2_128s = {
"slh_dsa_sha2_128s",
- SLH_DSA_128_KEY_SIZE,
SLH_DSA_128S_SIGNATURE_SIZE,
slh_dsa_sha2_128s_sign,
slh_dsa_sha2_128s_verify,
static const struct slh_dsa_alg
slh_dsa_sha2_128f = {
"slh_dsa_sha2_128f",
- SLH_DSA_128_KEY_SIZE,
SLH_DSA_128F_SIGNATURE_SIZE,
slh_dsa_sha2_128f_sign,
slh_dsa_sha2_128f_verify,
const struct tstring *msg, const struct tstring *ref)
{
uint8_t *sig = xalloc (alg->signature_size);
- ASSERT (pub->length == alg->key_size);
- ASSERT (priv->length == alg->key_size);
+ uint8_t bad_key[SLH_DSA_128_KEY_SIZE];
+
+ ASSERT (pub->length == SLH_DSA_128_KEY_SIZE);
+ ASSERT (priv->length == SLH_DSA_128_KEY_SIZE);
ASSERT (ref->length == alg->signature_size);
- alg->sign (pub->data, priv->data, msg->length, msg->data, sig);
+ ASSERT (alg->sign (pub->data, priv->data, msg->length, msg->data, sig));
if (! MEMEQ (alg->signature_size, sig, ref->data))
{
size_t i;
sig[alg->signature_size-1] ^= 1;
ASSERT (!alg->verify (pub->data, msg->length, msg->data, sig));
+ memcpy (bad_key, pub->data, sizeof (bad_key));
+ bad_key[5] ^= 1; /* Modifies public seed */
+ ASSERT (!alg->sign (bad_key, priv->data, msg->length, msg->data, sig));
+ ASSERT (sig[alg->signature_size - 1] == 0);
+
+ memcpy (bad_key, priv->data, sizeof (bad_key));
+ bad_key[5] ^= 1; /* Modifies private seed */
+ ASSERT (!alg->sign (pub->data, bad_key, msg->length, msg->data, sig));
+ ASSERT (sig[alg->signature_size - 1] == 0);
+
free (sig);
}