]> git.ipfire.org Git - thirdparty/nettle.git/commitdiff
Return success/failure from slh-dsa signing functions. master
authorNiels Möller <nisse@lysator.liu.se>
Wed, 1 Jul 2026 06:29:04 +0000 (08:29 +0200)
committerNiels Möller <nisse@lysator.liu.se>
Wed, 1 Jul 2026 06:29:04 +0000 (08:29 +0200)
ChangeLog
examples/hogweed-benchmark.c
nettle.texinfo
slh-dsa-internal.h
slh-dsa-sha2-128f.c
slh-dsa-sha2-128s.c
slh-dsa-shake-128f.c
slh-dsa-shake-128s.c
slh-dsa.c
slh-dsa.h
testsuite/slh-dsa-test.c

index 6346a8726722ec936d87406ad8b21105212585db..beecc55b08f454f78e195d9f1687031c219a2e7b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,18 @@
+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
index 58547d5388d309a5264cb92028ea8c187b4a1fa1..246efd7a62aab9e2185a420ec85e265632014e55 100644 (file)
@@ -901,7 +901,7 @@ struct slh_dsa_ctx
   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,
@@ -985,7 +985,8 @@ static void
 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
index 3802b3b333f4e948b6c12e9d7ab422d74d41ca74..3047b8368989051a541956f8622dda7edb38f0bf 100644 (file)
@@ -5837,6 +5837,18 @@ traversal of all leaves of the corresponding Merkle trees, and this is
 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
@@ -5893,13 +5905,14 @@ typically called with @var{root} = @var{public_seed} +
 @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})
index 209e58fead61c7fc7101f1d56a125002d8db45db..4db2ddcdfee5543e906b36aeef7c2cef02ccde13 100644 (file)
@@ -282,7 +282,7 @@ _slh_dsa_pure_rdigest (const struct slh_hash *hash,
                       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,
index a121bba5e76dc47fe9f28d78ac4f53dea961862e..62c181ee7b3ce3bdfad0eb8cae808abc1f178930 100644 (file)
@@ -35,6 +35,8 @@
 # include "config.h"
 #endif
 
+#include <string.h>
+
 #include "slh-dsa.h"
 #include "slh-dsa-internal.h"
 
@@ -65,19 +67,25 @@ slh_dsa_sha2_128f_generate_keypair (uint8_t *pub, uint8_t *priv,
 }
 
 /* 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
index 7dafd17f65eabc470f350deef2fe40a47fedc50f..1ac8e5435a4611190e30a984502266352678cb90 100644 (file)
@@ -35,6 +35,8 @@
 # include "config.h"
 #endif
 
+#include <string.h>
+
 #include "slh-dsa.h"
 #include "slh-dsa-internal.h"
 
@@ -65,19 +67,25 @@ slh_dsa_sha2_128s_generate_keypair (uint8_t *pub, uint8_t *priv,
 }
 
 /* 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
index a0901f69fddb5c863ad3d60561b89db5c5bed9a3..1161f582c9dc4b37a0507a6fdc9ce89089fd9653 100644 (file)
@@ -35,6 +35,8 @@
 # include "config.h"
 #endif
 
+#include <string.h>
+
 #include "slh-dsa.h"
 #include "slh-dsa-internal.h"
 
@@ -65,19 +67,25 @@ slh_dsa_shake_128f_generate_keypair (uint8_t *pub, uint8_t *priv,
 }
 
 /* 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
index 8dd0d2fe9f150c94017a8a911d2bdfe7914834cf..b8a4062be860ad96f6f58c4af52a5a18922b3b6e 100644 (file)
@@ -35,6 +35,8 @@
 # include "config.h"
 #endif
 
+#include <string.h>
+
 #include "slh-dsa.h"
 #include "slh-dsa-internal.h"
 
@@ -65,19 +67,25 @@ slh_dsa_shake_128s_generate_keypair (uint8_t *pub, uint8_t *priv,
 }
 
 /* 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
index 4a3e9a5b4304bd75568414da7b7e7032c518c331..9ee74d520d4567d4bcd444828ebf0a4b9e5c19f7 100644 (file)
--- a/slh-dsa.c
+++ b/slh-dsa.c
@@ -35,7 +35,6 @@
 # include "config.h"
 #endif
 
-#include <assert.h>
 #include <string.h>
 
 #include "memops.h"
@@ -66,7 +65,7 @@ _slh_dsa_pure_rdigest (const struct slh_hash *hash,
   _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,
@@ -103,7 +102,7 @@ _slh_dsa_sign (const struct slh_dsa_params *params,
 
       _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
index 0d4c774daac951493fa7891b2f46a83f52650e2d..09b8edf924c91431a06afe8f20c00b57ed967883 100644 (file)
--- a/slh-dsa.h
+++ b/slh-dsa.h
@@ -100,19 +100,19 @@ slh_dsa_sha2_128f_generate_keypair (uint8_t *pub, uint8_t *key,
                                    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);
index 1be9b24fa6abbc4f4b797f3b86f364daee404f20..5b3ab1852d31327f3f00299c9a5c12140b840c4f 100644 (file)
@@ -255,16 +255,15 @@ test_slh_dsa_128_root (root_func *f,
   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;
@@ -273,7 +272,6 @@ struct slh_dsa_alg
 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,
@@ -282,7 +280,6 @@ slh_dsa_shake_128s = {
 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,
@@ -291,7 +288,6 @@ slh_dsa_shake_128f = {
 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,
@@ -300,7 +296,6 @@ slh_dsa_sha2_128s = {
 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,
@@ -312,11 +307,13 @@ test_slh_dsa (const struct slh_dsa_alg *alg,
              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;
@@ -334,6 +331,16 @@ test_slh_dsa (const struct slh_dsa_alg *alg,
   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);
 }