]> git.ipfire.org Git - thirdparty/nettle.git/commitdiff
Implement xmss, part of slh-dsa.
authorNiels Möller <nisse@lysator.liu.se>
Wed, 12 Feb 2025 11:34:32 +0000 (12:34 +0100)
committerNiels Möller <nisse@lysator.liu.se>
Fri, 14 Feb 2025 15:41:39 +0000 (16:41 +0100)
ChangeLog
Makefile.in
slh-dsa-internal.h
slh-merkle.c [new file with mode: 0644]
slh-shake.c [new file with mode: 0644]
slh-wots.c
slh-xmss.c [new file with mode: 0644]
testsuite/slh-dsa-test.c

index 51283b09c1ce20693ede4136f053f71a213fa293..542255e5df02b1e85f397b0f4c10694fa9b63706 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,18 @@
+2025-02-12  Niels Möller  <nisse@lysator.liu.se>
+
+       * slh-shake.c (_slh_shake_init, _slh_shake): New file, new
+       functions, moved from slh-wots.c. Update callers.
+       * slh-merkle.c (_merkle_root, _merkle_sign, _merkle_verify): New
+       file, new functions.
+       * slh-xmss.c (xmss_leaf, xmss_node, _xmss_gen, _xmss_sign)
+       (_xmss_verify): New file, new functions.
+       * slh-dsa-internal.h (struct slh_merkle_ctx_public)
+       (struct slh_merkle_ctx_secret): New structs.
+       (merkle_leaf_hash_func, merkle_node_hash_func): New typedefs.
+       * Makefile.in (hogweed_SOURCES): Add slh-merkle.c, slh-shake.c and
+       slh-xmss.c.
+       * testsuite/slh-dsa-test.c: Add xmss and merkle tests.
+
 2025-02-11  Niels Möller  <nisse@lysator.liu.se>
 
        * slh-wots.c (_wots_gen, _wots_sign, _wots_verify): New file, new functions.
index 431704537ab28a95415550fc02009f836605a574..f38a57791c0214b94f4b54480eb570fe0bc39ba6 100644 (file)
@@ -227,7 +227,7 @@ hogweed_SOURCES = sexp.c sexp-format.c \
                  ed25519-sha512-sign.c ed25519-sha512-verify.c \
                  ed448-shake256.c ed448-shake256-pubkey.c \
                  ed448-shake256-sign.c ed448-shake256-verify.c \
-                 slh-wots.c
+                 slh-merkle.c slh-shake.c slh-wots.c slh-xmss.c
 
 OPT_SOURCES = fat-arm.c fat-arm64.c fat-ppc.c fat-s390x.c fat-x86_64.c mini-gmp.c
 
index 623dad5e083eabd1f4d5a341ead3bd47d4385045..862155a948d669322cb2417121119c0fb30fe834 100644 (file)
 #include <stdint.h>
 
 /* Name mangling */
+#define _slh_shake_init _nettle_slh_shake_init
+#define _slh_shake _nettle_slh_shake
 #define _wots_gen _nettle_wots_gen
 #define _wots_sign _nettle_wots_sign
 #define _wots_verify _nettle_wots_verify
+#define _merkle_root _nettle_merkle_root
+#define _merkle_sign _nettle_merkle_sign
+#define _merkle_verify _nettle_merkle_verify
+#define _xmss_gen _nettle_xmss_gen
+#define _xmss_sign _nettle_xmss_sign
+#define _xmss_verify _nettle_xmss_verify
 
 /* Size of a single hash, including the seed and prf parameters */
 #define _SLH_DSA_128_SIZE 16
 
+#define SLH_DSA_D 7
+
 /* Fields always big-endian */
 struct slh_address_tree
 {
@@ -72,6 +82,28 @@ enum slh_addr_type
     SLH_FORS_PRF = 6,
   };
 
+struct slh_merkle_ctx_public
+{
+  const uint8_t *seed;
+  struct slh_address_tree at;
+  unsigned keypair; /* Used only by fors_leaf and fors_node. */
+};
+
+struct slh_merkle_ctx_secret
+{
+  struct slh_merkle_ctx_public pub;
+  const uint8_t *secret_seed;
+};
+
+struct sha3_256_ctx;
+void
+_slh_shake_init (struct sha3_256_ctx *ctx, const uint8_t *public_seed,
+                const struct slh_address_tree *at, const struct slh_address_hash *ah);
+void
+_slh_shake (const uint8_t *public_seed,
+           const struct slh_address_tree *at, const struct slh_address_hash *ah,
+           const uint8_t *secret, uint8_t *out);
+
 #define _WOTS_SIGNATURE_LENGTH 35
 /* 560 bytes */
 #define WOTS_SIGNATURE_SIZE (_WOTS_SIGNATURE_LENGTH*_SLH_DSA_128_SIZE)
@@ -89,4 +121,46 @@ void
 _wots_verify (const uint8_t *public_seed, const struct slh_address_tree *at,
              unsigned keypair, const uint8_t *msg, const uint8_t *signature, uint8_t *pub);
 
+/* Merkle tree functions. Could be generalized for other merkle tree
+   applications, by using const void* for the ctx argument. */
+typedef void merkle_leaf_hash_func (const struct slh_merkle_ctx_secret *ctx, unsigned index, uint8_t *out);
+typedef void merkle_node_hash_func (const struct slh_merkle_ctx_public *ctx, unsigned height, unsigned index,
+                                   const uint8_t *left, const uint8_t *right, uint8_t *out);
+
+void
+_merkle_root (const struct slh_merkle_ctx_secret *ctx,
+             merkle_leaf_hash_func *leaf_hash, merkle_node_hash_func *node_hash,
+             unsigned height, unsigned start, uint8_t *root,
+             /* Must have space for (height + 1) node hashes */
+             uint8_t *stack);
+
+void
+_merkle_sign (const struct slh_merkle_ctx_secret *ctx,
+             merkle_leaf_hash_func *leaf_hash, merkle_node_hash_func *node_hash,
+             unsigned height, unsigned idx, uint8_t *signature);
+
+/* The hash argument is both input (leaf hash to be verified) and output (resulting root hash). */
+void
+_merkle_verify (const struct slh_merkle_ctx_public *ctx, merkle_node_hash_func *node_hash,
+               unsigned height, unsigned idx, const uint8_t *signature, uint8_t *hash);
+
+#define XMSS_H 9
+/* Just the auth path, excluding the wots signature, 144 bytes. */
+#define XMSS_AUTH_SIZE (XMSS_H * _SLH_DSA_128_SIZE)
+#define XMSS_SIGNATURE_SIZE (WOTS_SIGNATURE_SIZE + XMSS_AUTH_SIZE)
+
+void
+_xmss_gen (const uint8_t *public_seed, const uint8_t *secret_seed,
+          uint8_t *root);
+
+/* Signs using wots, then signs wots public key using xmss. Also
+   returns the xmss public key (i.e., root hash).*/
+void
+_xmss_sign (const struct slh_merkle_ctx_secret *ctx,
+           unsigned idx, const uint8_t *msg, uint8_t *signature, uint8_t *pub);
+
+void
+_xmss_verify (const struct slh_merkle_ctx_public *ctx,
+             unsigned idx, const uint8_t *msg, const uint8_t *signature, uint8_t *pub);
+
 #endif /* NETTLE_SLH_DSA_INTERNAL_H_INCLUDED */
diff --git a/slh-merkle.c b/slh-merkle.c
new file mode 100644 (file)
index 0000000..5a611d4
--- /dev/null
@@ -0,0 +1,123 @@
+/* slh-merkle.c
+
+   Copyright (C) 2025 Niels Möller
+
+   This file is part of GNU Nettle.
+
+   GNU Nettle is free software: you can redistribute it and/or
+   modify it under the terms of either:
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at your
+       option) any later version.
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at your
+       option) any later version.
+
+   or both in parallel, as here.
+
+   GNU Nettle is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see http://www.gnu.org/licenses/.
+*/
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <assert.h>
+
+#include "slh-dsa-internal.h"
+
+/* Computes root hash of a tree.
+   Example for height == 2, 4 entries:
+
+   i = 0 ==> stack: [0], i = 1
+   i = 1 ==> stack: [0, 1] ==> [0|1], i = 2
+   i = 2 ==> stack: [0|1, 2], i = 3
+   i = 3 ==> stack: [0|1, 2, 3] ==> [0|1, 2|3] ==> [0|1|2|3], i = 4
+
+   The size of the stack equals popcount(i)
+*/
+void
+_merkle_root (const struct slh_merkle_ctx_secret *ctx,
+             merkle_leaf_hash_func *leaf_hash, merkle_node_hash_func *node_hash,
+             unsigned height, unsigned start, uint8_t *root,
+             /* Must have space for (height + 1) node hashes */
+             uint8_t *stack)
+{
+  unsigned stack_size = 0;
+  unsigned i;
+  assert (height > 0);
+  assert ( (start & ((1<<height) - 1)) == 0);
+
+  for (i = 0; i < (1<<height); i++)
+    {
+      /* Leaf index. */
+      unsigned idx = start + i;
+      unsigned h;
+      assert (stack_size <= height);
+
+      leaf_hash (ctx, idx, stack + stack_size++ * _SLH_DSA_128_SIZE);
+
+      for (h = 1; (idx&1); h++)
+       {
+         assert (stack_size >= 2);
+         idx >>= 1;
+         stack_size--;
+         if (h == height)
+           {
+             assert (stack_size == 1);
+             node_hash (&ctx->pub, h, idx,
+                        stack + (stack_size - 1) * _SLH_DSA_128_SIZE,
+                        stack + stack_size * _SLH_DSA_128_SIZE,
+                        root);
+             return;
+           }
+         node_hash (&ctx->pub, h, idx,
+                    stack + (stack_size - 1) * _SLH_DSA_128_SIZE,
+                    stack + stack_size * _SLH_DSA_128_SIZE,
+                    stack + (stack_size - 1)* _SLH_DSA_128_SIZE);
+       }
+    }
+}
+
+void
+_merkle_sign (const struct slh_merkle_ctx_secret *ctx,
+             merkle_leaf_hash_func *leaf_hash, merkle_node_hash_func *node_hash,
+             unsigned height, unsigned idx, uint8_t *signature)
+{
+  unsigned h;
+  /* By generating the path from the end, we can use the output area
+     as the temporary stack. */
+  for (h = height; --h > 0;)
+    _merkle_root (ctx, leaf_hash, node_hash, h, (idx & -(1 << h)) ^ (1 << h),
+                 signature + h*_SLH_DSA_128_SIZE, signature);
+
+  leaf_hash (ctx, idx ^ 1, signature);
+}
+
+void
+_merkle_verify (const struct slh_merkle_ctx_public *ctx, merkle_node_hash_func *node_hash,
+               unsigned height, unsigned idx, const uint8_t *signature, uint8_t *hash)
+{
+  unsigned h;
+
+  for (h = 1; h <= height; h++, signature += _SLH_DSA_128_SIZE)
+    {
+      unsigned right = idx & 1;
+      idx >>= 1;
+      if (right)
+       node_hash (ctx, h, idx, signature, hash, hash);
+      else
+       node_hash (ctx, h, idx, hash, signature, hash);
+    }
+}
diff --git a/slh-shake.c b/slh-shake.c
new file mode 100644 (file)
index 0000000..83dbe9d
--- /dev/null
@@ -0,0 +1,58 @@
+/* slh-prf.c
+
+   Copyright (C) 2025 Niels Möller
+
+   This file is part of GNU Nettle.
+
+   GNU Nettle is free software: you can redistribute it and/or
+   modify it under the terms of either:
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at your
+       option) any later version.
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at your
+       option) any later version.
+
+   or both in parallel, as here.
+
+   GNU Nettle is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see http://www.gnu.org/licenses/.
+*/
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "sha3.h"
+#include "slh-dsa-internal.h"
+
+void
+_slh_shake_init (struct sha3_256_ctx *ctx, const uint8_t *public_seed,
+                const struct slh_address_tree *at, const struct slh_address_hash *ah)
+{
+  sha3_256_init (ctx);
+  sha3_256_update (ctx, _SLH_DSA_128_SIZE, public_seed);
+  sha3_256_update (ctx, sizeof(*at), (const uint8_t *) at);
+  sha3_256_update (ctx, sizeof(*ah), (const uint8_t *) ah);
+}
+
+void
+_slh_shake (const uint8_t *public_seed,
+           const struct slh_address_tree *at, const struct slh_address_hash *ah,
+           const uint8_t *secret, uint8_t *out)
+{
+  struct sha3_256_ctx ctx;
+  _slh_shake_init (&ctx, public_seed, at, ah);
+  sha3_256_update (&ctx, _SLH_DSA_128_SIZE, secret);
+  sha3_256_shake (&ctx, _SLH_DSA_128_SIZE, out);
+}
index f5dd7920aa56b097d261cb17e595bb8ec9fe5eb9..a74d84ddd61f51120aa2b178e987142cf8402896 100644 (file)
 #include "sha3.h"
 #include "bswap-internal.h"
 
-static void
-slh_shake_init (struct sha3_256_ctx *ctx, const uint8_t *public_seed,
-               const struct slh_address_tree *at, const struct slh_address_hash *ah)
-{
-  sha3_256_init (ctx);
-  sha3_256_update (ctx, _SLH_DSA_128_SIZE, public_seed);
-  sha3_256_update (ctx, sizeof(*at), (const uint8_t *) at);
-  sha3_256_update (ctx, sizeof(*ah), (const uint8_t *) ah);
-}
-
-static void
-slh_prf (const uint8_t *public_seed,
-        const struct slh_address_tree *at, const struct slh_address_hash *ah,
-        const uint8_t *secret, uint8_t *out)
-{
-  struct sha3_256_ctx ctx;
-  slh_shake_init (&ctx, public_seed, at, ah);
-  sha3_256_update (&ctx, _SLH_DSA_128_SIZE, secret);
-  sha3_256_shake (&ctx, _SLH_DSA_128_SIZE, out);
-}
-
 /* If s == 0, returns src and leaves dst unchanged. Otherwise, returns
    dst. For the ah argument, leaves ah->keypair and ah->height_chain
    unchanged, but overwrites the other fields. */
@@ -78,12 +57,12 @@ wots_chain (const uint8_t *public_seed, const struct slh_address_tree *at,
   ah->type = bswap32_if_le (SLH_WOTS_HASH);
   ah->index_hash = bswap32_if_le(i);
 
-  slh_prf (public_seed, at, ah, src, dst);
+  _slh_shake (public_seed, at, ah, src, dst);
 
   for (j = 1; j < s; j++)
     {
       ah->index_hash = bswap32_if_le(i + j);
-      slh_prf (public_seed, at, ah, dst, dst);
+      _slh_shake (public_seed, at, ah, dst, dst);
     }
 
   return dst;
@@ -98,7 +77,7 @@ wots_pk_init (const uint8_t *public_seed, const struct slh_address_tree *at,
   ah->height_chain = 0;
   ah->index_hash = 0;
 
-  slh_shake_init (ctx, public_seed, at, ah);
+  _slh_shake_init (ctx, public_seed, at, ah);
 }
 
 void
@@ -119,7 +98,7 @@ _wots_gen (const uint8_t *public_seed, const uint8_t *secret_seed, const struct
       ah.type = bswap32_if_le (SLH_WOTS_PRF);
       ah.height_chain = bswap32_if_le(i);
       ah.index_hash = 0;
-      slh_prf (public_seed, at, &ah, secret_seed, out);
+      _slh_shake (public_seed, at, &ah, secret_seed, out);
 
       /* Hash chain. */
       wots_chain (public_seed, at, &ah, 0, 15, out, out);
@@ -145,7 +124,7 @@ wots_sign_one (const uint8_t *public_seed,
   ah.keypair = bswap32_if_le (keypair);
   ah.height_chain = bswap32_if_le(i);
   ah.index_hash = 0;
-  slh_prf (public_seed, at, &ah, secret_seed, sig);
+  _slh_shake (public_seed, at, &ah, secret_seed, sig);
 
   /* Hash chain. */
   wots_chain (public_seed, at, &ah, 0, msg, sig, sig);
diff --git a/slh-xmss.c b/slh-xmss.c
new file mode 100644 (file)
index 0000000..1f0a36b
--- /dev/null
@@ -0,0 +1,104 @@
+/* slh-xmss.c
+
+   The eXtended Merkle Signature Scheme, part of SLH-DSA (FIPS 205)
+
+   Copyright (C) 2025 Niels Möller
+
+   This file is part of GNU Nettle.
+
+   GNU Nettle is free software: you can redistribute it and/or
+   modify it under the terms of either:
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at your
+       option) any later version.
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at your
+       option) any later version.
+
+   or both in parallel, as here.
+
+   GNU Nettle is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see http://www.gnu.org/licenses/.
+*/
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "bswap-internal.h"
+#include "sha3.h"
+#include "slh-dsa-internal.h"
+
+static void
+xmss_leaf (const struct slh_merkle_ctx_secret *ctx, unsigned idx, uint8_t *leaf)
+{
+  _wots_gen (ctx->pub.seed, ctx->secret_seed, &ctx->pub.at, idx, leaf);
+}
+
+static void
+xmss_node (const struct slh_merkle_ctx_public *ctx, unsigned height, unsigned index,
+          const uint8_t *left, const uint8_t *right, uint8_t *out)
+{
+  struct sha3_256_ctx sha3;
+  struct slh_address_hash ah =
+    {
+      bswap32_if_le (SLH_XMSS_TREE),
+      0,
+      bswap32_if_le (height),
+      bswap32_if_le (index),
+    };
+
+  _slh_shake_init (&sha3, ctx->seed, &ctx->at, &ah);
+  sha3_256_update (&sha3, _SLH_DSA_128_SIZE, left);
+  sha3_256_update (&sha3, _SLH_DSA_128_SIZE, right);
+  sha3_256_shake (&sha3, _SLH_DSA_128_SIZE, out);
+}
+
+void
+_xmss_gen (const uint8_t *public_seed, const uint8_t *secret_seed,
+          uint8_t *root)
+{
+  struct slh_merkle_ctx_secret ctx =
+    {
+      {
+       public_seed,
+       /* Everything zero, except layer and type. */
+       { bswap32_if_le(SLH_DSA_D-1), 0, 0, } ,
+       0,
+      },
+      secret_seed
+    };
+  uint8_t stack[(XMSS_H + 1)*_SLH_DSA_128_SIZE];
+  _merkle_root (&ctx, xmss_leaf, xmss_node, XMSS_H, 0, root, stack);
+}
+
+void
+_xmss_sign (const struct slh_merkle_ctx_secret *ctx,
+           unsigned idx, const uint8_t *msg, uint8_t *signature, uint8_t *pub)
+{
+  _wots_sign (ctx->pub.seed, ctx->secret_seed, &ctx->pub.at, idx, msg, signature, pub);
+  signature += WOTS_SIGNATURE_SIZE;
+
+  _merkle_sign (ctx, xmss_leaf, xmss_node, XMSS_H, idx, signature);
+  _merkle_verify (&ctx->pub, xmss_node, XMSS_H, idx, signature, pub);
+}
+
+void
+_xmss_verify (const struct slh_merkle_ctx_public *ctx,
+             unsigned idx, const uint8_t *msg, const uint8_t *signature, uint8_t *pub)
+{
+  _wots_verify (ctx->seed, &ctx->at, idx, msg, signature, pub);
+  signature += WOTS_SIGNATURE_SIZE;
+
+  _merkle_verify (ctx, xmss_node, XMSS_H, idx, signature, pub);
+}
index a0b4d0f3bd7ff28820bf3df45004d7d1617eec7f..3c00878b54717f4bcd24452348a2813cbc9b9278 100644 (file)
@@ -31,6 +31,7 @@
 
 #include "testutils.h"
 
+#include "sha3.h"
 #include "slh-dsa-internal.h"
 #include "bswap-internal.h"
 
@@ -78,6 +79,108 @@ test_wots_sign (const struct tstring *public_seed, const struct tstring *secret_
   ASSERT (MEMEQ(sizeof(pub), pub, exp_pub->data));
 }
 
+/* The xmss_leaf and xmss_node functions copied from slh-xmss.c */
+static void
+xmss_leaf (const struct slh_merkle_ctx_secret *ctx, unsigned idx, uint8_t *leaf)
+{
+  _wots_gen (ctx->pub.seed, ctx->secret_seed, &ctx->pub.at, idx, leaf);
+}
+
+static void
+xmss_node (const struct slh_merkle_ctx_public *ctx, unsigned height, unsigned index,
+          const uint8_t *left, const uint8_t *right, uint8_t *out)
+{
+  struct sha3_256_ctx sha3;
+  struct slh_address_hash ah =
+    {
+      bswap32_if_le (SLH_XMSS_TREE),
+      0,
+      bswap32_if_le (height),
+      bswap32_if_le (index),
+    };
+
+  _slh_shake_init (&sha3, ctx->seed, &ctx->at, &ah);
+  sha3_256_update (&sha3, _SLH_DSA_128_SIZE, left);
+  sha3_256_update (&sha3, _SLH_DSA_128_SIZE, right);
+  sha3_256_shake (&sha3, _SLH_DSA_128_SIZE, out);
+}
+
+static void
+test_merkle (const struct tstring *public_seed, const struct tstring *secret_seed,
+            unsigned layer, uint64_t tree_idx, uint32_t idx, const struct tstring *msg,
+            const struct tstring *exp_pub, const struct tstring *exp_sig)
+{
+  struct slh_merkle_ctx_secret ctx =
+    {
+      {
+       public_seed->data,
+       { bswap32_if_le(layer), 0, bswap64_if_le(tree_idx) },
+       0,
+      },
+      secret_seed->data,
+    };
+
+  uint8_t sig[XMSS_AUTH_SIZE];
+  uint8_t pub[_SLH_DSA_128_SIZE];
+  ASSERT (public_seed->length == _SLH_DSA_128_SIZE);
+  ASSERT (secret_seed->length == _SLH_DSA_128_SIZE);
+  ASSERT (msg->length == _SLH_DSA_128_SIZE);
+  ASSERT (exp_pub->length == _SLH_DSA_128_SIZE);
+  ASSERT (exp_sig->length == XMSS_AUTH_SIZE);
+
+  _merkle_sign (&ctx, xmss_leaf, xmss_node, XMSS_H, idx, sig);
+  ASSERT (MEMEQ(sizeof(sig), sig, exp_sig->data));
+
+  memcpy (pub, msg->data, sizeof(pub));
+  _merkle_verify (&ctx.pub, xmss_node, XMSS_H, idx, sig, pub);
+  ASSERT (MEMEQ(sizeof(pub), pub, exp_pub->data));
+}
+
+static void
+test_xmss_gen(const struct tstring *public_seed, const struct tstring *secret_seed,
+             const struct tstring *exp_pub)
+{
+  uint8_t pub[_SLH_DSA_128_SIZE];
+  ASSERT (public_seed->length == _SLH_DSA_128_SIZE);
+  ASSERT (secret_seed->length == _SLH_DSA_128_SIZE);
+  ASSERT (exp_pub->length == _SLH_DSA_128_SIZE);
+
+  _xmss_gen (public_seed->data, secret_seed->data, pub);
+  ASSERT (MEMEQ(sizeof(pub), pub, exp_pub->data));
+}
+
+static void
+test_xmss_sign (const struct tstring *public_seed, const struct tstring *secret_seed,
+               unsigned layer, uint64_t tree_idx, uint32_t idx, const struct tstring *msg,
+               const struct tstring *exp_pub, const struct tstring *exp_sig)
+{
+  struct slh_merkle_ctx_secret ctx =
+    {
+      {
+       public_seed->data,
+       { bswap32_if_le(layer), 0, bswap64_if_le(tree_idx) },
+       0,
+      },
+      secret_seed->data,
+    };
+
+  uint8_t sig[XMSS_SIGNATURE_SIZE];
+  uint8_t pub[_SLH_DSA_128_SIZE];
+  ASSERT (public_seed->length == _SLH_DSA_128_SIZE);
+  ASSERT (secret_seed->length == _SLH_DSA_128_SIZE);
+  ASSERT (msg->length == _SLH_DSA_128_SIZE);
+  ASSERT (exp_pub->length == _SLH_DSA_128_SIZE);
+  ASSERT (exp_sig->length == XMSS_SIGNATURE_SIZE);
+
+  _xmss_sign (&ctx, idx, msg->data, sig, pub);
+  ASSERT (MEMEQ(sizeof(sig), sig, exp_sig->data));
+  ASSERT (MEMEQ(sizeof(pub), pub, exp_pub->data));
+
+  memset (pub, 0, sizeof(pub));
+  _xmss_verify (&ctx.pub, idx, msg->data, sig, pub);
+  ASSERT (MEMEQ(sizeof(pub), pub, exp_pub->data));
+}
+
 void
 test_main(void)
 {
@@ -115,4 +218,47 @@ test_main(void)
                       "711281599b3e05e7 5492ae3425eaa7f1 4ff8c6a9630bba6e bd236f195269a481"
                       "e87eb3d444825ba4 424ee5b2d9efb595 d5a338f4c253f79d e9d04535206ca6db"
                       "c2d4c9a1ec20849b 0db3fbe10c1446d5"));
+
+  test_merkle (public_seed, secret_seed, 0, UINT64_C(0x29877722d7c079), 0x156,
+              /* The message signed is the wots public key. */
+              SHEX("99747c3547770fa288a628ed15122d3e"),
+              SHEX("1be9523f2c90cd553ef5be5aa1c5c4fa"),
+              SHEX("612d5bac915a3996 2cdbcacee0969dcf 8ecfb830cea2206c 37749c65b8f673db"
+                   "090b1e2ade6c2a2f 349b5915103a3ac7 8482c39e99ffc462 6fb4cf4a116804ab"
+                   "9d93d7104660fefa 0753cf875cb22fd6 0e55dc2f303de036 47712b12067a55f7"
+                   "a467897bbed0d3a0 9d50e9deaadff78d e9ac65c1fd05d076 10a79c8c465141ad"
+                   "65e60340531fab08 f1f433ef823283fe"));
+
+  test_xmss_gen (public_seed, secret_seed,
+                SHEX("ac524902fc81f503 2bc27b17d9261ebd"));
+
+  test_xmss_sign(public_seed, secret_seed, 0, UINT64_C(0x29877722d7c079), 0x156,
+                /* The message signed is a fors public key. */
+                SHEX("3961b2cab15e08c633be827744a07f01"),
+                SHEX("1be9523f2c90cd553ef5be5aa1c5c4fa"),
+                SHEX(/* Embedded wots signature. */
+                     "e1933de10e3fface 5fb8f8707c35ac13 74dc14ee8518481c 7e63d936ecc62f50"
+                     "c7f951b87bc716dc 45e9bcfec6f6d97e 7fafdacb6db05ed3 778f21851f325e25"
+                     "470da8dd81c41223 6d66cbee9ffa9c50 b86aa40baf213494 dfacca22aa0fb479"
+                     "53928735ca4212cf 53a09ab0335d20a8 e62ede797c8e7493 54d636f15f3150c5"
+                     "52797b76c091a41f 949f7fb57b42f744 1cca410264d6421f 4aa2c7e2ff4834a8"
+                     "db0e6e7750b2e11f f1c89a42d1fbc271 8358e38325886ad1 2346cd694f9eab73"
+                     "46c9a23b5ebe7637 bfd834a412318b01 188b0f29e3bd979f 8ae734acf1563af3"
+                     "03d3c095e9eaeba3 5207b9df3acf9ee4 7da5c1e2652f3b86 41698f3d2260591b"
+                     "07d00565e5d6be18 36033d2b7ef2c33b dc5cf3bba95b42df 6f73345b835341b2"
+                     "50e2862c9f2f9cef 77cfa74cfb04c560 d8a0038c4e96cb0d a2b3e9b2cd3cecf5"
+                     "22fda0d67e5f62b2 ee23bd42a61c7da4 8f0ea30b81af7ccb 6bb02cde272d2574"
+                     "1325e9d91535615c 0184f2d7f226141d 79b42412721fd345 61d93663650b3c1b"
+                     "6901872bc4c0bb15 bcd9038950b7717f 7f448b6126592076 a2bad2d63c55399c"
+                     "243fdbdb0c8d676b 2ae455e7f0a9b18d 3fc889c43387f2cb c4dc73d7c85bfab6"
+                     "b4b04463a3dd359c 3a8f61bfa6c4b042 4aeba4dd8a95ec12 43b2e36c29f82e1d"
+                     "711281599b3e05e7 5492ae3425eaa7f1 4ff8c6a9630bba6e bd236f195269a481"
+                     "e87eb3d444825ba4 424ee5b2d9efb595 d5a338f4c253f79d e9d04535206ca6db"
+                     "c2d4c9a1ec20849b 0db3fbe10c1446d5"
+                     /* Auth path aka inclusion proof. */
+                     "612d5bac915a3996 2cdbcacee0969dcf 8ecfb830cea2206c 37749c65b8f673db"
+                     "090b1e2ade6c2a2f 349b5915103a3ac7 8482c39e99ffc462 6fb4cf4a116804ab"
+                     "9d93d7104660fefa 0753cf875cb22fd6 0e55dc2f303de036 47712b12067a55f7"
+                     "a467897bbed0d3a0 9d50e9deaadff78d e9ac65c1fd05d076 10a79c8c465141ad"
+                     "65e60340531fab08 f1f433ef823283fe"));
 }