/* wc_slhdsa.c
 *
 * Copyright (C) 2006-2026 wolfSSL Inc.
 *
 * This file is part of wolfSSL.
 *
 * wolfSSL is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * wolfSSL 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 a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
 */

#include <wolfssl/wolfcrypt/libwolfssl_sources.h>

#if FIPS_VERSION3_GE(2,0,0)
    /* set NO_WRAPPERS before headers, use direct internal f()s not wrappers */
    #define FIPS_NO_WRAPPERS
#endif

#include <wolfssl/wolfcrypt/wc_slhdsa.h>

#ifdef WOLFSSL_HAVE_SLHDSA

#include <wolfssl/wolfcrypt/asn.h>
#include <wolfssl/wolfcrypt/cpuid.h>
#include <wolfssl/wolfcrypt/error-crypt.h>
#ifdef NO_INLINE
    #include <wolfssl/wolfcrypt/misc.h>
#else
    #define WOLFSSL_MISC_INCLUDED
    #include <wolfcrypt/src/misc.c>
#endif
#include <wolfssl/wolfcrypt/hash.h>
#include <wolfssl/wolfcrypt/sha3.h>
#ifdef WOLFSSL_SLHDSA_SHA2
    #include <wolfssl/wolfcrypt/sha256.h>
    #include <wolfssl/wolfcrypt/sha512.h>
    #include <wolfssl/wolfcrypt/hmac.h>
#endif

#ifdef WC_SLHDSA_NO_ASM
    #undef USE_INTEL_SPEEDUP
    #undef WOLFSSL_ARMASM
    #undef WOLFSSL_RISCV_ASM
#endif

#if defined(USE_INTEL_SPEEDUP)
/* CPU information for Intel. */
static cpuid_flags_t cpuid_flags = WC_CPUID_INITIALIZER;
#endif


/* Winternitz number. */
#define SLHDSA_W                16
/* Number of iterations of hashing itself from Winternitz number. */
#define SLHDSA_WM1              (SLHDSA_W - 1)


#ifndef WOLFSSL_SLHDSA_PARAM_NO_256
    /* Maximum size of hash output. */
    #define SLHDSA_MAX_N                32
    #ifndef WOLFSSL_SLHDSA_PARAM_NO_FAST
        /* Maximum number of indices for FORS signatures. */
        #define SLHDSA_MAX_INDICES_SZ   35
    #else
        /* Maximum number of indices for FORS signatures. */
        #define SLHDSA_MAX_INDICES_SZ   22
    #endif
#elif !defined(WOLFSSL_SLHDSA_PARAM_NO_192)
    /* Maximum size of hash output. */
    #define SLHDSA_MAX_N                24
    #ifndef WOLFSSL_SLHDSA_PARAM_NO_FAST
        /* Maximum number of indices for FORS signatures. */
        #define SLHDSA_MAX_INDICES_SZ   33
    #else
        /* Maximum number of indices for FORS signatures. */
        #define SLHDSA_MAX_INDICES_SZ   17
    #endif
#else
    /* Maximum size of hash output. */
    #define SLHDSA_MAX_N                16
    #ifndef WOLFSSL_SLHDSA_PARAM_NO_FAST
        /* Maximum number of indices for FORS signatures. */
        #define SLHDSA_MAX_INDICES_SZ   33
    #else
        /* Maximum number of indices for FORS signatures. */
        #define SLHDSA_MAX_INDICES_SZ   14
    #endif
#endif

#ifndef WOLFSSL_SLHDSA_PARAM_NO_SMALL
    #if !defined(WOLFSSL_SLHDSA_PARAM_NO_256)
        /* Maximum number of trees for FORS. */
        #define SLHDSA_MAX_A            14
    #elif !defined(WOLFSSL_SLHDSA_PARAM_NO_192)
        /* Maximum number of trees for FORS. */
        #define SLHDSA_MAX_A            14
    #else
        /* Maximum number of trees for FORS. */
        #define SLHDSA_MAX_A            12
    #endif
#else
    #if !defined(WOLFSSL_SLHDSA_PARAM_NO_256)
        /* Maximum number of trees for FORS. */
        #define SLHDSA_MAX_A            9
    #elif !defined(WOLFSSL_SLHDSA_PARAM_NO_192)
        /* Maximum number of trees for FORS. */
        #define SLHDSA_MAX_A            8
    #else
        /* Maximum number of trees for FORS. */
        #define SLHDSA_MAX_A            6
    #endif
#endif

#ifndef WOLFSSL_SLHDSA_PARAM_NO_SMALL
    /* Maximum height of Merkle tree. */
    #define SLHDSA_MAX_H_M              9
#else
    /* Maximum height of Merkle tree. */
    #define SLHDSA_MAX_H_M              3
#endif

/* Maximum message size in nibbles. */
#define SLHDSA_MAX_MSG_SZ       ((2 * SLHDSA_MAX_N) + 3)

/* SLH-DSA WOTS+ length: len = len_1 + len_2 = 2*n + 3 (for w=16). The chain
 * helpers below pass loop indices and chain steps through (byte) casts; this
 * assertion documents the invariant they rely on. */
wc_static_assert(SLHDSA_MAX_MSG_SZ <= 255);

#ifndef WOLFSSL_SLHDSA_PARAM_NO_256F
    /* Maximum number of bytes to produce from digest of message. */
    #define SLHDSA_MAX_MD               49
#elif !defined(WOLFSSL_SLHDSA_PARAM_NO_256S)
    /* Maximum number of bytes to produce from digest of message. */
    #define SLHDSA_MAX_MD               47
#elif !defined(WOLFSSL_SLHDSA_PARAM_NO_192F)
    /* Maximum number of bytes to produce from digest of message. */
    #define SLHDSA_MAX_MD               42
#elif !defined(WOLFSSL_SLHDSA_PARAM_NO_192S)
    /* Maximum number of bytes to produce from digest of message. */
    #define SLHDSA_MAX_MD               39
#elif !defined(WOLFSSL_SLHDSA_PARAM_NO_128F)
    /* Maximum number of bytes to produce from digest of message. */
    #define SLHDSA_MAX_MD               34
#else
    /* Maximum number of bytes to produce from digest of message. */
    #define SLHDSA_MAX_MD               30
#endif


/******************************************************************************
 * HashAddress
 ******************************************************************************/

/* HashAddress types. */
/* WOTS+ hash. */
#define HA_WOTS_HASH    0
/* WOTS+ Public Key. */
#define HA_WOTS_PK      1
/* XMSS tree. */
#define HA_TREE         2
/* FORS tree. */
#define HA_FORS_TREE    3
/* FORS Root. */
#define HA_FORS_ROOTS   4
/* WOTS Pseudo-random function. */
#define HA_WOTS_PRF     5
/* FORS Pseudo-random function. */
#define HA_FORS_PRF     6

/* Size of an encoded HashAddress. */
#define SLHDSA_HA_SZ    32

/* Initialize a HashAddress.
 *
 * @param [in] a  HashAddress to initialize.
 */
#define HA_Init(a)                  XMEMSET(a, 0, sizeof(HashAddress))
/* Copy a HashAddress.
 *
 * @param [out] a  HashAddress to copy into.
 * @param [in]  b  HashAddress to copy from.
 */
#define HA_Copy(a, b)               XMEMCPY(a, b, sizeof(HashAddress))
/* Set layer address into HashAddress.
 *
 * FIPS 205. Section 4.3. Table 1. Line 1.
 *
 * @param [in] a  HashAddress set.
 * @param [in] l  Layer address.
 */
#define HA_SetLayerAddress(a, l)    (a)[0] = (word32)(l)
/* Set tree address into HashAddress.
 *
 * FIPS 205. Section 4.3. Table 1. Line 2.
 *
 * @param [in] a  HashAddress set.
 * @param [in] t  Tree address.
 */
#define HA_SetTreeAddress(a, t)                                                \
    do { (a)[1] = (t)[0]; (a)[2] = (t)[1]; (a)[3] = (t)[2]; } while (0)
/* Set type and clear following fields.
 *
 * FIPS 205. Section 4.3. Table 1. Line 3.
 *
 * @param [in] a  HashAddress set.
 * @param [in] y  HashAddress type.
 */
#define HA_SetTypeAndClear(a, y)                                               \
    do { (a)[4] = (word32)(y); (a)[5] = 0U; (a)[6] = 0U; (a)[7] = 0U; } while (0)
/* Set type and clear following fields but not Key Pair Address.
 *
 * FIPS 205. Section 4.3. Table 1. Line 3. But don't clear Key Pair Address.
 *
 * @param [in] a  HashAddress set.
 * @param [in] y  HashAddress type.
 */
#define HA_SetTypeAndClearNotKPA(a, y)                                         \
    do { (a)[4] = (word32)(y); (a)[6] = 0U; (a)[7] = 0U; } while (0)
/* Set key pair address into HashAddress.
 *
 * FIPS 205. Section 4.3. Table 1. Line 4.
 *
 * @param [in] a  HashAddress set.
 * @param [in] i  Key pair address.
 */
#define HA_SetKeyPairAddress(a, i)  (a)[5] = (word32)(i)
/* Set chain address into HashAddress.
 *
 * FIPS 205. Section 4.3. Table 1. Line 5.
 *
 * @param [in] a  HashAddress set.
 * @param [in] i  Chain address.
 */
#define HA_SetChainAddress(a, i)    (a)[6] = (word32)(i)
/* Set tree height into HashAddress.
 *
 * FIPS 205. Section 4.3. Table 1. Line 5.
 *
 * @param [in] a  HashAddress set.
 * @param [in] i  Tree height.
 */
#define HA_SetTreeHeight(a, i)      (a)[6] = (word32)(i)
/* Set tree height as big-endian into HashAddress.
 *
 * FIPS 205. Section 4.3. Table 1. Line 5. But encode value big-endian.
 *
 * @param [in] a  HashAddress set.
 * @param [in] i  Tree height.
 */
#define HA_SetTreeHeightBE(a, i)    c32toa((word32)(i), (a) + (6 * 4))
/* Set hash address into HashAddress.
 *
 * FIPS 205. Section 4.3. Table 1. Line 6.
 *
 * @param [in] a  HashAddress set.
 * @param [in] i  Hash address.
 */
#define HA_SetHashAddress(a, i)     (a)[7] = (word32)(i)
/* Set tree index into HashAddress.
 *
 * FIPS 205. Section 4.3. Table 1. Line 6.
 *
 * @param [in] a  HashAddress set.
 * @param [in] i  Tree index.
 */
#define HA_SetTreeIndex(a, i)       (a)[7] = (word32)(i)
/* Copy key pair address from one HashAddress to another.
 *
 * FIPS 205. Section 4.3. Table 1. Line 4 and 7.
 *
 * @param [in] a  HashAddress to copy into.
 * @param [in] b  HashAddress to copy from.
 */
#define HA_CopyKeyPairAddress(a, b) (a)[5] = (b)[5]

/* FIPS 205. Section 4.3. Table 1. Line 8 - Get tree index is not needed as
 * index is set and index value modified before being set again.
 */

/* HashAddress type. */
typedef word32 HashAddress[8];

/* Encode a HashAddress.
 *
 * @param [in]  adrs     HashAddress to encode.
 * @param [out] address  Buffer to encode into.
 */
static void HA_Encode(const word32* adrs, byte* address)
{
#ifndef WOLFSSL_WC_SLHDSA_SMALL
    c32toa(adrs[0], address + (0 * 4));
    c32toa(adrs[1], address + (1 * 4));
    c32toa(adrs[2], address + (2 * 4));
    c32toa(adrs[3], address + (3 * 4));
    c32toa(adrs[4], address + (4 * 4));
    c32toa(adrs[5], address + (5 * 4));
    c32toa(adrs[6], address + (6 * 4));
    c32toa(adrs[7], address + (7 * 4));
#else
    int i;

    for (i = 0; i < 8; i++) {
        c32toa(adrs[i], address + (i * 4));
    }
#endif
}

/******************************************************************************
 * Index Tree - 3 x 32-bit words
 ******************************************************************************/

/* Mask the tree index.
 *
 * @param [in] t     Tree index.
 * @param [in] mask  Mask to apply to index.
 * @return  Masked tree index.
 */
#define INDEX_TREE_MASK(t, mask)    ((t)[2] & (mask))

/* Shift the tree index down by a number of bits.
 *
 * @param [in] t  Tree index.
 * @param [in] b  Number of bits to shift.
 */
#define INDEX_TREE_SHIFT_DOWN(t, b)                     \
    (t)[2] = ((t)[1] << (32 - (b))) | ((t)[2] >> (b));  \
    (t)[1] =                           (t)[1] >> (b);

/******************************************************************************
 * Parameters
 ******************************************************************************/

/* Create parameter entry.
 *
 * Other parameters:
 *   len = 2 * n + 3
 *   dl1 = upper((k * a) / 8)
 *   dl2 = upper((h - (h / d)) / 8)
 *   dl3 = upper(h / (8 * d))
 *   sigLen = Root +     FORS SK + FORS AUTH + d * (XMSS SIG + XMSS AUTH)
 *           (   1 +           k +     k * a + d * (      h2 +       len)) * n
 *
 * @param [in] p    Parameter name.
 * @param [in] n    Hash size in bytes.
 * @param [in] h    Total tree height.
 * @param [in] d    Depth of subtree.
 * @param [in] h_m  Height of message tree - XMSS tree.
 * @param [in] a    Number of authentication nodes.
 * @param [in] k    Number of FORS signatures.
 */
#define SLHDSA_PARAMETERS(p, n, h, d, h_m, a, k)    \
    { (p), (n), (h), (d), (h_m), (a), (k),          \
      2 * (n) + 3,                                  \
      (((k) * (a)) + 7) / 8,                        \
      (((h) - ((h) / (d))) + 7) / 8,                \
      ((h) + ((8 * (d)) - 1)) / (8 * (d)),          \
      (1 + (k) * (1 + (a)) + (d) * ((h_m) + 2*(n) + 3)) * (n) }

/* An array of known parameters.
 *
 * FIPS 205. Section 11. Table 2.
 */
static const SlhDsaParameters SlhDsaParams[] =
{
                                     /*  n,  h,  d, h_m,  a,  k */
#ifndef WOLFSSL_SLHDSA_PARAM_NO_128S
    SLHDSA_PARAMETERS(SLHDSA_SHAKE128S, 16, 63,  7,   9, 12, 14),
#endif
#ifndef WOLFSSL_SLHDSA_PARAM_NO_128F
    SLHDSA_PARAMETERS(SLHDSA_SHAKE128F, 16, 66, 22,   3,  6, 33),
#endif
#ifndef WOLFSSL_SLHDSA_PARAM_NO_192S
    SLHDSA_PARAMETERS(SLHDSA_SHAKE192S, 24, 63,  7,   9, 14, 17),
#endif
#ifndef WOLFSSL_SLHDSA_PARAM_NO_192F
    SLHDSA_PARAMETERS(SLHDSA_SHAKE192F, 24, 66, 22,   3,  8, 33),
#endif
#ifndef WOLFSSL_SLHDSA_PARAM_NO_256S
    SLHDSA_PARAMETERS(SLHDSA_SHAKE256S, 32, 64,  8,   8, 14, 22),
#endif
#ifndef WOLFSSL_SLHDSA_PARAM_NO_256F
    SLHDSA_PARAMETERS(SLHDSA_SHAKE256F, 32, 68, 17,   4,  9, 35),
#endif
#ifdef WOLFSSL_SLHDSA_SHA2
                                     /*  n,  h,  d, h_m,  a,  k */
#ifndef WOLFSSL_SLHDSA_PARAM_NO_SHA2_128S
    SLHDSA_PARAMETERS(SLHDSA_SHA2_128S,  16, 63,  7,   9, 12, 14),
#endif
#ifndef WOLFSSL_SLHDSA_PARAM_NO_SHA2_128F
    SLHDSA_PARAMETERS(SLHDSA_SHA2_128F,  16, 66, 22,   3,  6, 33),
#endif
#ifndef WOLFSSL_SLHDSA_PARAM_NO_SHA2_192S
    SLHDSA_PARAMETERS(SLHDSA_SHA2_192S,  24, 63,  7,   9, 14, 17),
#endif
#ifndef WOLFSSL_SLHDSA_PARAM_NO_SHA2_192F
    SLHDSA_PARAMETERS(SLHDSA_SHA2_192F,  24, 66, 22,   3,  8, 33),
#endif
#ifndef WOLFSSL_SLHDSA_PARAM_NO_SHA2_256S
    SLHDSA_PARAMETERS(SLHDSA_SHA2_256S,  32, 64,  8,   8, 14, 22),
#endif
#ifndef WOLFSSL_SLHDSA_PARAM_NO_SHA2_256F
    SLHDSA_PARAMETERS(SLHDSA_SHA2_256F,  32, 68, 17,   4,  9, 35),
#endif
#endif /* WOLFSSL_SLHDSA_SHA2 */
};

/* Number of parameters in array. */
#define SLHDSA_PARAM_LEN    \
    ((int)(sizeof(SlhDsaParams) / sizeof(SlhDsaParameters)))

/******************************************************************************
 * Hashes
 ******************************************************************************/

#ifndef WOLFSSL_WC_SLHDSA_SMALL
/* Hash three data elements with SHAKE-256.
 *
 * Will be less than WC_SHA3_256_COUNT * 8 bytes of data.
 *
 * @param [in]  shake      SHAKE-256 object.
 * @param [in]  data1      First block of data to hash.
 * @param [in]  data1_len  Length of first block of data.
 * @param [in]  adrs       Unencoded HashAddress.
 * @param [in]  data2      Second block of data to hash.
 * @param [in]  data2_len  Length of second block of data.
 * @param [out] hash       Hash output.
 * @param [in]  hash_len   Length of hash to output in bytes.
 * @return  0 on success.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_hash_shake_3(wc_Shake* shake, const byte* data1,
    byte data1_len, const word32* adrs, const byte* data2, byte data2_len,
    byte* hash, byte hash_len)
{
#ifdef WOLFSSL_SLHDSA_FULL_HASH
    int ret;
    byte address[SLHDSA_HA_SZ];

    /* Encode hash address. */
    HA_Encode(adrs, address);

    /* Update the SHAKE-256 object with first block of data. */
    ret = wc_Shake256_Update(shake, data1, data1_len);
    if (ret == 0) {
        /* Update the SHAKE-256 object with encoded HashAddress. */
        ret = wc_Shake256_Update(shake, address, SLHDSA_HA_SZ);
    }
    if (ret == 0) {
        /* Update the SHAKE-256 object with second block of data. */
        ret = wc_Shake256_Update(shake, data2, data2_len);
    }
    if (ret == 0) {
        /* Calculate and output hash. */
        ret = wc_Shake256_Final(shake, hash, hash_len);
    }

    return ret;
#elif defined(USE_INTEL_SPEEDUP)
    word64* state = shake->s;
    word8* state8 = (word8*)shake->s;
    word32 o = 0;

    /* Move the first block of data into the state. */
    XMEMCPY(state8 + o, data1, data1_len);
    o += data1_len;
    /* Encode the HashAddress into the state next. */
    HA_Encode(adrs, state8 + o);
    o += SLHDSA_HA_SZ;
    /* Move the second block of data into the state next. */
    XMEMCPY(state8 + o, data2, data2_len);
    o += data2_len;
    /* Place SHAKE end-of-content marker. */
    state8[o] = 0x1f;
    o += 1;
    /* Zero out rest of state. */
    XMEMSET(state8 + o, 0, sizeof(shake->s) - o);
    /* Place SHAKE-256 end-of-data marker. */
    state8[WC_SHA3_256_COUNT * 8 - 1] ^= 0x80;

#ifndef WC_SHA3_NO_ASM
    /* Check availability of AVX2 instructions. */
    if (IS_INTEL_AVX2(cpuid_flags) && (SAVE_VECTOR_REGISTERS2() == 0)) {
        /* Process the state using AVX2 instructions. */
        sha3_block_avx2(state);
        RESTORE_VECTOR_REGISTERS();
    }
    /* Check availability of BMI2 instructions. */
    else if (IS_INTEL_BMI2(cpuid_flags)) {
        /* Process the state using BMI2 instructions. */
        sha3_block_bmi2(state);
    }
    else
#endif
    {
        /* Process the state using C code. */
        BlockSha3(state);
    }
    /* Copy hash result, of the required length, from the state into hash. */
    XMEMCPY(hash, shake->s, hash_len);

    return 0;
#else
    /* Copy the first block of data into the cached data buffer. */
    XMEMCPY(shake->t, data1, data1_len);
    /* Encode HashAddress into the cached data buffer next. */
    HA_Encode(adrs, shake->t + data1_len);
    /* Copy the second block of data into the cached data buffer next. */
    XMEMCPY(shake->t + data1_len + SLHDSA_HA_SZ, data2, data2_len);

    /* Update count of bytes cached. */
    shake->i = (byte)(data1_len + SLHDSA_HA_SZ + data2_len);

    /* Calculate and output hash. */
    return wc_Shake256_Final(shake, hash, hash_len);
#endif
}
#endif

/* Hash four data elements with SHAKE-256.
 *
 * Will be less than WC_SHA3_256_COUNT * 8 bytes of data.
 *
 * @param [in]  shake      SHAKE-256 object.
 * @param [in]  data1      First block of data to hash.
 * @param [in]  data1_len  Length of first block of data.
 * @param [in]  adrs       Unencoded HashAddress.
 * @param [in]  data2      Second block of data to hash.
 * @param [in]  data2_len  Length of second block of data.
 * @param [in]  data3      Third block of data to hash.
 * @param [in]  data3_len  Length of third block of data.
 * @param [out] hash       Hash output.
 * @param [in]  hash_len   Length of hash to output in bytes.
 * @return  0 on success.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_hash_shake_4(wc_Shake* shake, const byte* data1,
    byte data1_len, const word32* adrs, const byte* data2, byte data2_len,
    const byte* data3, byte data3_len, byte* hash, byte hash_len)
{
#ifdef WOLFSSL_SLHDSA_FULL_HASH
    int ret;
    byte address[SLHDSA_HA_SZ];

    /* Encode hash address. */
    HA_Encode(adrs, address);

    /* Update the SHAKE-256 object with first block of data. */
    ret = wc_Shake256_Update(shake, data1, data1_len);
    if (ret == 0) {
        /* Update the SHAKE-256 object with encoded HashAddress. */
        ret = wc_Shake256_Update(shake, address, SLHDSA_HA_SZ);
    }
    if (ret == 0) {
        /* Update the SHAKE-256 object with second block of data. */
        ret = wc_Shake256_Update(shake, data2, data2_len);
    }
    if (ret == 0) {
        /* Update the SHAKE-256 object with third block of data. */
        ret = wc_Shake256_Update(shake, data3, data3_len);
    }
    if (ret == 0) {
        /* Calculate and output hash. */
        ret = wc_Shake256_Final(shake, hash, hash_len);
    }

    return ret;
#elif defined(USE_INTEL_SPEEDUP)
    word64* state = shake->s;
    word8* state8 = (word8*)shake->s;
    word32 o = 0;

    /* Move the first block of data into the state. */
    XMEMCPY(state8 + o, data1, data1_len);
    o += data1_len;
    /* Encode the HashAddress into the state next. */
    HA_Encode(adrs, state8 + o);
    o += SLHDSA_HA_SZ;
    /* Move the second block of data into the state next. */
    XMEMCPY(state8 + o, data2, data2_len);
    o += data2_len;
    /* Move the third block of data into the state next. */
    XMEMCPY(state8 + o, data3, data3_len);
    o += data3_len;
    /* Place SHAKE end-of-content marker. */
    state8[o] = 0x1f;
    o += 1;
    /* Zero out rest of state. */
    XMEMSET(state8 + o, 0, sizeof(shake->s) - o);
    /* Place SHAKE-256 end-of-data marker. */
    state8[WC_SHA3_256_COUNT * 8 - 1] ^= 0x80;

#ifndef WC_SHA3_NO_ASM
    /* Check availability of AVX2 instructions. */
    if (IS_INTEL_AVX2(cpuid_flags) && (SAVE_VECTOR_REGISTERS2() == 0)) {
        /* Process the state using AVX2 instructions. */
        sha3_block_avx2(state);
        RESTORE_VECTOR_REGISTERS();
    }
    /* Check availability of BMI2 instructions. */
    else if (IS_INTEL_BMI2(cpuid_flags)) {
        /* Process the state using BMI2 instructions. */
        sha3_block_bmi2(state);
    }
    else
#endif
    {
        /* Process the state using C code. */
        BlockSha3(state);
    }
    /* Copy hash result, of the required length, from the state into hash. */
    XMEMCPY(hash, shake->s, hash_len);

    return 0;
#else
    /* Copy the first block of data into the cached data buffer. */
    XMEMCPY(shake->t, data1, data1_len);
    /* Encode HashAddress into the cached data buffer next. */
    HA_Encode(adrs, shake->t + data1_len);
    /* Copy the second block of data into the cached data buffer next. */
    XMEMCPY(shake->t + data1_len + SLHDSA_HA_SZ, data2, data2_len);
    /* Copy the third block of data into the cached data buffer next. */
    XMEMCPY(shake->t + data1_len + SLHDSA_HA_SZ + data2_len, data3, data3_len);

    /* Update count of bytes cached. */
    shake->i = (byte)(data1_len + SLHDSA_HA_SZ + data2_len + data3_len);

    /* Calculate and output hash. */
    return wc_Shake256_Final(shake, hash, hash_len);
#endif
}

/******************************************************************************
 * SHA2 Hash Functions (FIPS 205, Section 11.2)
 ******************************************************************************/

#ifdef WOLFSSL_SLHDSA_SHA2

/* Size of compressed HashAddress (ADRS^c) per FIPS 205 Section 11.2. */
#define SLHDSA_HAC_SZ   22

/* Encode a compressed HashAddress (ADRS^c).
 *
 * FIPS 205. Section 11.2.
 *   Byte 0:      low byte of adrs[0] (layer)
 *   Bytes 1-8:   adrs[2..3] (low 8 bytes of tree address)
 *   Byte 9:      low byte of adrs[4] (type)
 *   Bytes 10-21: adrs[5..7] (remaining 12 bytes, verbatim)
 *
 * @param [in]  adrs     HashAddress to encode (8 x word32).
 * @param [out] address  Buffer to encode into (22 bytes).
 */
static void HA_Encode_Compressed(const word32* adrs, byte* address)
{
    /* Byte 0: low byte of layer address. */
    address[0] = (byte)adrs[0];
    /* Bytes 1-4: adrs[2] (tree address high word). */
    c32toa(adrs[2], address + 1);
    /* Bytes 5-8: adrs[3] (tree address low word). */
    c32toa(adrs[3], address + 5);
    /* Byte 9: low byte of type. */
    address[9] = (byte)adrs[4];
    /* Bytes 10-13: adrs[5] (key pair address / padding). */
    c32toa(adrs[5], address + 10);
    /* Bytes 14-17: adrs[6] (chain address / tree height). */
    c32toa(adrs[6], address + 14);
    /* Bytes 18-21: adrs[7] (hash address / tree index). */
    c32toa(adrs[7], address + 18);
}

/* Pre-compute SHA2 midstates for PK.seed.
 *
 * SHA-256: PK.seed || pad(64 - n) is exactly one 64-byte block.
 * SHA-512: PK.seed || pad(128 - n) is exactly one 128-byte block.
 *
 * @param [in, out] key  SLH-DSA key with pk_seed set at key->sk[2*n].
 * @return  0 on success.
 */
static int slhdsakey_precompute_sha2_midstates(SlhDsaKey* key)
{
    int ret = 0;
    byte n = key->params->n;
    const byte* pk_seed = key->sk + 2 * n;
    byte block[WC_SHA512_BLOCK_SIZE];

    /* SHA-256 midstate: PK.seed || zeros to fill 64-byte block. */
    XMEMSET(block, 0, WC_SHA256_BLOCK_SIZE);
    XMEMCPY(block, pk_seed, n);
    if (key->hash.sha2.sha256_mid_inited) {
        wc_Sha256Free(&key->hash.sha2.sha256_mid);
        key->hash.sha2.sha256_mid_inited = 0;
    }
    ret = wc_InitSha256(&key->hash.sha2.sha256_mid);
    if (ret == 0) {
        key->hash.sha2.sha256_mid_inited = 1;
        ret = wc_Sha256Update(&key->hash.sha2.sha256_mid, block,
            WC_SHA256_BLOCK_SIZE);
    }

    /* SHA-512 midstate: PK.seed || zeros to fill 128-byte block.
     * Only needed for categories 3 and 5 (n > 16). */
    if ((ret == 0) && (n > 16)) {
        XMEMSET(block, 0, WC_SHA512_BLOCK_SIZE);
        XMEMCPY(block, pk_seed, n);
        if (key->hash.sha2.sha512_mid_inited) {
            wc_Sha512Free(&key->hash.sha2.sha512_mid);
            key->hash.sha2.sha512_mid_inited = 0;
        }
        ret = wc_InitSha512(&key->hash.sha2.sha512_mid);
        if (ret == 0) {
            key->hash.sha2.sha512_mid_inited = 1;
            ret = wc_Sha512Update(&key->hash.sha2.sha512_mid, block,
                WC_SHA512_BLOCK_SIZE);
        }
    }

    return ret;
}

/* SHA2 F function.
 *
 * FIPS 205. Section 11.2.
 *   F(PK.seed, ADRS, M1) = Trunc_n(SHA-256(PK.seed||pad(64-n)||ADRS^c||M1))
 *
 * Uses pre-computed midstate for the first block.
 *
 * @param [in]  key      SLH-DSA key (SHA2 hash objects + midstate).
 * @param [in]  pk_seed  Public key seed (unused - midstate).
 * @param [in]  adrs     HashAddress.
 * @param [in]  m        Message of n bytes.
 * @param [in]  n        Number of bytes in hash output.
 * @param [out] hash     Buffer to hold hash output.
 * @return  0 on success.
 */
static int slhdsakey_hash_f_sha2(SlhDsaKey* key, const byte* pk_seed,
    const word32* adrs, const byte* m, byte n, byte* hash)
{
    int ret;
    byte address[SLHDSA_HAC_SZ];
    byte digest[WC_SHA256_DIGEST_SIZE];

    (void)pk_seed;

    /* Encode compressed address. */
    HA_Encode_Compressed(adrs, address);

    /* Restore SHA-256 midstate. */

    if (key->hash.sha2.sha256_inited) {
        wc_Sha256Free(&key->hash.sha2.sha256);
        key->hash.sha2.sha256_inited = 0;
    }
    ret = wc_Sha256Copy(&key->hash.sha2.sha256_mid, &key->hash.sha2.sha256);
    if (ret == 0) {
        key->hash.sha2.sha256_inited = 1;
        /* Update with compressed ADRS and message. */
        ret = wc_Sha256Update(&key->hash.sha2.sha256, address, SLHDSA_HAC_SZ);
    }
    if (ret == 0) {
        ret = wc_Sha256Update(&key->hash.sha2.sha256, m, n);
    }
    if (ret == 0) {
        ret = wc_Sha256Final(&key->hash.sha2.sha256, digest);
    }
    if (ret == 0) {
        /* Truncate to n bytes. */
        XMEMCPY(hash, digest, n);
    }

    return ret;
}

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
/* SHA2 H function.
 *
 * FIPS 205. Section 11.2.
 *   Cat 1: H(PK.seed, ADRS, M2) = Trunc_n(SHA-256(PK.seed||pad||ADRS^c||M2))
 *   Cat 3,5: H(PK.seed, ADRS, M2) = Trunc_n(SHA-512(PK.seed||pad||ADRS^c||M2))
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  pk_seed  Public key seed (unused - midstate).
 * @param [in]  adrs     HashAddress.
 * @param [in]  node     Message of 2n bytes.
 * @param [in]  n        Number of bytes in hash output.
 * @param [out] hash     Buffer to hold hash output.
 * @return  0 on success.
 */
static int slhdsakey_hash_h_sha2(SlhDsaKey* key, const byte* pk_seed,
    const word32* adrs, const byte* node, byte n, byte* hash)
{
    int ret;
    byte address[SLHDSA_HAC_SZ];

    (void)pk_seed;

    /* Encode compressed address. */
    HA_Encode_Compressed(adrs, address);

    if (n == WC_SLHDSA_N_128) {
        /* Category 1: use SHA-256. */
        byte digest[WC_SHA256_DIGEST_SIZE];

        if (key->hash.sha2.sha256_inited) {
            wc_Sha256Free(&key->hash.sha2.sha256);
            key->hash.sha2.sha256_inited = 0;
        }
        ret = wc_Sha256Copy(&key->hash.sha2.sha256_mid,
            &key->hash.sha2.sha256);
        if (ret == 0) {
            key->hash.sha2.sha256_inited = 1;
            ret = wc_Sha256Update(&key->hash.sha2.sha256, address,
                SLHDSA_HAC_SZ);
        }
        if (ret == 0) {
            ret = wc_Sha256Update(&key->hash.sha2.sha256, node, 2U * n);
        }
        if (ret == 0) {
            ret = wc_Sha256Final(&key->hash.sha2.sha256, digest);
        }
        if (ret == 0) {
            XMEMCPY(hash, digest, n);
        }
    }
    else {
        /* Categories 3, 5: use SHA-512. */
        byte digest[WC_SHA512_DIGEST_SIZE];

        if (key->hash.sha2.sha512_inited) {
            wc_Sha512Free(&key->hash.sha2.sha512);
            key->hash.sha2.sha512_inited = 0;
        }
        ret = wc_Sha512Copy(&key->hash.sha2.sha512_mid,
            &key->hash.sha2.sha512);
        if (ret == 0) {
            key->hash.sha2.sha512_inited = 1;
            ret = wc_Sha512Update(&key->hash.sha2.sha512, address,
                SLHDSA_HAC_SZ);
        }
        if (ret == 0) {
            ret = wc_Sha512Update(&key->hash.sha2.sha512, node, 2U * n);
        }
        if (ret == 0) {
            ret = wc_Sha512Final(&key->hash.sha2.sha512, digest);
        }
        if (ret == 0) {
            XMEMCPY(hash, digest, n);
        }
    }

    return ret;
}
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

/* SHA2 H function with two separate n-byte halves.
 *
 * Same as slhdsakey_hash_h_sha2 but M2 = m1 || m2.
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  pk_seed  Public key seed (unused - midstate).
 * @param [in]  adrs     HashAddress.
 * @param [in]  m1       First n bytes of message.
 * @param [in]  m2       Second n bytes of message.
 * @param [in]  n        Number of bytes in hash output.
 * @param [out] hash     Buffer to hold hash output.
 * @return  0 on success.
 */
static int slhdsakey_hash_h_2_sha2(SlhDsaKey* key, const byte* pk_seed,
    const word32* adrs, const byte* m1, const byte* m2, byte n, byte* hash)
{
    int ret;
    byte address[SLHDSA_HAC_SZ];

    (void)pk_seed;

    /* Encode compressed address. */
    HA_Encode_Compressed(adrs, address);

    if (n == WC_SLHDSA_N_128) {
        /* Category 1: use SHA-256. */
        byte digest[WC_SHA256_DIGEST_SIZE];

        if (key->hash.sha2.sha256_inited) {
            wc_Sha256Free(&key->hash.sha2.sha256);
            key->hash.sha2.sha256_inited = 0;
        }
        ret = wc_Sha256Copy(&key->hash.sha2.sha256_mid,
            &key->hash.sha2.sha256);
        if (ret == 0) {
            key->hash.sha2.sha256_inited = 1;
            ret = wc_Sha256Update(&key->hash.sha2.sha256, address,
                SLHDSA_HAC_SZ);
        }
        if (ret == 0) {
            ret = wc_Sha256Update(&key->hash.sha2.sha256, m1, n);
        }
        if (ret == 0) {
            ret = wc_Sha256Update(&key->hash.sha2.sha256, m2, n);
        }
        if (ret == 0) {
            ret = wc_Sha256Final(&key->hash.sha2.sha256, digest);
        }
        if (ret == 0) {
            XMEMCPY(hash, digest, n);
        }
    }
    else {
        /* Categories 3, 5: use SHA-512. */
        byte digest[WC_SHA512_DIGEST_SIZE];

        if (key->hash.sha2.sha512_inited) {
            wc_Sha512Free(&key->hash.sha2.sha512);
            key->hash.sha2.sha512_inited = 0;
        }
        ret = wc_Sha512Copy(&key->hash.sha2.sha512_mid,
            &key->hash.sha2.sha512);
        if (ret == 0) {
            key->hash.sha2.sha512_inited = 1;
            ret = wc_Sha512Update(&key->hash.sha2.sha512, address,
                SLHDSA_HAC_SZ);
        }
        if (ret == 0) {
            ret = wc_Sha512Update(&key->hash.sha2.sha512, m1, n);
        }
        if (ret == 0) {
            ret = wc_Sha512Update(&key->hash.sha2.sha512, m2, n);
        }
        if (ret == 0) {
            ret = wc_Sha512Final(&key->hash.sha2.sha512, digest);
        }
        if (ret == 0) {
            XMEMCPY(hash, digest, n);
        }
    }

    return ret;
}

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
/* SHA2 PRF function.
 *
 * FIPS 205. Section 11.2.
 *   PRF(PK.seed, SK.seed, ADRS) =
 *       Trunc_n(SHA-256(PK.seed || pad(64-n) || ADRS^c || SK.seed))
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  pk_seed  Public key seed (unused - midstate).
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  adrs     HashAddress.
 * @param [in]  n        Number of bytes in hash output.
 * @param [out] hash     Buffer to hold hash output.
 * @return  0 on success.
 */
static int slhdsakey_hash_prf_sha2(SlhDsaKey* key, const byte* pk_seed,
    const byte* sk_seed, const word32* adrs, byte n, byte* hash)
{
    int ret;
    byte address[SLHDSA_HAC_SZ];
    byte digest[WC_SHA256_DIGEST_SIZE];

    (void)pk_seed;

    /* Encode compressed address. */
    HA_Encode_Compressed(adrs, address);

    /* Restore SHA-256 midstate. */
    if (key->hash.sha2.sha256_inited) {
        wc_Sha256Free(&key->hash.sha2.sha256);
        key->hash.sha2.sha256_inited = 0;
    }
    ret = wc_Sha256Copy(&key->hash.sha2.sha256_mid, &key->hash.sha2.sha256);
    if (ret == 0) {
        key->hash.sha2.sha256_inited = 1;
        ret = wc_Sha256Update(&key->hash.sha2.sha256, address, SLHDSA_HAC_SZ);
    }
    if (ret == 0) {
        ret = wc_Sha256Update(&key->hash.sha2.sha256, sk_seed, n);
    }
    if (ret == 0) {
        ret = wc_Sha256Final(&key->hash.sha2.sha256, digest);
    }
    if (ret == 0) {
        XMEMCPY(hash, digest, n);
    }

    return ret;
}
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

/* SHA2 T_l streaming: start with address.
 *
 * Restores midstate then updates with compressed ADRS.
 *
 * @param [in] key      SLH-DSA key.
 * @param [in] pk_seed  Public key seed (unused - midstate).
 * @param [in] adrs     HashAddress.
 * @param [in] n        Number of bytes of hash output (determines cat).
 * @return  0 on success.
 */
static int slhdsakey_hash_start_addr_sha2(SlhDsaKey* key,
    const byte* pk_seed, const word32* adrs, byte n)
{
    int ret;
    byte address[SLHDSA_HAC_SZ];

    (void)pk_seed;

    HA_Encode_Compressed(adrs, address);

    if (n == WC_SLHDSA_N_128) {
        /* Category 1: SHA-256 -- use sha256_2 (T_l must not collide with
         * sha256 which is used by F and H). */
        if (key->hash.sha2.sha256_2_inited) {
            wc_Sha256Free(&key->hash.sha2.sha256_2);
            key->hash.sha2.sha256_2_inited = 0;
        }
        ret = wc_Sha256Copy(&key->hash.sha2.sha256_mid,
            &key->hash.sha2.sha256_2);
        if (ret == 0) {
            key->hash.sha2.sha256_2_inited = 1;
            ret = wc_Sha256Update(&key->hash.sha2.sha256_2, address,
                SLHDSA_HAC_SZ);
        }
    }
    else {
        /* Categories 3, 5: SHA-512 -- use sha512_2 (T_l must not collide
         * with sha512 which is used by H). */
        if (key->hash.sha2.sha512_2_inited) {
            wc_Sha512Free(&key->hash.sha2.sha512_2);
            key->hash.sha2.sha512_2_inited = 0;
        }
        ret = wc_Sha512Copy(&key->hash.sha2.sha512_mid,
            &key->hash.sha2.sha512_2);
        if (ret == 0) {
            key->hash.sha2.sha512_2_inited = 1;
            ret = wc_Sha512Update(&key->hash.sha2.sha512_2, address,
                SLHDSA_HAC_SZ);
        }
    }

    return ret;
}

/* SHA2 T_l streaming: update with data.
 *
 * @param [in] key   SLH-DSA key.
 * @param [in] data  Data to hash.
 * @param [in] len   Length of data.
 * @return  0 on success.
 */
static int slhdsakey_hash_update_sha2(SlhDsaKey* key, const byte* data,
    word32 len)
{
    if (key->params->n == WC_SLHDSA_N_128) {
        return wc_Sha256Update(&key->hash.sha2.sha256_2, data, len);
    }
    else {
        return wc_Sha512Update(&key->hash.sha2.sha512_2, data, len);
    }
}

/* SHA2 T_l streaming: finalize.
 *
 * @param [in]  key   SLH-DSA key.
 * @param [out] hash  Output buffer.
 * @param [in]  len   Desired output length (truncate to n).
 * @return  0 on success.
 */
static int slhdsakey_hash_final_sha2(SlhDsaKey* key, byte* hash, word32 len)
{
    int ret;
    byte n = key->params->n;

    if (n == WC_SLHDSA_N_128) {
        byte digest[WC_SHA256_DIGEST_SIZE];
        ret = wc_Sha256Final(&key->hash.sha2.sha256_2, digest);
        if (ret == 0) {
            XMEMCPY(hash, digest, (len < n) ? len : n);
        }
    }
    else {
        byte digest[WC_SHA512_DIGEST_SIZE];
        ret = wc_Sha512Final(&key->hash.sha2.sha512_2, digest);
        if (ret == 0) {
            XMEMCPY(hash, digest, (len < n) ? len : n);
        }
    }

    return ret;
}

/* SHA2 T_l streaming: free internal allocations.
 *
 * @param [in]  key   SLH-DSA key.
 */
static void slhdsakey_hash_free_sha2(SlhDsaKey* key)
{
    byte n = key->params->n;

    if (n == WC_SLHDSA_N_128) {
        wc_Sha256Free(&key->hash.sha2.sha256_2);
        key->hash.sha2.sha256_2_inited = 0;
    }
    else {
        wc_Sha512Free(&key->hash.sha2.sha512_2);
        key->hash.sha2.sha512_2_inited = 0;
    }

    return;
}

/* Local MGF1 implementation for H_msg.
 *
 * FIPS 205. Section 11.2.
 *   H_msg uses MGF1-SHA-256/512(R || PK.seed || digest, m) where m is the
 *   required output length.
 *
 * @param [in]  key       SLH-DSA key (for hash objects).
 * @param [in]  seed      Seed data for MGF1.
 * @param [in]  seedLen   Length of seed.
 * @param [out] out       Output buffer.
 * @param [in]  outLen    Required output length.
 * @return  0 on success.
 */
static int slhdsakey_mgf1_sha2(SlhDsaKey* key, const byte* seed,
    word32 seedLen, byte* out, word32 outLen)
{
    int ret = 0;
    word32 counter = 0;
    word32 done = 0;
    byte n = key->params->n;

    while ((ret == 0) && (done < outLen)) {
        byte cBuf[4];
        word32 left = outLen - done;

        c32toa(counter, cBuf);

        if (n == WC_SLHDSA_N_128) {
            /* Category 1: MGF1-SHA-256. */
            byte digest[WC_SHA256_DIGEST_SIZE];
            word32 cpLen = (left < WC_SHA256_DIGEST_SIZE) ?
                left : WC_SHA256_DIGEST_SIZE;

            if (! key->hash.sha2.sha256_2_inited) {
                ret = wc_InitSha256(&key->hash.sha2.sha256_2);
                if (ret == 0)
                    key->hash.sha2.sha256_2_inited = 1;
            }
            if (ret == 0) {
                ret = wc_Sha256Update(&key->hash.sha2.sha256_2, seed, seedLen);
            }
            if (ret == 0) {
                ret = wc_Sha256Update(&key->hash.sha2.sha256_2, cBuf, 4);
            }
            if (ret == 0) {
                ret = wc_Sha256Final(&key->hash.sha2.sha256_2, digest);
            }
            if (ret == 0) {
                XMEMCPY(out + done, digest, cpLen);
                done += cpLen;
            }
        }
        else {
            /* Categories 3, 5: MGF1-SHA-512. */
            byte digest[WC_SHA512_DIGEST_SIZE];
            word32 cpLen = (left < WC_SHA512_DIGEST_SIZE) ?
                left : WC_SHA512_DIGEST_SIZE;

            if (! key->hash.sha2.sha512_2_inited) {
                ret = wc_InitSha512(&key->hash.sha2.sha512_2);
                if (ret == 0)
                    key->hash.sha2.sha512_2_inited = 1;
            }
            if (ret == 0) {
                ret = wc_Sha512Update(&key->hash.sha2.sha512_2, seed, seedLen);
            }
            if (ret == 0) {
                ret = wc_Sha512Update(&key->hash.sha2.sha512_2, cBuf, 4);
            }
            if (ret == 0) {
                ret = wc_Sha512Final(&key->hash.sha2.sha512_2, digest);
            }
            if (ret == 0) {
                XMEMCPY(out + done, digest, cpLen);
                done += cpLen;
            }
        }
        counter++;
    }

    return ret;
}

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
/* SHA2 PRF_msg function.
 *
 * FIPS 205. Section 11.2.
 *   PRF_msg(SK.prf, opt_rand, M) =
 *       Trunc_n(HMAC-SHA-256/512(SK.prf, opt_rand || M))
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sk_prf   SK.prf seed.
 * @param [in]  opt_rand Random or PK.seed.
 * @param [in]  hdr      Message header (2 bytes).
 * @param [in]  ctx      Context data (may be NULL).
 * @param [in]  ctxSz    Context data length.
 * @param [in]  msg      Message data.
 * @param [in]  msgSz    Message data length.
 * @param [in]  n        Number of bytes in hash output.
 * @param [out] hash     Buffer to hold hash output.
 * @return  0 on success.
 */
static int slhdsakey_prf_msg_sha2(SlhDsaKey* key, const byte* sk_prf,
    const byte* opt_rand, const byte* hdr, const byte* ctx, byte ctxSz,
    const byte* msg, word32 msgSz, byte n, byte* hash)
{
    int ret;
    Hmac hmac;
    int hmacType;
    byte digest[WC_SHA512_DIGEST_SIZE];

    if (n == WC_SLHDSA_N_128) {
        hmacType = WC_SHA256;
    }
    else {
        hmacType = WC_SHA512;
    }

    ret = wc_HmacInit(&hmac, key->heap, INVALID_DEVID);
    if (ret == 0) {
        ret = wc_HmacSetKey(&hmac, hmacType, sk_prf, n);
    }
    if (ret == 0) {
        ret = wc_HmacUpdate(&hmac, opt_rand, n);
    }
    if ((ret == 0) && (hdr != NULL)) {
        ret = wc_HmacUpdate(&hmac, hdr, 2);
    }
    if ((ret == 0) && (ctxSz > 0) && (ctx != NULL)) {
        ret = wc_HmacUpdate(&hmac, ctx, ctxSz);
    }
    if (ret == 0) {
        ret = wc_HmacUpdate(&hmac, msg, msgSz);
    }
    if (ret == 0) {
        ret = wc_HmacFinal(&hmac, digest);
    }
    wc_HmacFree(&hmac);

    if (ret == 0) {
        XMEMCPY(hash, digest, n);
    }

    return ret;
}
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

/* SHA2 H_msg function.
 *
 * FIPS 205. Section 11.2.
 *   H_msg(R, PK.seed, PK.root, M) = MGF1-SHA-256/512(
 *       R || PK.seed || SHA-256/512(R || PK.seed || PK.root || M), m)
 *
 * @param [in]  key    SLH-DSA key.
 * @param [in]  r      Randomizer (n bytes from signature).
 * @param [in]  hdr    Message header (2 bytes).
 * @param [in]  ctx    Context data (may be NULL).
 * @param [in]  ctxSz  Context data length.
 * @param [in]  msg    Message data.
 * @param [in]  msgSz  Message data length.
 * @param [out] md     Output message digest.
 * @param [in]  mdLen  Required digest length (dl1+dl2+dl3).
 * @return  0 on success.
 */
static int slhdsakey_h_msg_sha2(SlhDsaKey* key, const byte* r,
    const byte* hdr, const byte* ctx, byte ctxSz, const byte* msg,
    word32 msgSz, byte* md, word32 mdLen)
{
    int ret = 0;
    byte n = key->params->n;
    const byte* pk_seed = key->sk + 2 * n;
    const byte* pk_root = key->sk + 3 * n;

    if (n == WC_SLHDSA_N_128) {
        /* Category 1: SHA-256 + MGF1-SHA-256. */
        byte innerHash[WC_SHA256_DIGEST_SIZE];
        /* Seed for MGF1: R || PK.seed || innerHash. */
        byte mgfSeed[32 + 16 + WC_SHA256_DIGEST_SIZE];

        /* Step 1: innerHash = SHA-256(R || PK.seed || PK.root || M). */
        if (! key->hash.sha2.sha256_2_inited) {
            ret = wc_InitSha256(&key->hash.sha2.sha256_2);
            if (ret == 0)
                key->hash.sha2.sha256_2_inited = 1;
        }
        if (ret == 0) {
            ret = wc_Sha256Update(&key->hash.sha2.sha256_2, r, n);
        }
        if (ret == 0) {
            ret = wc_Sha256Update(&key->hash.sha2.sha256_2, pk_seed, n);
        }
        if (ret == 0) {
            ret = wc_Sha256Update(&key->hash.sha2.sha256_2, pk_root, n);
        }
        if ((ret == 0) && (hdr != NULL)) {
            ret = wc_Sha256Update(&key->hash.sha2.sha256_2, hdr, 2);
        }
        if ((ret == 0) && (ctxSz > 0) && (ctx != NULL)) {
            ret = wc_Sha256Update(&key->hash.sha2.sha256_2, ctx, ctxSz);
        }
        if (ret == 0) {
            ret = wc_Sha256Update(&key->hash.sha2.sha256_2, msg, msgSz);
        }
        if (ret == 0) {
            ret = wc_Sha256Final(&key->hash.sha2.sha256_2, innerHash);
        }

        /* Step 2: MGF1-SHA-256(R || PK.seed || innerHash, mdLen). */
        if (ret == 0) {
            XMEMCPY(mgfSeed, r, n);
            XMEMCPY(mgfSeed + n, pk_seed, n);
            XMEMCPY(mgfSeed + 2 * n, innerHash, WC_SHA256_DIGEST_SIZE);
            ret = slhdsakey_mgf1_sha2(key, mgfSeed,
                2U * n + WC_SHA256_DIGEST_SIZE, md, mdLen);
        }
    }
    else {
        /* Categories 3, 5: SHA-512 + MGF1-SHA-512. */
        byte innerHash[WC_SHA512_DIGEST_SIZE];
        /* Seed for MGF1: R || PK.seed || innerHash. */
        byte mgfSeed[32 + 32 + WC_SHA512_DIGEST_SIZE];

        /* Step 1: innerHash = SHA-512(R || PK.seed || PK.root || M). */
        if (! key->hash.sha2.sha512_2_inited) {
            ret = wc_InitSha512(&key->hash.sha2.sha512_2);
            if (ret == 0)
                key->hash.sha2.sha512_2_inited = 1;
        }
        if (ret == 0) {
            ret = wc_Sha512Update(&key->hash.sha2.sha512_2, r, n);
        }
        if (ret == 0) {
            ret = wc_Sha512Update(&key->hash.sha2.sha512_2, pk_seed, n);
        }
        if (ret == 0) {
            ret = wc_Sha512Update(&key->hash.sha2.sha512_2, pk_root, n);
        }
        if ((ret == 0) && (hdr != NULL)) {
            ret = wc_Sha512Update(&key->hash.sha2.sha512_2, hdr, 2);
        }
        if ((ret == 0) && (ctxSz > 0) && (ctx != NULL)) {
            ret = wc_Sha512Update(&key->hash.sha2.sha512_2, ctx, ctxSz);
        }
        if (ret == 0) {
            ret = wc_Sha512Update(&key->hash.sha2.sha512_2, msg, msgSz);
        }
        if (ret == 0) {
            ret = wc_Sha512Final(&key->hash.sha2.sha512_2, innerHash);
        }

        /* Step 2: MGF1-SHA-512(R || PK.seed || innerHash, mdLen). */
        if (ret == 0) {
            XMEMCPY(mgfSeed, r, n);
            XMEMCPY(mgfSeed + n, pk_seed, n);
            XMEMCPY(mgfSeed + 2 * n, innerHash, WC_SHA512_DIGEST_SIZE);
            ret = slhdsakey_mgf1_sha2(key, mgfSeed,
                2U * n + WC_SHA512_DIGEST_SIZE, md, mdLen);
        }
    }

    return ret;
}

#endif /* WOLFSSL_SLHDSA_SHA2 */

/******************************************************************************
 * Dispatching Hash Macros
 ******************************************************************************/

/* When WOLFSSL_SLHDSA_SHA2 is defined, macros dispatch between SHAKE and SHA2
 * based on the key's parameter set. When not defined, macros call SHAKE
 * directly (zero overhead). */

#ifdef WOLFSSL_SLHDSA_SHA2

/* SHAKE wrapper functions for SHA2 dispatch macros. */
static int slhdsakey_hash_f_shake(SlhDsaKey* key, const byte* pk_seed,
    const word32* adrs, const byte* m, byte n, byte* hash)
{
#ifndef WOLFSSL_WC_SLHDSA_SMALL
    return slhdsakey_hash_shake_3(&key->hash.shk.shake, pk_seed, n, adrs, m,
        n, hash, n);
#else
    return slhdsakey_hash_shake_4(&key->hash.shk.shake, pk_seed, n, adrs, m,
        n, NULL, 0, hash, n);
#endif
}

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
static int slhdsakey_hash_h_shake(SlhDsaKey* key, const byte* pk_seed,
    const word32* adrs, const byte* node, byte n, byte* hash)
{
#ifndef WOLFSSL_WC_SLHDSA_SMALL
    return slhdsakey_hash_shake_3(&key->hash.shk.shake, pk_seed, n, adrs, node,
        (byte)(2 * n), hash, n);
#else
    return slhdsakey_hash_shake_4(&key->hash.shk.shake, pk_seed, n, adrs, node,
        (byte)(2 * n), NULL, 0, hash, n);
#endif
}
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

static int slhdsakey_hash_h_2_shake(SlhDsaKey* key, const byte* pk_seed,
    const word32* adrs, const byte* m1, const byte* m2, byte n, byte* hash)
{
    return slhdsakey_hash_shake_4(&key->hash.shk.shake, pk_seed, n, adrs, m1,
        n, m2, n, hash, n);
}

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
static int slhdsakey_hash_prf_shake(SlhDsaKey* key, const byte* pk_seed,
    const byte* sk_seed, const word32* adrs, byte n, byte* hash)
{
#ifndef WOLFSSL_WC_SLHDSA_SMALL
    return slhdsakey_hash_shake_3(&key->hash.shk.shake, pk_seed, n, adrs,
        sk_seed, n, hash, n);
#else
    return slhdsakey_hash_shake_4(&key->hash.shk.shake, pk_seed, n, adrs,
        sk_seed, n, NULL, 0, hash, n);
#endif
}
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

#define HASH_PRF(k, pk_seed, sk_seed, adrs, n, o)                            \
    (SLHDSA_IS_SHA2((k)->params->param) ?                                    \
        slhdsakey_hash_prf_sha2(k, pk_seed, sk_seed, adrs, n, o) :          \
        slhdsakey_hash_prf_shake(k, pk_seed, sk_seed, adrs, n, o))

#define HASH_F(k, pk_seed, adrs, m, n, o)                                   \
    (SLHDSA_IS_SHA2((k)->params->param) ?                                    \
        slhdsakey_hash_f_sha2(k, pk_seed, adrs, m, n, o) :                  \
        slhdsakey_hash_f_shake(k, pk_seed, adrs, m, n, o))

#define HASH_H(k, pk_seed, adrs, node, n, o)                                \
    (SLHDSA_IS_SHA2((k)->params->param) ?                                    \
        slhdsakey_hash_h_sha2(k, pk_seed, adrs, node, n, o) :               \
        slhdsakey_hash_h_shake(k, pk_seed, adrs, node, n, o))

#define HASH_H_2(k, pk_seed, adrs, m1, m2, n, o)                            \
    (SLHDSA_IS_SHA2((k)->params->param) ?                                    \
        slhdsakey_hash_h_2_sha2(k, pk_seed, adrs, m1, m2, n, o) :           \
        slhdsakey_hash_h_2_shake(k, pk_seed, adrs, m1, m2, n, o))

#else /* !WOLFSSL_SLHDSA_SHA2 */

#ifndef WOLFSSL_WC_SLHDSA_SMALL
/* PRF hash.
 *
 * FIPS 205. Section 4.1.
 *   PRF(PK.seed, SK.seed, ADRS) (Bn x Bn x B32 -> Bn) is a PRF that is used to
 *   generate the secret values in WOTS+ and FORS private keys.
 * FIPS 205. Section 11.1.
 *   PRF(PK.seed, SK.seed, ADRS) = SHAKE256(PK.seed || ADRS || SK.seed, 8n)
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  adrs     HashAddress.
 * @param [in]  n        Number of bytes in hash output.
 * @param [out] hash     Buffer to hold hash output.
 * @return  0 on success.
 * @return  SHAKE-256 error return code on digest failure.
 */
#define HASH_PRF(k, pk_seed, sk_seed, adrs, n, o)                            \
    slhdsakey_hash_shake_3(&(k)->hash.shk.shake, pk_seed, n, adrs,           \
        sk_seed, n, o, n)
/* Hash F. */
#define HASH_F(k, pk_seed, adrs, m, n, o)                                    \
    slhdsakey_hash_shake_3(&(k)->hash.shk.shake, pk_seed, n, adrs, m, n,    \
        o, n)
/* Hash H. */
#define HASH_H(k, pk_seed, adrs, node, n, o)                                \
    slhdsakey_hash_shake_3(&(k)->hash.shk.shake, pk_seed, n, adrs, node,    \
        (byte)(2 * (n)), o, (n))
#else
/* PRF hash. */
#define HASH_PRF(k, pk_seed, sk_seed, adrs, n, o)                           \
    slhdsakey_hash_shake_4(&(k)->hash.shk.shake, pk_seed, n, adrs,          \
        sk_seed, n, NULL, 0, o, n)
/* Hash F. */
#define HASH_F(k, pk_seed, adrs, m, n, o)                                   \
    slhdsakey_hash_shake_4(&(k)->hash.shk.shake, pk_seed, n, adrs, m, n,   \
        NULL, 0, o, n)
/* Hash H. */
#define HASH_H(k, pk_seed, adrs, node, n, o)                                \
    slhdsakey_hash_shake_4(&(k)->hash.shk.shake, pk_seed, n, adrs, node,   \
        (byte)(2 * (n)), NULL, 0, o, n)
#endif

/* Hash H with 2n byte message as two separate n byte parameters. */
#define HASH_H_2(k, pk_seed, adrs, m1, m2, n, o)                            \
    slhdsakey_hash_shake_4(&(k)->hash.shk.shake, pk_seed, n, adrs, m1, n,  \
        m2, n, o, n)

#endif /* WOLFSSL_SLHDSA_SHA2 */

/* T_l streaming dispatch macros for the secondary hash (used by WOTS+ pk
 * compression and FORS root computation). */
#ifdef WOLFSSL_SLHDSA_SHA2

#define HASH_T_START_ADDR(k, pk_seed, adrs, n)                              \
    (SLHDSA_IS_SHA2((k)->params->param) ?                                    \
        slhdsakey_hash_start_addr_sha2(k, pk_seed, adrs, n) :               \
        slhdsakey_hash_start_addr(&(k)->hash.shk.shake2, pk_seed, adrs, n))

#define HASH_T_UPDATE(k, d, l)                                               \
    (SLHDSA_IS_SHA2((k)->params->param) ?                                    \
        slhdsakey_hash_update_sha2(k, d, l) :                                \
        slhdsakey_hash_update(&(k)->hash.shk.shake2, d, l))

#define HASH_T_FINAL(k, o, l)                                                \
    (SLHDSA_IS_SHA2((k)->params->param) ?                                    \
        slhdsakey_hash_final_sha2(k, o, l) :                                 \
        slhdsakey_hash_final(&(k)->hash.shk.shake2, o, l))

#define HASH_T_FREE(k)                                                       \
    (SLHDSA_IS_SHA2((k)->params->param) ?                                    \
        slhdsakey_hash_free_sha2(k) :                                        \
        slhdsakey_hash_free(&(k)->hash.shk.shake2))

#else

#define HASH_T_START_ADDR(k, pk_seed, adrs, n)                               \
    slhdsakey_hash_start_addr(&(k)->hash.shk.shake2, pk_seed, adrs, n)

#define HASH_T_UPDATE(k, d, l)                                               \
    slhdsakey_hash_update(&(k)->hash.shk.shake2, d, l)

#define HASH_T_FINAL(k, o, l)                                                \
    slhdsakey_hash_final(&(k)->hash.shk.shake2, o, l)

#define HASH_T_FREE(k)                                                       \
    slhdsakey_hash_free(&(k)->hash.shk.shake2)

#endif /* WOLFSSL_SLHDSA_SHA2 */

/* Start hashing with SHAKE-256.
 *
 * @param [in] shake  SHAKE-256 object.
 * @param [in] data   First block of data to hash.
 * @param [in] len    Length in bytes of first block of data.
 * @return  0 on success.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_hash_start(wc_Shake* shake, const byte* data, byte len)
{
#if defined(USE_INTEL_SPEEDUP)
    /* Clear state for new hash. */
    XMEMSET(shake->s, 0, sizeof(shake->s));
#endif
#ifdef WOLFSSL_SLHDSA_FULL_HASH
    /* Update the hash. */
    return wc_Shake256_Update(shake, data, len);
#else
    /* Copy the data to hash into the cache and update cached length. */
    XMEMCPY(shake->t, data, len);
    shake->i = (byte)len;

    return 0;
#endif
}

/* Start hashing with SHAKE-256. HashAddress to update too.
 *
 * @param [in] shake    SHAKE-256 object.
 * @param [in] pk_seed  Public key seed - a hash output.
 * @param [in] adrs     HashAddress.
 * @param [in] n        Number of bytes in hash output.
 * @return  0 on success.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_hash_start_addr(wc_Shake* shake, const byte* pk_seed,
    const word32* adrs, byte n)
{
#ifdef WOLFSSL_SLHDSA_FULL_HASH
    int ret;
    byte address[SLHDSA_HA_SZ];

    /* Encode HashAddress. */
    HA_Encode(adrs, address);

#if defined(USE_INTEL_SPEEDUP)
    /* Clear state for new hash. */
    XMEMSET(shake->s, 0, sizeof(shake->s));
#endif
    /* Update the hash with the public key seed. */
    ret = wc_Shake256_Update(shake, pk_seed, n);
    if (ret == 0) {
        /* Update the hash with the encoded HashAddress. */
        ret = wc_Shake256_Update(shake, address, SLHDSA_HA_SZ);
    }

    return ret;
#else
#if defined(USE_INTEL_SPEEDUP)
    /* Clear state for new hash. */
    XMEMSET(shake->s, 0, sizeof(shake->s));
#endif
    /* Copy the data to hash into the cache and update cached length. */
    XMEMCPY(shake->t, pk_seed, n);
    HA_Encode(adrs, shake->t + n);
    shake->i = (byte)(n + SLHDSA_HA_SZ);

    return 0;
#endif
}

/* Update the hash with more data.
 *
 * @param [in] shake  SHAKE-256 object.
 * @param [in] data   Block of data to hash.
 * @param [in] len    Length in bytes of first block of data.
 * @return  0 on success.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_hash_update(wc_Shake* shake, const byte* data, word32 len)
{
    return wc_Shake256_Update(shake, data, len);
}

/* Calculate and output hash.
 *
 * @param [in]  shake  SHAKE-256 object.
 * @param [out] hash   Hash output.
 * @param [in]  len    Length of hash to output in bytes.
 * @return  0 on success.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_hash_final(wc_Shake* shake, byte* hash, word32 len)
{
    return wc_Shake256_Final(shake, hash, len);
}

/* Free internal resources.
 *
 * @param [in]  shake  SHAKE-256 object.
 */
static void slhdsakey_hash_free(wc_Shake* shake)
{
    wc_Shake256_Free(shake);
}

/******************************************************************************
 * Conversion functions
 ******************************************************************************/

/* Convert array of bytes to array of b-bit values.
 *
 * b is 6, 8, 9, 12 or 14.
 *
 * FIPS 205. Section 4.4. Algorithm 4.
 * base_2b(X, b, out_len)
 *   1: in <- 0
 *   2: bits <- 0
 *   3: total <- 0
 *   4: for out from 0 to out_len - 1 do
 *   5:     while bits < b do
 *   6:         total <- (total << 8) + X[in]
 *   7:         in <- in + 1
 *   8:         bits <- bits + 8
 *   9:     end while
 *  10:     bits <- bits - b
 *  11:     baseb[out] <- (total >> bits mod 2^b
 *  12: end for
 *  13: return baseb
 *
 * @param [in]  x       Array of bytes.
 * @param [in]  b       Number of bits.
 * @param [in]  outLen  Length of output array.
 * @param [out] baseb   Array of b-bit values.
 */
static void slhdsakey_base_2b(const byte* x, byte b, byte outLen, word16* baseb)
{
    int j;
    int i = 0;
    int bits = 0;
    int total = 0;
    word16 mask = (word16)((1 << b) - 1);

    for (j = 0; j < outLen; j++) {
        while (bits < b) {
            total = (total << 8) + x[i++];
            bits += 8;
        }
        bits -= b;
        baseb[j] = (word16)((total >> bits) & mask);
    }
}

/******************************************************************************
 * WOTS+
 ******************************************************************************/

/* Iterate the hash function s times.
 *
 * FIPS 205. Section 5. Algorithm 5.
 * chain(X, i, s, PK.seed, ADRS)
 *   1: tmp <- X
 *   2: for j from i to i + s - 1 do
 *   3:     ADRS.setHashAddress(j)
 *   4:     tmp <- F(PK.seed, ADRS, tmp
 *   5: end for
 *   6: return tmp
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  x        n-byte string.
 * @param [in]  i        Start index iterations.
 * @param [in]  s        Number of times to iterate.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     HashAddress.
 * @param [out] node     Hash output - n bytes.
 * @return  0 on success.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_chain(SlhDsaKey* key, const byte* x, byte i, byte s,
    const byte* pk_seed, word32* adrs, byte* node)
{
    int ret = 0;
    int j;
    byte n = key->params->n;

    /* When no steps, copy. */
    if (s == 0) {
        /* Only copy when input and output buffers different. */
        if (x != node) {
            XMEMCPY(node, x, n);
        }
    }
    else {
        /* Set the hash address for first iteration. */
        HA_SetHashAddress(adrs, i);
        /* First iteration of hash using input and writing to output buffers. */
        ret = HASH_F(key, pk_seed, adrs, x, n, node);
        if (ret == 0) {
            for (j = i + 1; j < i + s; j++) {
                /* Set the hash address. */
                HA_SetHashAddress(adrs, j);
                /* Iterate hash using output buffer as input. */
                ret = HASH_F(key, pk_seed, adrs, node, n, node);
                if (ret != 0) {
                    break;
                }
            }
        }
    }

    return ret;
}

#if defined(USE_INTEL_SPEEDUP) && !defined(WOLFSSL_WC_SLHDSA_SMALL)
#ifndef WOLFSSL_SLHDSA_PARAM_NO_128
/* Set into SHAKE-256 x4 state the 16-byte seed and encoded HashAddress.
 *
 * @param [in, out] state  SHAKE-256 x4 state.
 * @param [in]      seed   Seed at start of each hash.
 * @param [in]      addr   Encoded HashAddress for each hash.
 */
#define SHAKE256_SET_SEED_HA_X4_16(state, seed, addr)                       \
do {                                                                        \
    /* Set 4 copies of the seed 64-bits at a time. */                       \
    (state)[0] = (state)[1] = (state)[2] = (state)[3] =                     \
                        readUnalignedWord64((seed) + (0 * sizeof(word64))); \
    (state)[4] = (state)[5] = (state)[6] = (state)[7] =                     \
                        readUnalignedWord64((seed) + (1 * sizeof(word64))); \
    /* 32 bytes copied 8 bytes at a time. */                                \
    (state)[ 8] = (state)[ 9] = (state)[10] = (state)[11] =                 \
                        readUnalignedWord64((addr) + (0 * sizeof(word64))); \
    (state)[12] = (state)[13] = (state)[14] = (state)[15] =                 \
                        readUnalignedWord64((addr) + (1 * sizeof(word64))); \
    (state)[16] = (state)[17] = (state)[18] = (state)[19] =                 \
                        readUnalignedWord64((addr) + (2 * sizeof(word64))); \
    (state)[20] = (state)[21] = (state)[22] = (state)[23] =                 \
                        readUnalignedWord64((addr) + (3 * sizeof(word64))); \
} while (0)

/* Append to SHAKE-256 x4 state the 16-byte hash.
 *
 * @param [in, out] state  SHAKE-256 x4 state.
 * @param [in]      hash   Hash data for each hash.
 */
#define SHAKE256_SET_HASH_X4_16(state, hash)                                \
do {                                                                        \
    (state)[24] = ((word64*)((hash) + 0 * 16))[0];                          \
    (state)[25] = ((word64*)((hash) + 1 * 16))[0];                          \
    (state)[26] = ((word64*)((hash) + 2 * 16))[0];                          \
    (state)[27] = ((word64*)((hash) + 3 * 16))[0];                          \
    (state)[28] = ((word64*)((hash) + 0 * 16))[1];                          \
    (state)[29] = ((word64*)((hash) + 1 * 16))[1];                          \
    (state)[30] = ((word64*)((hash) + 2 * 16))[1];                          \
    (state)[31] = ((word64*)((hash) + 3 * 16))[1];                          \
} while (0)

/* Get the four SHAKE-256 16-byte hash results.
 *
 * @param [in]  state  SHAKE-256 x4 state.
 * @param [out] hash   Hash buffer to hold 4 16-byte hash results.
 */
#define SHAKE256_GET_HASH_X4_16(state, hash)                                \
do {                                                                        \
    ((word64*)((hash) + 0 * 16))[0] = (state)[0];                           \
    ((word64*)((hash) + 1 * 16))[0] = (state)[1];                           \
    ((word64*)((hash) + 2 * 16))[0] = (state)[2];                           \
    ((word64*)((hash) + 3 * 16))[0] = (state)[3];                           \
    ((word64*)((hash) + 0 * 16))[1] = (state)[4];                           \
    ((word64*)((hash) + 1 * 16))[1] = (state)[5];                           \
    ((word64*)((hash) + 2 * 16))[1] = (state)[6];                           \
    ((word64*)((hash) + 3 * 16))[1] = (state)[7];                           \
} while (0)
#endif

#ifndef WOLFSSL_SLHDSA_PARAM_NO_192
/* Set into SHAKE-256 x4 state the 24-byte seed and encoded HashAddress.
 *
 * @param [in, out] state  SHAKE-256 x4 state.
 * @param [in]      seed   Seed at start of each hash.
 * @param [in]      addr   Encoded HashAddress for each hash.
 */
#define SHAKE256_SET_SEED_HA_X4_24(state, seed, addr)                       \
do {                                                                        \
    (state)[0] = (state)[1] = (state)[ 2] = (state)[ 3] =                   \
                        readUnalignedWord64((seed) + (0 * sizeof(word64))); \
    (state)[4] = (state)[5] = (state)[ 6] = (state)[ 7] =                   \
                        readUnalignedWord64((seed) + (1 * sizeof(word64))); \
    (state)[8] = (state)[9] = (state)[10] = (state)[11] =                   \
                        readUnalignedWord64((seed) + (2 * sizeof(word64))); \
    /* 32 bytes copied 8 bytes at a time. */                                \
    (state)[12] = (state)[13] = (state)[14] = (state)[15] =                 \
                        readUnalignedWord64((addr) + (0 * sizeof(word64))); \
    (state)[16] = (state)[17] = (state)[18] = (state)[19] =                 \
                        readUnalignedWord64((addr) + (1 * sizeof(word64))); \
    (state)[20] = (state)[21] = (state)[22] = (state)[23] =                 \
                        readUnalignedWord64((addr) + (2 * sizeof(word64))); \
    (state)[24] = (state)[25] = (state)[26] = (state)[27] =                 \
                        readUnalignedWord64((addr) + (3 * sizeof(word64))); \
} while (0)

/* Append to SHAKE-256 x4 state the 24-byte hash.
 *
 * @param [in, out] state  SHAKE-256 x4 state.
 * @param [in]      hash   Hash data for each hash.
 */
#define SHAKE256_SET_HASH_X4_24(state, hash)                                \
do {                                                                        \
    (state)[28] = ((word64*)((hash) + 0 * 24))[0];                          \
    (state)[29] = ((word64*)((hash) + 1 * 24))[0];                          \
    (state)[30] = ((word64*)((hash) + 2 * 24))[0];                          \
    (state)[31] = ((word64*)((hash) + 3 * 24))[0];                          \
    (state)[32] = ((word64*)((hash) + 0 * 24))[1];                          \
    (state)[33] = ((word64*)((hash) + 1 * 24))[1];                          \
    (state)[34] = ((word64*)((hash) + 2 * 24))[1];                          \
    (state)[35] = ((word64*)((hash) + 3 * 24))[1];                          \
    (state)[36] = ((word64*)((hash) + 0 * 24))[2];                          \
    (state)[37] = ((word64*)((hash) + 1 * 24))[2];                          \
    (state)[38] = ((word64*)((hash) + 2 * 24))[2];                          \
    (state)[39] = ((word64*)((hash) + 3 * 24))[2];                          \
} while (0)

/* Get the four SHAKE-256 24-byte (hash) results.
 *
 * @param [in]  state  SHAKE-256 x4 state.
 * @param [out] hash   Hash buffer to hold 4 24-byte hash results.
 */
#define SHAKE256_GET_HASH_X4_24(state, hash)                                \
do {                                                                        \
    ((word64*)((hash) + 0 * 24))[0] = (state)[ 0];                          \
    ((word64*)((hash) + 1 * 24))[0] = (state)[ 1];                          \
    ((word64*)((hash) + 2 * 24))[0] = (state)[ 2];                          \
    ((word64*)((hash) + 3 * 24))[0] = (state)[ 3];                          \
    ((word64*)((hash) + 0 * 24))[1] = (state)[ 4];                          \
    ((word64*)((hash) + 1 * 24))[1] = (state)[ 5];                          \
    ((word64*)((hash) + 2 * 24))[1] = (state)[ 6];                          \
    ((word64*)((hash) + 3 * 24))[1] = (state)[ 7];                          \
    ((word64*)((hash) + 0 * 24))[2] = (state)[ 8];                          \
    ((word64*)((hash) + 1 * 24))[2] = (state)[ 9];                          \
    ((word64*)((hash) + 2 * 24))[2] = (state)[10];                          \
    ((word64*)((hash) + 3 * 24))[2] = (state)[11];                          \
} while (0)
#endif

#ifndef WOLFSSL_SLHDSA_PARAM_NO_256
/* Set into SHAKE-256 x4 state the 32-byte seed and encoded HashAddress.
 *
 * @param [in, out] state  SHAKE-256 x4 state.
 * @param [in]      seed   Seed at start of each hash.
 * @param [in]      addr   Encoded HashAddress for each hash.
 */
#define SHAKE256_SET_SEED_HA_X4_32(state, seed, addr)                       \
do {                                                                        \
    (state)[ 0] = (state)[ 1] = (state)[ 2] = (state)[ 3] =                 \
                        readUnalignedWord64((seed) + (0 * sizeof(word64))); \
    (state)[ 4] = (state)[ 5] = (state)[ 6] = (state)[ 7] =                 \
                        readUnalignedWord64((seed) + (1 * sizeof(word64))); \
    (state)[ 8] = (state)[ 9] = (state)[10] = (state)[11] =                 \
                        readUnalignedWord64((seed) + (2 * sizeof(word64))); \
    (state)[12] = (state)[13] = (state)[14] = (state)[15] =                 \
                        readUnalignedWord64((seed) + (3 * sizeof(word64))); \
    /* 32 bytes copied 8 bytes at a time. */                                \
    (state)[16] = (state)[17] = (state)[18] = (state)[19] =                 \
                        readUnalignedWord64((addr) + (0 * sizeof(word64))); \
    (state)[20] = (state)[21] = (state)[22] = (state)[23] =                 \
                        readUnalignedWord64((addr) + (1 * sizeof(word64))); \
    (state)[24] = (state)[25] = (state)[26] = (state)[27] =                 \
                        readUnalignedWord64((addr) + (2 * sizeof(word64))); \
    (state)[28] = (state)[29] = (state)[30] = (state)[31] =                 \
                        readUnalignedWord64((addr) + (3 * sizeof(word64))); \
} while (0)

/* Append to SHAKE-256 x4 state the 32-byte hash.
 *
 * @param [in, out] state  SHAKE-256 x4 state.
 * @param [in]      hash   Hash data for each hash.
 */
#define SHAKE256_SET_HASH_X4_32(state, hash)                                \
do {                                                                        \
    (state)[32] = ((word64*)((hash) + 0 * 32))[0];                          \
    (state)[33] = ((word64*)((hash) + 1 * 32))[0];                          \
    (state)[34] = ((word64*)((hash) + 2 * 32))[0];                          \
    (state)[35] = ((word64*)((hash) + 3 * 32))[0];                          \
    (state)[36] = ((word64*)((hash) + 0 * 32))[1];                          \
    (state)[37] = ((word64*)((hash) + 1 * 32))[1];                          \
    (state)[38] = ((word64*)((hash) + 2 * 32))[1];                          \
    (state)[39] = ((word64*)((hash) + 3 * 32))[1];                          \
    (state)[40] = ((word64*)((hash) + 0 * 32))[2];                          \
    (state)[41] = ((word64*)((hash) + 1 * 32))[2];                          \
    (state)[42] = ((word64*)((hash) + 2 * 32))[2];                          \
    (state)[43] = ((word64*)((hash) + 3 * 32))[2];                          \
    (state)[44] = ((word64*)((hash) + 0 * 32))[3];                          \
    (state)[45] = ((word64*)((hash) + 1 * 32))[3];                          \
    (state)[46] = ((word64*)((hash) + 2 * 32))[3];                          \
    (state)[47] = ((word64*)((hash) + 3 * 32))[3];                          \
} while (0)

/* Get the four SHAKE-256 32-byte hash results.
 *
 * @param [in]  state  SHAKE-256 x4 state.
 * @param [out] hash   Hash buffer to hold 4 32-byte hash results.
 */
#define SHAKE256_GET_HASH_X4_32(state, hash)                                \
do {                                                                        \
    ((word64*)((hash) + 0 * 32))[0] = (state)[ 0];                          \
    ((word64*)((hash) + 1 * 32))[0] = (state)[ 1];                          \
    ((word64*)((hash) + 2 * 32))[0] = (state)[ 2];                          \
    ((word64*)((hash) + 3 * 32))[0] = (state)[ 3];                          \
    ((word64*)((hash) + 0 * 32))[1] = (state)[ 4];                          \
    ((word64*)((hash) + 1 * 32))[1] = (state)[ 5];                          \
    ((word64*)((hash) + 2 * 32))[1] = (state)[ 6];                          \
    ((word64*)((hash) + 3 * 32))[1] = (state)[ 7];                          \
    ((word64*)((hash) + 0 * 32))[2] = (state)[ 8];                          \
    ((word64*)((hash) + 1 * 32))[2] = (state)[ 9];                          \
    ((word64*)((hash) + 2 * 32))[2] = (state)[10];                          \
    ((word64*)((hash) + 3 * 32))[2] = (state)[11];                          \
    ((word64*)((hash) + 0 * 32))[3] = (state)[12];                          \
    ((word64*)((hash) + 1 * 32))[3] = (state)[13];                          \
    ((word64*)((hash) + 2 * 32))[3] = (state)[14];                          \
    ((word64*)((hash) + 3 * 32))[3] = (state)[15];                          \
} while (0)
#endif

/* Set the end of the SHAKE256 x4 state.
 *
 * @param [in, out] state  SHAKE-256 x4 state.
 * @param [in]      o      Offset to end of data.
 */
#define SHAKE256_SET_END_X4(state, o)                                       \
do {                                                                        \
    /* Data end marker. */                                                  \
    (state)[(o) + 0] = (word64)0x1f;                                        \
    (state)[(o) + 1] = (word64)0x1f;                                        \
    (state)[(o) + 2] = (word64)0x1f;                                        \
    (state)[(o) + 3] = (word64)0x1f;                                        \
    XMEMSET((state) + (o) + 4, 0,                                           \
        (size_t)(25 * 4 - ((o) + 4)) * sizeof(word64));                     \
    /* SHAKE-256 (state) end marker. */                                     \
    ((word8*)((state) + 4 * WC_SHA3_256_COUNT - 4))[7] ^= 0x80;             \
    ((word8*)((state) + 4 * WC_SHA3_256_COUNT - 3))[7] ^= 0x80;             \
    ((word8*)((state) + 4 * WC_SHA3_256_COUNT - 2))[7] ^= 0x80;             \
    ((word8*)((state) + 4 * WC_SHA3_256_COUNT - 1))[7] ^= 0x80;             \
} while (0)

/* Set into SHAKE-256 x4 state the n-byte seed and encoded HashAddress.
 *
 * @param [in, out] state  SHAKE-256 x4 state.
 * @param [in]      seed   Seed at start of each hash.
 * @param [in]      addr   Encoded HashAddress for each hash.
 * @param [in]      n      Number of bytes of seed.
 * @return  Offset after seed and HashAddress.
 */
static word32 slhdsakey_shake256_set_seed_ha_x4(word64* state,
    const byte* seed, const byte* addr, int n)
{
    int i;
    word32 o = 0;

    /* Set 4 copies of the seed 64-bits at a time. */
    for (i = 0; i < n; i += 8) {
        state[o + 0] = state[o + 1] = state[o + 2] = state[o + 3] =
                readUnalignedWord64(seed + i);
        o += 4;
    }
    /* 32 bytes copied 8 bytes at a time. */
    for (i = 0; i < SLHDSA_HA_SZ; i += 8) {
        state[o + 0] = state[o + 1] = state[o + 2] = state[o + 3] =
            readUnalignedWord64(addr + i);
        o += 4;
    }

    return o;
}

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
/* Fill out SHAKE-256 x4 state with n-byte seed, encoded HashAddress and hash.
 *
 * @param [in, out] state  SHAKE-256 x4 state.
 * @param [in]      seed   Seed at start of each hash.
 * @param [in]      addr   Encoded HashAddress for each hash.
 * @param [in]      hash   Hash data to put into each hash.
 * @param [in]      n      Number of bytes of seed.
 * @return  Offset after seed and HashAddress.
 */
static word32 slhdsakey_shake256_set_seed_ha_hash_x4(word64* state,
    const byte* seed, const byte* addr, const byte* hash, int n)
{
    int i;
    word32 o;
    word32 ret;

    ret = o = slhdsakey_shake256_set_seed_ha_x4(state, seed, addr, n);
    for (i = 0; i < n; i += 8) {
        state[o + 0] = state[o + 1] = state[o + 2] = state[o + 3] =
            readUnalignedWord64(hash + i);
        o += 4;
    }

    SHAKE256_SET_END_X4(state, o);

    return ret;
}
#endif /* WOLFSSL_SLHDSA_VERIFY_ONLY */

/* Get the four SHAKE-256 n-byte hash results.
 *
 * @param [in]  state  SHAKE-256 x4 state.
 * @param [out] hash   Hash buffer to hold 4 n-byte hash results.
 * @param [in]  n      Length of each hash in bytes.
 */
static void slhdsakey_shake256_get_hash_x4(const word64* state, byte* hash,
    int n)
{
    int i;

    for (i = 0; i < (n / 8); i++) {
        ((word64*)(hash + 0 * n))[i] = state[4 * i + 0];
        ((word64*)(hash + 1 * n))[i] = state[4 * i + 1];
        ((word64*)(hash + 2 * n))[i] = state[4 * i + 2];
        ((word64*)(hash + 3 * n))[i] = state[4 * i + 3];
    }
}

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
/* Set the chain address into the SHAKE-256 x4 state.
 *
 * @param [in, out] state  SHAKE-256 x4 state.
 * @param [in]      o      Offset of state after HashAddress.
 * @param [in]      a      Value to set that increments for each hash.
 */
#define SHAKE256_SET_CHAIN_ADDRESS(state, o, a)                             \
do {                                                                        \
    ((word8*)((state) + (o) - 4))[3] = (word8)((a) + 0);                    \
    ((word8*)((state) + (o) - 3))[3] = (word8)((a) + 1);                    \
    ((word8*)((state) + (o) - 2))[3] = (word8)((a) + 2);                    \
    ((word8*)((state) + (o) - 1))[3] = (word8)((a) + 3);                    \
} while (0)
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

/* Set the chain address indices into the SHAKE-256 x4 state.
 *
 * @param [in, out] state  SHAKE-256 x4 state.
 * @param [in]      o      Offset of state after HashAddress.
 * @param [in]      idx    Indices to set for each hash.
 */
#define SHAKE256_SET_CHAIN_ADDRESS_IDX(state, o, idx)                       \
do {                                                                        \
    ((word8*)((state) + (o) - 4))[3] = (idx)[0];                            \
    ((word8*)((state) + (o) - 3))[3] = (idx)[1];                            \
    ((word8*)((state) + (o) - 2))[3] = (idx)[2];                            \
    ((word8*)((state) + (o) - 1))[3] = (idx)[3];                            \
} while (0)

/* Set the hash address into the SHAKE-256 x4 state.
 *
 * @param [in, out] state  SHAKE-256 x4 state.
 * @param [in]      (o)      Offset of state after HashAddress.
 * @param [in]      a      Value to set for each hash.
 */
#define SHAKE256_SET_HASH_ADDRESS(state, o, a)                              \
do {                                                                        \
    ((word8*)((state) + (o) - 4))[7] = (word8)(a);                          \
    ((word8*)((state) + (o) - 3))[7] = (word8)(a);                          \
    ((word8*)((state) + (o) - 2))[7] = (word8)(a);                          \
    ((word8*)((state) + (o) - 1))[7] = (word8)(a);                          \
} while (0)

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
/* Set the tree index into the SHAKE-256 x4 state.
 *
 * @param [in, out] state  SHAKE-256 x4 state.
 * @param [in]      (o)      Offset of state after HashAddress.
 * @param [in]      ti     Value to encode that increments for each hash.
 */
#define SHAKE256_SET_TREE_INDEX(state, o, ti)                               \
do {                                                                        \
    c32toa((word32)((ti) + 0), (byte*)&((word32*)((state) + (o) - 4))[1]);  \
    c32toa((word32)((ti) + 1), (byte*)&((word32*)((state) + (o) - 3))[1]);  \
    c32toa((word32)((ti) + 2), (byte*)&((word32*)((state) + (o) - 2))[1]);  \
    c32toa((word32)((ti) + 3), (byte*)&((word32*)((state) + (o) - 1))[1]);  \
} while (0)
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

/* Set the tree indices into the SHAKE-256 x4 state.
 *
 * @param [in, out] state  SHAKE-256 x4 state.
 * @param [in]      (o)      Offset of state after HashAddress.
 * @param [in]      ti     Indices to encode for each hash.
 */
#define SHAKE256_SET_TREE_INDEX_IDX(state, o, ti)                           \
do {                                                                        \
    c32toa((ti)[0], (byte*)&((word32*)((state) + (o) - 4))[1]);             \
    c32toa((ti)[1], (byte*)&((word32*)((state) + (o) - 3))[1]);             \
    c32toa((ti)[2], (byte*)&((word32*)((state) + (o) - 2))[1]);             \
    c32toa((ti)[3], (byte*)&((word32*)((state) + (o) - 1))[1]);             \
} while (0)

/* Set the tree height into the SHAKE-256 x4 state.
 *
 * @param [in, out] state  SHAKE-256 x4 state.
 * @param [in]      (o)      Offset of state after HashAddress.
 * @param [in]      ti     Value to encode for each hash.
 */
#define SHAKE256_SET_TREE_HEIGHT(state, o, th)                              \
do {                                                                        \
    c32toa((th), (byte*)&((word32*)((state) + (o) - 4))[0]);                \
    c32toa((th), (byte*)&((word32*)((state) + (o) - 3))[0]);                \
    c32toa((th), (byte*)&((word32*)((state) + (o) - 2))[0]);                \
    c32toa((th), (byte*)&((word32*)((state) + (o) - 1))[0]);                \
} while (0)

#ifndef WOLFSSL_SLHDSA_PARAM_NO_128
/* Iterate the hash function s times with 4 hashes when n=16.
 *
 * FIPS 205. Section 5. Algorithm 5.
 * chain(X, i, s, PK.seed, ADRS)
 *   1: tmp <- X
 *   2: for j from i to i + s - 1 do
 *   3:     ADRS.setHashAddress(j)
 *   4:     tmp <- F(PK.seed, ADRS, tmp
 *   5: end for
 *   6: return tmp
 *
 * @param [in, out] sk       4 hashes to iterate.
 * @param [in]      i        Start index iterations.
 * @param [in]      s        Number of times to iterate.
 * @param [in]      pk_seed  Public key seed.
 * @param [in]      addr     Encoded HashAddress.
 * @param [in]      idx      Indices for chain address.
 * @param [in]      heap     Dynamic memory allocation hint.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_chain_idx_x4_16(byte* sk, word32 i, word32 s,
    const byte* pk_seed, byte* addr, byte* idx, void* heap)
{
    int ret = 0;
    word32 j;
    WC_DECLARE_VAR(fixed, word64, 6 * 4, heap);
    WC_DECLARE_VAR(state, word64, 25 * 4, heap);

    (void)heap;

    WC_ALLOC_VAR_EX(fixed, word64, 6 * 4, heap, DYNAMIC_TYPE_SLHDSA,
        ret = MEMORY_E);
    if (ret == 0) {
        WC_ALLOC_VAR_EX(state, word64, 25 * 4, heap, DYNAMIC_TYPE_SLHDSA,
            ret = MEMORY_E);
    }
    if (ret == 0) {
        SHAKE256_SET_SEED_HA_X4_16(fixed, pk_seed, addr);
        SHAKE256_SET_CHAIN_ADDRESS_IDX(fixed, 24, idx);
        SHAKE256_SET_HASH_X4_16(state, sk);

        for (j = i; j < i + s; j++) {
            if (j != i) {
                XMEMCPY(state + 24, state, 16 * 4);
            }
            XMEMCPY(state, fixed, (6 * 4) * sizeof(word64));
            SHAKE256_SET_HASH_ADDRESS(state, 24, j);
            SHAKE256_SET_END_X4(state, 32);
            ret = SAVE_VECTOR_REGISTERS2();
            if (ret != 0)
                return ret;
            sha3_blocksx4_avx2(state);
            RESTORE_VECTOR_REGISTERS();
        }

        SHAKE256_GET_HASH_X4_16(state, sk);
    }

    WC_FREE_VAR_EX(state, heap, DYNAMIC_TYPE_SLHDSA);
    WC_FREE_VAR_EX(fixed, heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}
#endif
#ifndef WOLFSSL_SLHDSA_PARAM_NO_192
/* Iterate the hash function s times with 4 hashes when n=24.
 *
 * FIPS 205. Section 5. Algorithm 5.
 * chain(X, i, s, PK.seed, ADRS)
 *   1: tmp <- X
 *   2: for j from i to i + s - 1 do
 *   3:     ADRS.setHashAddress(j)
 *   4:     tmp <- F(PK.seed, ADRS, tmp
 *   5: end for
 *   6: return tmp
 *
 * @param [in, out] sk       4 hashes to iterate.
 * @param [in]      i        Start index iterations.
 * @param [in]      s        Number of times to iterate.
 * @param [in]      pk_seed  Public key seed.
 * @param [in]      addr     Encoded HashAddress.
 * @param [in]      idx      Indices for chain address.
 * @param [in]      heap     Dynamic memory allocation hint.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_chain_idx_x4_24(byte* sk, word32 i, word32 s,
    const byte* pk_seed, byte* addr, byte* idx, void* heap)
{
    int ret = 0;
    word32 j;
    WC_DECLARE_VAR(fixed, word64, 7 * 4, heap);
    WC_DECLARE_VAR(state, word64, 25 * 4, heap);

    (void)heap;

    WC_ALLOC_VAR_EX(fixed, word64, 7 * 4, heap, DYNAMIC_TYPE_SLHDSA,
        ret = MEMORY_E);
    if (ret == 0) {
        WC_ALLOC_VAR_EX(state, word64, 25 * 4, heap, DYNAMIC_TYPE_SLHDSA,
            ret = MEMORY_E);
    }
    if (ret == 0) {
        SHAKE256_SET_SEED_HA_X4_24(fixed, pk_seed, addr);
        SHAKE256_SET_CHAIN_ADDRESS_IDX(fixed, 28, idx);
        SHAKE256_SET_HASH_X4_24(state, sk);

        for (j = i; j < i + s; j++) {
            if (j != i) {
                XMEMCPY(state + 28, state, 24 * 4);
            }
            XMEMCPY(state, fixed, 28 * sizeof(word64));
            SHAKE256_SET_HASH_ADDRESS(state, 28, j);
            SHAKE256_SET_END_X4(state, 40);
            ret = SAVE_VECTOR_REGISTERS2();
            if (ret != 0)
                return ret;
            sha3_blocksx4_avx2(state);
            RESTORE_VECTOR_REGISTERS();
        }

        SHAKE256_GET_HASH_X4_24(state, sk);
    }

    WC_FREE_VAR_EX(state, heap, DYNAMIC_TYPE_SLHDSA);
    WC_FREE_VAR_EX(fixed, heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}
#endif
#ifndef WOLFSSL_SLHDSA_PARAM_NO_256
/* Iterate the hash function s times with 4 hashes when n=32.
 *
 * FIPS 205. Section 5. Algorithm 5.
 * chain(X, i, s, PK.seed, ADRS)
 *   1: tmp <- X
 *   2: for j from i to i + s - 1 do
 *   3:     ADRS.setHashAddress(j)
 *   4:     tmp <- F(PK.seed, ADRS, tmp
 *   5: end for
 *   6: return tmp
 *
 * @param [in, out] sk       4 hashes to iterate.
 * @param [in]      i        Start index iterations.
 * @param [in]      s        Number of times to iterate.
 * @param [in]      pk_seed  Public key seed.
 * @param [in]      addr     Encoded HashAddress.
 * @param [in]      idx      Indices for chain address.
 * @param [in]      heap     Dynamic memory allocation hint.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_chain_idx_x4_32(byte* sk, word32 i, word32 s,
    const byte* pk_seed, byte* addr, byte* idx, void* heap)
{
    int ret = 0;
    word32 j;
    WC_DECLARE_VAR(fixed, word64, 8 * 4, heap);
    WC_DECLARE_VAR(state, word64, 25 * 4, heap);

    (void)heap;

    WC_ALLOC_VAR_EX(fixed, word64, 8 * 4, heap, DYNAMIC_TYPE_SLHDSA,
        ret = MEMORY_E);
    if (ret == 0) {
        WC_ALLOC_VAR_EX(state, word64, 25 * 4, heap, DYNAMIC_TYPE_SLHDSA,
            ret = MEMORY_E);
    }
    if (ret == 0) {
        SHAKE256_SET_SEED_HA_X4_32(fixed, pk_seed, addr);
        SHAKE256_SET_CHAIN_ADDRESS_IDX(fixed, 32, idx);
        SHAKE256_SET_HASH_X4_32(state, sk);

        for (j = i; j < i + s; j++) {
            if (j != i) {
                XMEMCPY(state + 32, state, 32 * 4);
            }
            XMEMCPY(state, fixed, 32 * sizeof(word64));
            SHAKE256_SET_HASH_ADDRESS(state, 32, j);
            SHAKE256_SET_END_X4(state, 48);
            ret = SAVE_VECTOR_REGISTERS2();
            if (ret != 0)
                return ret;
            sha3_blocksx4_avx2(state);
            RESTORE_VECTOR_REGISTERS();
        }

        SHAKE256_GET_HASH_X4_32(state, sk);
    }

    WC_FREE_VAR_EX(state, heap, DYNAMIC_TYPE_SLHDSA);
    WC_FREE_VAR_EX(fixed, heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}
#endif
#endif

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
#if defined(USE_INTEL_SPEEDUP) && !defined(WOLFSSL_WC_SLHDSA_SMALL)
/* PRF hash 4 simultaneously.
 *
 * Each hash varies by the chain address with the first value in sequence passed
 * in.
 *
 * FIPS 205. Section 4.1.
 *   PRF(PK.seed, SK.seed, ADRS) (Bn x Bn x B32 -> Bn) is a PRF that is used to
 *   generate the secret values in WOTS+ and FORS private keys.
 * FIPS 205. Section 11.1.
 *   PRF(PK.seed, SK.seed, ADRS) = SHAKE256(PK.seed || ADRS || SK.seed, 8n)
 *
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  addr     Encoded HashAddress.
 * @param [in]  n        Number of bytes in hash output.
 * @param [in]  ca       Chain address start index.
 * @param [out] sk       Buffer to hold hash output.
 * @param [in]  heap     Dynamic memory allocation hint.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_hash_prf_x4(const byte* pk_seed, const byte* sk_seed,
    byte* addr, byte n, byte ca, byte* sk, void* heap)
{
    int ret = 0;
    word32 o = 0;
    WC_DECLARE_VAR(state, word64, 25 * 4, heap);

    (void)heap;

    WC_ALLOC_VAR_EX(state, word64, 25 * 4, heap, DYNAMIC_TYPE_SLHDSA,
        ret = MEMORY_E);
    if (ret == 0) {
        o = slhdsakey_shake256_set_seed_ha_hash_x4(state, pk_seed, addr,
            sk_seed, n);
        SHAKE256_SET_CHAIN_ADDRESS(state, o, ca);
        ret = SAVE_VECTOR_REGISTERS2();
        if (ret == 0) {
            sha3_blocksx4_avx2(state);
            slhdsakey_shake256_get_hash_x4(state, sk, n);
            RESTORE_VECTOR_REGISTERS();
        }

        WC_FREE_VAR_EX(state, heap, DYNAMIC_TYPE_SLHDSA);
    }

    return ret;
}

#if !defined(WOLFSSL_SLHDSA_PARAM_NO_128)
/* Iterate the hash function 15 times with 4 hashes when n=16.
 *
 * FIPS 205. Section 5. Algorithm 5.
 * chain(X, i, s, PK.seed, ADRS)
 *   1: tmp <- X
 *   2: for j from i to i + s - 1 do
 *   3:     ADRS.setHashAddress(j)
 *   4:     tmp <- F(PK.seed, ADRS, tmp
 *   5: end for
 *   6: return tmp
 *
 * @param [in, out] sk       4 hashes to iterate.
 * @param [in]      pk_seed  Public key seed.
 * @param [in]      addr     Encoded HashAddress.
 * @param [in]      ca       Chain address start index.
 * @param [in]      heap     Dynamic memory allocation hint.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_chain_x4_16(byte* sk, const byte* pk_seed, byte* addr,
    byte ca, void* heap)
{
    int ret = 0;
    int j;
    WC_DECLARE_VAR(fixed, word64, 8 * 4, heap);
    WC_DECLARE_VAR(state, word64, 25 * 4, heap);

    (void)heap;

    WC_ALLOC_VAR_EX(fixed, word64, 8 * 4, heap, DYNAMIC_TYPE_SLHDSA,
        ret = MEMORY_E);
    if (ret == 0) {
        WC_ALLOC_VAR_EX(state, word64, 25 * 4, heap, DYNAMIC_TYPE_SLHDSA,
            ret = MEMORY_E);
    }
    if (ret == 0) {
        SHAKE256_SET_SEED_HA_X4_16(fixed, pk_seed, addr);
        SHAKE256_SET_CHAIN_ADDRESS(fixed, 24, ca);
        SHAKE256_SET_HASH_X4_16(state, sk);

        for (j = 0; j < 15; j++) {
            if (j != 0) {
                XMEMCPY(state + 24, state, 16 * 4);
            }
            XMEMCPY(state, fixed, 24 * sizeof(word64));
            SHAKE256_SET_HASH_ADDRESS(state, 24, j);
            SHAKE256_SET_END_X4(state, 32);
            ret = SAVE_VECTOR_REGISTERS2();
            if (ret != 0)
                break;
            sha3_blocksx4_avx2(state);
            RESTORE_VECTOR_REGISTERS();
        }

        if (ret == 0)
            SHAKE256_GET_HASH_X4_16(state, sk);
    }

    WC_FREE_VAR_EX(state, heap, DYNAMIC_TYPE_SLHDSA);
    WC_FREE_VAR_EX(fixed, heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

#if !defined(WOLFSSL_SLHDSA_PARAM_NO_192)
/* Iterate the hash function 15 times with 4 hashes when n=24.
 *
 * FIPS 205. Section 5. Algorithm 5.
 * chain(X, i, s, PK.seed, ADRS)
 *   1: tmp <- X
 *   2: for j from i to i + s - 1 do
 *   3:     ADRS.setHashAddress(j)
 *   4:     tmp <- F(PK.seed, ADRS, tmp
 *   5: end for
 *   6: return tmp
 *
 * @param [in, out] sk       4 hashes to iterate.
 * @param [in]      pk_seed  Public key seed.
 * @param [in]      addr     Encoded HashAddress.
 * @param [in]      ca       Chain address start index.
 * @param [in]      heap     Dynamic memory allocation hint.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_chain_x4_24(byte* sk, const byte* pk_seed, byte* addr,
    byte ca, void* heap)
{
    int ret = 0;
    int j;
    WC_DECLARE_VAR(fixed, word64, 8 * 4, heap);
    WC_DECLARE_VAR(state, word64, 25 * 4, heap);

    (void)heap;

    WC_ALLOC_VAR_EX(fixed, word64, 8 * 4, heap, DYNAMIC_TYPE_SLHDSA,
        ret = MEMORY_E);
    if (ret == 0) {
        WC_ALLOC_VAR_EX(state, word64, 25 * 4, heap, DYNAMIC_TYPE_SLHDSA,
            ret = MEMORY_E);
    }
    if (ret == 0) {
        SHAKE256_SET_SEED_HA_X4_24(fixed, pk_seed, addr);
        SHAKE256_SET_CHAIN_ADDRESS(fixed, 28, ca);
        SHAKE256_SET_HASH_X4_24(state, sk);

        for (j = 0; j < 15; j++) {
            if (j != 0) {
                XMEMCPY(state + 28, state, 24 * 4);
            }
            XMEMCPY(state, fixed, 28 * sizeof(word64));
            SHAKE256_SET_HASH_ADDRESS(state, 28, j);
            SHAKE256_SET_END_X4(state, 40);
            ret = SAVE_VECTOR_REGISTERS2();
            if (ret != 0)
                break;
            sha3_blocksx4_avx2(state);
            RESTORE_VECTOR_REGISTERS();
        }

        if (ret == 0)
            SHAKE256_GET_HASH_X4_24(state, sk);
    }

    WC_FREE_VAR_EX(state, heap, DYNAMIC_TYPE_SLHDSA);
    WC_FREE_VAR_EX(fixed, heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}
#endif

#if !defined(WOLFSSL_SLHDSA_PARAM_NO_256)
/* Iterate the hash function 15 times with 4 hashes when n=32.
 *
 * FIPS 205. Section 5. Algorithm 5.
 * chain(X, i, s, PK.seed, ADRS)
 *   1: tmp <- X
 *   2: for j from i to i + s - 1 do
 *   3:     ADRS.setHashAddress(j)
 *   4:     tmp <- F(PK.seed, ADRS, tmp
 *   5: end for
 *   6: return tmp
 *
 * @param [in, out] sk       4 hashes to iterate.
 * @param [in]      pk_seed  Public key seed.
 * @param [in]      addr     Encoded HashAddress.
 * @param [in]      ca       Chain address start index.
 * @param [in]      heap     Dynamic memory allocation hint.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_chain_x4_32(byte* sk, const byte* pk_seed, byte* addr,
    byte ca, void* heap)
{
    int ret = 0;
    int j;
    WC_DECLARE_VAR(fixed, word64, 8 * 4, heap);
    WC_DECLARE_VAR(state, word64, 25 * 4, heap);

    (void)heap;

    WC_ALLOC_VAR_EX(fixed, word64, 8 * 4, heap, DYNAMIC_TYPE_SLHDSA,
        ret = MEMORY_E);
    if (ret == 0) {
        WC_ALLOC_VAR_EX(state, word64, 25 * 4, heap, DYNAMIC_TYPE_SLHDSA,
            ret = MEMORY_E);
    }
    if (ret == 0) {
        SHAKE256_SET_SEED_HA_X4_32(fixed, pk_seed, addr);
        SHAKE256_SET_CHAIN_ADDRESS(fixed, 32, ca);
        SHAKE256_SET_HASH_X4_32(state, sk);

        for (j = 0; j < 15; j++) {
            if (j != 0) {
                XMEMCPY(state + 32, state, 32 * 4);
            }
            XMEMCPY(state, fixed, 32 * sizeof(word64));
            SHAKE256_SET_HASH_ADDRESS(state, 32, j);
            SHAKE256_SET_END_X4(state, 48);
            ret = SAVE_VECTOR_REGISTERS2();
            if (ret != 0)
                break;
            sha3_blocksx4_avx2(state);
            RESTORE_VECTOR_REGISTERS();
        }

        if (ret == 0)
            SHAKE256_GET_HASH_X4_32(state, sk);
    }

    WC_FREE_VAR_EX(state, heap, DYNAMIC_TYPE_SLHDSA);
    WC_FREE_VAR_EX(fixed, heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}
#endif

/* PRF hash 4 simultaneously.
 *
 * Each hash varies by the chain address which is passed in as an array.
 *
 * FIPS 205. Section 4.1.
 *   PRF(PK.seed, SK.seed, ADRS) (Bn x Bn x B32 -> Bn) is a PRF that is used to
 *   generate the secret values in WOTS+ and FORS private keys.
 * FIPS 205. Section 11.1.
 *   PRF(PK.seed, SK.seed, ADRS) = SHAKE256(PK.seed || ADRS || SK.seed, 8n)
 *
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  addr     Encoded HashAddress.
 * @param [in]  n        Number of bytes in hash output.
 * @param [in]  idx      Four chain address indices.
 * @param [out] sk       Buffer to hold hash output.
 * @param [in]  heap     Dynamic memory allocation hint.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_hash_prf_idx_x4(const byte* pk_seed, const byte* sk_seed,
    byte* addr, byte n, byte* idx, byte* sk, void* heap)
{
    int ret = 0;
    word32 o = 0;
    WC_DECLARE_VAR(state, word64, 25 * 4, heap);

    (void)heap;

    WC_ALLOC_VAR_EX(state, word64, 25 * 4, heap, DYNAMIC_TYPE_SLHDSA,
        ret = MEMORY_E);
    if (ret == 0) {
        o = slhdsakey_shake256_set_seed_ha_hash_x4(state, pk_seed, addr,
            sk_seed, n);
        SHAKE256_SET_CHAIN_ADDRESS_IDX(state, o, idx);
        ret = SAVE_VECTOR_REGISTERS2();
        if (ret == 0) {
            sha3_blocksx4_avx2(state);
            RESTORE_VECTOR_REGISTERS();
            slhdsakey_shake256_get_hash_x4(state, sk, n);
        }

        WC_FREE_VAR_EX(state, heap, DYNAMIC_TYPE_SLHDSA);
    }

    return ret;
}

#if !defined(WOLFSSL_SLHDSA_PARAM_NO_128)
/* Iterate hash function up to index times for each of the hashes when n=16.
 *
 * FIPS 205. Section 5. Algorithm 5.
 * chain(X, i, s, PK.seed, ADRS)
 *   1: tmp <- X
 *   2: for j from i to i + s - 1 do
 *   3:     ADRS.setHashAddress(j)
 *   4:     tmp <- F(PK.seed, ADRS, tmp
 *   5: end for
 *   6: return tmp
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sk       Hashes to iterate. Data modified.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     HashAddress.
 * @param [in]  addr     Encoded HashAddress.
 * @param [in]  msg      Array of counts.
 * @param [in]  idx      Indices into array of counts.
 * @param [in]  j        Minimum number of iterations for all 4 hashes.
 * @param [in]  cnt      Number of hashes to iterate.
 * @param [out] sig      Hash results.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_chain_idx_16(SlhDsaKey* key, byte* sk,
     const byte* pk_seed, word32* adrs, byte* addr, const byte* msg, byte* idx,
     int j, int cnt, byte* sig)
{
    int ret = 0;

    /* Iterate the minimum number of iterations on all hashes. */
    if (j != 0) {
        ret = slhdsakey_chain_idx_x4_16(sk, 0U, (word32)j, pk_seed, addr, idx,
            key->heap);
    }
    if (ret == 0) {
        if (cnt > 3) {
            /* Copy out hash at index 3 as it is finished. */
            XMEMCPY(sig + idx[3] * 16, sk + 3 * 16, 16);
        }
        /* Check if more iterations needed for index 2. */
        if (msg[idx[2]] != j) {
            /* Do 4 as we can't do less. */
            ret = slhdsakey_chain_idx_x4_16(sk, (word32)j,
                    (word32)(msg[idx[2]] - j), pk_seed, addr, idx, key->heap);
            /* Update number of iterations performed. */
            j = msg[idx[2]];
        }
    }
    if (ret == 0) {
        /* Copy out hash at index 2 as it is finished. */
        XMEMCPY(sig + idx[2] * 16, sk + 2 * 16, 16);
        /* Check if more iterations needed for index 1. */
        if (msg[idx[1]] != j) {
            /* Do 4 as we can't do less. */
            ret = slhdsakey_chain_idx_x4_16(sk, (word32)j,
                    (word32)(msg[idx[1]] - j), pk_seed, addr, idx, key->heap);
            /* Update number of iterations performed. */
            j = msg[idx[1]];
        }
    }
    if (ret == 0) {
        /* Copy out hash at index 1 as it is finished. */
        XMEMCPY(sig + idx[1] * 16, sk + 1 * 16, 16);
        /* Check if more iterations needed for index 0. */
        if (msg[idx[0]] != j) {
            /* Iterate 1 hash as it takes less time than doing 4. */
            HA_SetChainAddress(adrs, idx[0]);
            ret = slhdsakey_chain(key, sk, (byte)j, (byte)(msg[idx[0]] - j),
                    pk_seed, adrs, sk);
        }
    }
    if (ret == 0) {
        /* Copy out hash at index 0 as it is finished. */
        XMEMCPY(sig + idx[0] * 16, sk + 0 * 16, 16);
    }

    return ret;
}
#endif

#if !defined(WOLFSSL_SLHDSA_PARAM_NO_192)
/* Iterate hash function up to index times for each of the hashes when n=24.
 *
 * FIPS 205. Section 5. Algorithm 5.
 * chain(X, i, s, PK.seed, ADRS)
 *   1: tmp <- X
 *   2: for j from i to i + s - 1 do
 *   3:     ADRS.setHashAddress(j)
 *   4:     tmp <- F(PK.seed, ADRS, tmp
 *   5: end for
 *   6: return tmp
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sk       Hashes to iterate. Data modified.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     HashAddress.
 * @param [in]  addr     Encoded HashAddress.
 * @param [in]  msg      Array of counts.
 * @param [in]  idx      Indices into array of counts.
 * @param [in]  j        Minimum number of iterations for all 4 hashes.
 * @param [in]  cnt      Number of hashes to iterate.
 * @param [out] sig      Hash results.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_chain_idx_24(SlhDsaKey* key, byte* sk,
     const byte* pk_seed, word32* adrs, byte* addr, const byte* msg, byte* idx,
     int j, int cnt, byte* sig)
{
    int ret = 0;

    /* Iterate the minimum number of iterations on all hashes. */
    if (j != 0) {
        ret = slhdsakey_chain_idx_x4_24(sk, 0U, (word32)j, pk_seed, addr, idx,
            key->heap);
    }
    if (ret == 0) {
        if (cnt > 3) {
            /* Copy out hash at index 3 as it is finished. */
            XMEMCPY(sig + idx[3] * 24, sk + 3 * 24, 24);
        }
        /* Check if more iterations needed for index 2. */
        if (msg[idx[2]] != j) {
            /* Do 4 as we can't do less. */
            ret = slhdsakey_chain_idx_x4_24(sk, (word32)j,
                    (word32)(msg[idx[2]] - j), pk_seed, addr, idx, key->heap);
            /* Update number of iterations performed. */
            j = msg[idx[2]];
        }
    }
    if (ret == 0) {
        /* Copy out hash at index 2 as it is finished. */
        XMEMCPY(sig + idx[2] * 24, sk + 2 * 24, 24);
        /* Check if more iterations needed for index 1. */
        if (msg[idx[1]] != j) {
            /* Do 4 as we can't do less. */
            ret = slhdsakey_chain_idx_x4_24(sk, (word32)j,
                    (word32)(msg[idx[1]] - j), pk_seed, addr, idx, key->heap);
            /* Update number of iterations performed. */
            j = msg[idx[1]];
        }
    }
    if (ret == 0) {
        /* Copy out hash at index 1 as it is finished. */
        XMEMCPY(sig + idx[1] * 24, sk + 1 * 24, 24);
        /* Check if more iterations needed for index 0. */
        if (msg[idx[0]] != j) {
            /* Iterate 1 hash as it takes less time than doing 4. */
            HA_SetChainAddress(adrs, idx[0]);
            ret = slhdsakey_chain(key, sk, (byte)j, (byte)(msg[idx[0]] - j),
                    pk_seed, adrs, sk);
        }
    }
    if (ret == 0) {
        /* Copy out hash at index 0 as it is finished. */
        XMEMCPY(sig + idx[0] * 24, sk + 0 * 24, 24);
    }

    return ret;
}
#endif

#if !defined(WOLFSSL_SLHDSA_PARAM_NO_256)
/* Iterate hash function up to index times for each of the hashes when n=32.
 *
 * FIPS 205. Section 5. Algorithm 5.
 * chain(X, i, s, PK.seed, ADRS)
 *   1: tmp <- X
 *   2: for j from i to i + s - 1 do
 *   3:     ADRS.setHashAddress(j)
 *   4:     tmp <- F(PK.seed, ADRS, tmp
 *   5: end for
 *   6: return tmp
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sk       Hashes to iterate. Data modified.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     HashAddress.
 * @param [in]  addr     Encoded HashAddress.
 * @param [in]  msg      Array of counts.
 * @param [in]  idx      Indices into array of counts.
 * @param [in]  j        Minimum number of iterations for all 4 hashes.
 * @param [in]  cnt      Number of hashes to iterate.
 * @param [out] sig      Hash results.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_chain_idx_32(SlhDsaKey* key, byte* sk,
     const byte* pk_seed, word32* adrs, byte* addr, const byte* msg, byte* idx,
     int j, int cnt, byte* sig)
{
    int ret = 0;

    /* Iterate the minimum number of iterations on all hashes. */
    if (j != 0) {
        ret = slhdsakey_chain_idx_x4_32(sk, 0U, (word32)j, pk_seed, addr, idx,
            key->heap);
    }
    if (ret == 0) {
        if (cnt > 3) {
            /* Copy out hash at index 3 as it is finished. */
            XMEMCPY(sig + idx[3] * 32, sk + 3 * 32, 32);
        }
        /* Check if more iterations needed for index 2. */
        if (msg[idx[2]] != j) {
            /* Do 4 as we can't do less. */
            ret = slhdsakey_chain_idx_x4_32(sk, (word32)j,
                    (word32)(msg[idx[2]] - j), pk_seed, addr, idx, key->heap);
            /* Update number of iterations performed. */
            j = msg[idx[2]];
        }
    }
    if (ret == 0) {
        /* Copy out hash at index 2 as it is finished. */
        XMEMCPY(sig + idx[2] * 32, sk + 2 * 32, 32);
        /* Check if more iterations needed for index 1. */
        if (msg[idx[1]] != j) {
            /* Do 4 as we can't do less. */
            ret = slhdsakey_chain_idx_x4_32(sk, (word32)j,
                    (word32)(msg[idx[1]] - j), pk_seed, addr, idx, key->heap);
            /* Update number of iterations performed. */
            j = msg[idx[1]];
        }
    }
    if (ret == 0) {
        /* Copy out hash at index 1 as it is finished. */
        XMEMCPY(sig + idx[1] * 32, sk + 1 * 32, 32);
        /* Check if more iterations needed for index 0. */
        if (msg[idx[0]] != j) {
            /* Iterate 1 hash as it takes less time than doing 4. */
            HA_SetChainAddress(adrs, idx[0]);
            ret = slhdsakey_chain(key, sk, (byte)j, (byte)(msg[idx[0]] - j),
                    pk_seed, adrs, sk);
        }
    }
    if (ret == 0) {
        /* Copy out hash at index 0 as it is finished. */
        XMEMCPY(sig + idx[0] * 32, sk + 0 * 32, 32);
    }

    return ret;
}
#endif
#endif

#if defined(USE_INTEL_SPEEDUP) && !defined(WOLFSSL_WC_SLHDSA_SMALL)
#if !defined(WOLFSSL_SLHDSA_PARAM_NO_128)
/* Generate WOTS+ public key, 16-byte hashes - 4 consecutive at a time.
 *
 * FIPS 205 Section 5.1. Algorithm 6.
 * wots_pkGen(SK.seed, PK.seed, ADRS)
 *  ...
 *   4: for i from 0 to len - 1 do
 *   5:     skADRS.setChainAddress(i)
 *   6:     sk <- PRF(PK.seed, SK.seed, skADRS)
 *                                            > compute secret value for chain i
 *   7:     ADRS.setChainAddress(i)
 *   8:     tmp[i] <- chain(sk 0, w - 1, PK.seed, ADRS)
 *                                            > compute public value for chain i
 *   9: end for
 *  10: wotspkADRS <- ADRS     > copy address to create WOTS+ public key address
 *  ...
 *  13: pk <- Tlen(PK.seed, wotspkADRS, tmp)               > compress public key
 *  ...
 *
 * @param [in] key      SLH-DSA key.
 * @param [in] sk_seed  Private key seed.
 * @param [in] pk_seed  Public key seed.
 * @param [in] addr     Encoded HashAddress.
 * @param [in] sk_addr  Encoded WOTS PRF HashAddress.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_wots_pkgen_chain_x4_16(SlhDsaKey* key, const byte* sk_seed,
    const byte* pk_seed, byte* addr, byte* sk_addr)
{
    int ret = 0;
    int i;
    byte len = key->params->len;
    WC_DECLARE_VAR(sk, byte, (SLHDSA_MAX_MSG_SZ + 3) * 16, key->heap);

    WC_ALLOC_VAR_EX(sk, byte, (SLHDSA_MAX_MSG_SZ + 3) * 16, key->heap,
        DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E);
    if (ret == 0) {
        for (i = 0; i < len - 3; i += 4) {
            ret = slhdsakey_hash_prf_x4(pk_seed, sk_seed, sk_addr, 16, (byte)i,
                sk + i * 16, key->heap);
            if (ret != 0) {
                break;
            }
            ret = slhdsakey_chain_x4_16(sk + i * 16, pk_seed, addr, (byte)i,
                key->heap);
            if (ret != 0) {
                break;
            }
        }
    }
    if (ret == 0) {
        ret = slhdsakey_hash_prf_x4(pk_seed, sk_seed, sk_addr, 16, (byte)i,
            sk + i * 16, key->heap);
        if (ret == 0) {
            ret = slhdsakey_chain_x4_16(sk + i * 16, pk_seed, addr, (byte)i,
                key->heap);
        }
    }
    if (ret == 0) {
        ret = HASH_T_UPDATE(key, sk, (word32)len * 16U);
    }

    WC_FREE_VAR_EX(sk, key->heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}
#endif

#if !defined(WOLFSSL_SLHDSA_PARAM_NO_192)
/* Generate WOTS+ public key, 24-byte hashes - 4 consecutive at a time.
 *
 * FIPS 205 Section 5.1. Algorithm 6.
 * wots_pkGen(SK.seed, PK.seed, ADRS)
 *  ...
 *   4: for i from 0 to len - 1 do
 *   5:     skADRS.setChainAddress(i)
 *   6:     sk <- PRF(PK.seed, SK.seed, skADRS)
 *                                            > compute secret value for chain i
 *   7:     ADRS.setChainAddress(i)
 *   8:     tmp[i] <- chain(sk 0, w - 1, PK.seed, ADRS)
 *                                            > compute public value for chain i
 *   9: end for
 *  10: wotspkADRS <- ADRS     > copy address to create WOTS+ public key address
 *  ...
 *  13: pk <- Tlen(PK.seed, wotspkADRS, tmp)               > compress public key
 *  ...
 *
 * @param [in] key      SLH-DSA key.
 * @param [in] sk_seed  Private key seed.
 * @param [in] pk_seed  Public key seed.
 * @param [in] addr     Encoded HashAddress.
 * @param [in] sk_addr  Encoded WOTS PRF HashAddress.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_wots_pkgen_chain_x4_24(SlhDsaKey* key, const byte* sk_seed,
    const byte* pk_seed, byte* addr, byte* sk_addr)
{
    int ret = 0;
    int i;
    byte len = key->params->len;
    WC_DECLARE_VAR(sk, byte, (SLHDSA_MAX_MSG_SZ + 3) * 24, key->heap);

    WC_ALLOC_VAR_EX(sk, byte, (SLHDSA_MAX_MSG_SZ + 3) * 24, key->heap,
        DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E);
    if (ret == 0) {
        for (i = 0; i < len - 3; i += 4) {
            ret = slhdsakey_hash_prf_x4(pk_seed, sk_seed, sk_addr, 24, (byte)i,
                sk + i * 24, key->heap);
            if (ret != 0) {
                break;
            }
            ret = slhdsakey_chain_x4_24(sk + i * 24, pk_seed, addr, (byte)i,
                key->heap);
            if (ret != 0) {
                break;
            }
        }
    }
    if (ret == 0) {
        ret = slhdsakey_hash_prf_x4(pk_seed, sk_seed, sk_addr, 24, (byte)i,
            sk + i * 24, key->heap);
        if (ret == 0) {
            ret = slhdsakey_chain_x4_24(sk + i * 24, pk_seed, addr, (byte)i,
                key->heap);
        }
    }
    if (ret == 0) {
        ret = HASH_T_UPDATE(key, sk, (word32)len * 24U);
    }

    WC_FREE_VAR_EX(sk, key->heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}
#endif

#if !defined(WOLFSSL_SLHDSA_PARAM_NO_256)
/* Generate WOTS+ public key, 32-byte hashes - 4 consecutive at a time.
 *
 * FIPS 205 Section 5.1. Algorithm 6.
 * wots_pkGen(SK.seed, PK.seed, ADRS)
 *  ...
 *   4: for i from 0 to len - 1 do
 *   5:     skADRS.setChainAddress(i)
 *   6:     sk <- PRF(PK.seed, SK.seed, skADRS)
 *                                            > compute secret value for chain i
 *   7:     ADRS.setChainAddress(i)
 *   8:     tmp[i] <- chain(sk 0, w - 1, PK.seed, ADRS)
 *                                            > compute public value for chain i
 *   9: end for
 *  10: wotspkADRS <- ADRS     > copy address to create WOTS+ public key address
 *  ...
 *  13: pk <- Tlen(PK.seed, wotspkADRS, tmp)               > compress public key
 *  ...
 *
 * @param [in] key      SLH-DSA key.
 * @param [in] sk_seed  Private key seed.
 * @param [in] pk_seed  Public key seed.
 * @param [in] addr     Encoded HashAddress.
 * @param [in] sk_addr  Encoded WOTS PRF HashAddress.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_wots_pkgen_chain_x4_32(SlhDsaKey* key, const byte* sk_seed,
    const byte* pk_seed, byte* addr, byte* sk_addr)
{
    int ret = 0;
    int i;
    byte len = key->params->len;
    WC_DECLARE_VAR(sk, byte, (SLHDSA_MAX_MSG_SZ + 3) * 32, key->heap);

    WC_ALLOC_VAR_EX(sk, byte, (SLHDSA_MAX_MSG_SZ + 3) * 32, key->heap,
        DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E);
    if (ret == 0) {
        for (i = 0; i < len - 3; i += 4) {
            ret = slhdsakey_hash_prf_x4(pk_seed, sk_seed, sk_addr, 32, (byte)i,
                sk + i * 32, key->heap);
            if (ret != 0) {
                break;
            }
            ret = slhdsakey_chain_x4_32(sk + i * 32, pk_seed, addr, (byte)i,
                key->heap);
            if (ret != 0) {
                break;
            }
        }
    }
    if (ret == 0) {
        ret = slhdsakey_hash_prf_x4(pk_seed, sk_seed, sk_addr, 32, (byte)i,
            sk + i * 32, key->heap);
        if (ret == 0) {
            ret = slhdsakey_chain_x4_32(sk + i * 32, pk_seed, addr, (byte)i,
                key->heap);
        }
    }
    if (ret == 0) {
        ret = HASH_T_UPDATE(key, sk, (word32)len * 32U);
    }

    WC_FREE_VAR_EX(sk, key->heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}
#endif

/* Generate WOTS+ public key - 4 consecutive addresses at a time.
 *
 * FIPS 205 Section 5.1. Algorithm 6.
 * wots_pkGen(SK.seed, PK.seed, ADRS)
 *  ...
 *   4: for i from 0 to len - 1 do
 *   5:     skADRS.setChainAddress(i)
 *   6:     sk <- PRF(PK.seed, SK.seed, skADRS)
 *                                            > compute secret value for chain i
 *   7:     ADRS.setChainAddress(i)
 *   8:     tmp[i] <- chain(sk 0, w - 1, PK.seed, ADRS)
 *                                            > compute public value for chain i
 *   9: end for
 *  10: wotspkADRS <- ADRS     > copy address to create WOTS+ public key address
 *  ...
 *  13: pk <- Tlen(PK.seed, wotspkADRS, tmp)               > compress public key
 *  ...
 *
 * @param [in] key      SLH-DSA key.
 * @param [in] sk_seed  Private key seed.
 * @param [in] pk_seed  Public key seed.
 * @param [in] adrs     HashAddress.
 * @param [in] sk_adrs  WOTS PRF HashAddress.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_wots_pkgen_chain_x4(SlhDsaKey* key, const byte* sk_seed,
    const byte* pk_seed, word32* adrs, word32* sk_adrs)
{
    int ret = 0;
    byte sk_addr[SLHDSA_HA_SZ];
    byte addr[SLHDSA_HA_SZ];
    byte n = key->params->n;

    HA_SetHashAddress(sk_adrs, 0);
    HA_Encode(sk_adrs, sk_addr);
    HA_Encode(adrs, addr);

#if !defined(WOLFSSL_SLHDSA_PARAM_NO_128)
    if (n == WC_SLHDSA_N_128) {
        ret = slhdsakey_wots_pkgen_chain_x4_16(key, sk_seed, pk_seed, addr,
            sk_addr);
    }
    else
#endif
#if !defined(WOLFSSL_SLHDSA_PARAM_NO_192)
    if (n == 24) {
        ret = slhdsakey_wots_pkgen_chain_x4_24(key, sk_seed, pk_seed, addr,
            sk_addr);
    }
    else
#endif
#if !defined(WOLFSSL_SLHDSA_PARAM_NO_256)
    if (n == 32) {
        ret = slhdsakey_wots_pkgen_chain_x4_32(key, sk_seed, pk_seed, addr,
            sk_addr);
    }
    else
#endif
    if (ret == 0) {
        ret = NOT_COMPILED_IN;
    }

    return ret;
}
#endif

/* Generate WOTS+ public key.
 *
 * FIPS 205 Section 5.1. Algorithm 6.
 * wots_pkGen(SK.seed, PK.seed, ADRS)
 *  ...
 *   4: for i from 0 to len - 1 do
 *   5:     skADRS.setChainAddress(i)
 *   6:     sk <- PRF(PK.seed, SK.seed, skADRS)
 *                                            > compute secret value for chain i
 *   7:     ADRS.setChainAddress(i)
 *   8:     tmp[i] <- chain(sk 0, w - 1, PK.seed, ADRS)
 *                                            > compute public value for chain i
 *   9: end for
 *  10: wotspkADRS <- ADRS     > copy address to create WOTS+ public key address
 *  ...
 *  13: pk <- Tlen(PK.seed, wotspkADRS, tmp)               > compress public key
 *  ...
 *
 * @param [in] key      SLH-DSA key.
 * @param [in] sk_seed  Private key seed.
 * @param [in] pk_seed  Public key seed.
 * @param [in] adrs     HashAddress.
 * @param [in] sk_adrs  WOTS PRF HashAddress.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_wots_pkgen_chain_c(SlhDsaKey* key, const byte* sk_seed,
    const byte* pk_seed, word32* adrs, word32* sk_adrs)
{
    int ret = 0;
    int i;
    byte n = key->params->n;
    byte len = key->params->len;

#if !defined(WOLFSSL_WC_SLHDSA_SMALL_MEM)
    WC_DECLARE_VAR(sk, byte, (SLHDSA_MAX_MSG_SZ + 3) * SLHDSA_MAX_N, key->heap);

    WC_ALLOC_VAR_EX(sk, byte, (SLHDSA_MAX_MSG_SZ + 3) * SLHDSA_MAX_N,
        key->heap, DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E);
    if (ret == 0) {
        /* Step 4. len consecutive addresses. */
        for (i = 0; i < len; i++) {
            /* Step 5. Set chain address for WOTS PRF. */
            HA_SetChainAddress(sk_adrs, i);
            /* Step 6. PRF hash seeds and chain address. */
            ret = HASH_PRF(key, pk_seed, sk_seed, sk_adrs, n,
                sk + i * n);
            if (ret != 0) {
                break;
            }
            /* Step 7. Set chain address for WOTS HASH. */
            HA_SetChainAddress(adrs, i);
            /* Step 8. Chain hashes for w-1 iterations. */
            ret = slhdsakey_chain(key, sk + i * n, 0, SLHDSA_WM1, pk_seed, adrs,
                sk + i * n);
            if (ret != 0) {
                break;
            }
        }
    }
    if (ret == 0) {
        /* Step 13: Compress public key. */
        ret = HASH_T_UPDATE(key, sk, (word32)len * n);
    }
    WC_FREE_VAR_EX(sk, key->heap, DYNAMIC_TYPE_SLHDSA);
#else
    /* Step 4. len consecutive addresses. */
    for (i = 0; i < len; i++) {
        byte sk[SLHDSA_MAX_N];

        /* Step 5. Set chain address for WOTS PRF. */
        HA_SetChainAddress(sk_adrs, i);
        /* Step 6. PRF hash seeds and chain address. */
        ret = HASH_PRF(key, pk_seed, sk_seed, sk_adrs, n, sk);
        if (ret != 0) {
            break;
        }
        /* Step 7. Set chain address for WOTS HASH. */
        HA_SetChainAddress(adrs, i);
        /* Step 8. Chain hashes for w-1 iterations. */
        ret = slhdsakey_chain(key, sk, 0, SLHDSA_WM1, pk_seed, adrs, sk);
        if (ret != 0) {
            break;
        }

        /* Step 13: Compress public key - for each tmp. */
        ret = HASH_T_UPDATE(key, sk, n);
        if (ret != 0) {
            break;
        }
    }
#endif

    return ret;
}

/* Generate WOTS+ public key.
 *
 * FIPS 205 Section 5.1. Algorithm 6.
 * wots_pkGen(SK.seed, PK.seed, ADRS)
 *   1: skADRS <- ADRS       > copy address to create key generation key address
 *   2: skADRS.setTypeAndClear(WOTS_PRF)
 *   3: skADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
 *  ...
 *  11: wotspkADRS.setTypeAndClear(WOTS_PK)
 *  12: wotspkADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
 *  13: pk <- Tlen(PK.seed, wotspkADRS, tmp)               > compress public key
 *  14: return pk
 *
 * @param [in] key      SLH-DSA key.
 * @param [in] sk_seed  Private key seed.
 * @param [in] pk_seed  Public key seed.
 * @param [in] adrs     HashAddress.
 * @param [in] sk_adrs  WOTS PRF HashAddress.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_wots_pkgen(SlhDsaKey* key, const byte* sk_seed,
    const byte* pk_seed, word32* adrs, byte* node)
{
    int ret;
    byte n = key->params->n;
    int hash_t_started = 0;

    {
        HashAddress wotspk_adrs;

        /* Steps 11-12. Copy address and set to WOTS PK. */
        HA_Copy(wotspk_adrs, adrs);
        HA_SetTypeAndClearNotKPA(wotspk_adrs, HA_WOTS_PK);
        /* Step 13. Start hash with public key seed and address. */
        ret = HASH_T_START_ADDR(key, pk_seed, wotspk_adrs, n);
    }
    if (ret == 0) {
        HashAddress sk_adrs;

        hash_t_started = 1;

        /* Steps 1-2. Copy address and set to WOTS PRF. */
        HA_Copy(sk_adrs, adrs);
        HA_SetTypeAndClearNotKPA(sk_adrs, HA_WOTS_PRF);
        /* Steps 4-10,13: Generate hashes and update the public key hash. */
#if defined(USE_INTEL_SPEEDUP) && !defined(WOLFSSL_WC_SLHDSA_SMALL)
        if (!SLHDSA_IS_SHA2(key->params->param) &&
                IS_INTEL_AVX2(cpuid_flags) &&
                (SAVE_VECTOR_REGISTERS2() == 0)) {
            ret = slhdsakey_wots_pkgen_chain_x4(key, sk_seed, pk_seed, adrs,
                sk_adrs);
            RESTORE_VECTOR_REGISTERS();
        }
        else
#endif
        {
            ret = slhdsakey_wots_pkgen_chain_c(key, sk_seed, pk_seed, adrs,
                sk_adrs);
        }
    }
    if (ret == 0) {
        /* Step 13: Output hash of compressed public key. */
        ret = HASH_T_FINAL(key, node, n);
    }

    if (hash_t_started) {
        HASH_T_FREE(key);
    }

    return ret;
}

#if defined(USE_INTEL_SPEEDUP) && !defined(WOLFSSL_WC_SLHDSA_SMALL)
#if !defined(WOLFSSL_SLHDSA_PARAM_NO_128)
/* Generate a WOTS+ signature, 32-byte hashed, on msg - iterating 4 hashes.
 *
 * FIPS 205. Section 5.2. Algorithm 7
 * wots_sign(M, SK.seed, PK.seed, ADRS)
 *  ...
 *  11: for i from 0 to len - 1 do
 *  12:     skADRS.setChainAddress(i)
 *  13:     sk <- PRF(PK.seed, SK.seed, skADRS)   > compute chain i secret value
 *  14:     ADRS.setChainAddress(i)
 *  15:     sig[i] <- chain(sk, 0, msg[i], PK.seed, ADRS)
 *                                             > compute chain i signature value
 *  16: end for
 *  17: return sig
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  msg      Encoded message with checksum.
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     HashAddress.
 * @param [in]  sk_adrs  PRF HashAddress.
 * @param [out] sig      Signature - (2.n + 3) hashes of length n.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_wots_sign_chain_x4_16(SlhDsaKey* key, const byte* msg,
    const byte* sk_seed, const byte* pk_seed, word32* adrs, byte* addr,
    byte* sk_addr, byte* sig)
{
    int ret = 0;
    int i;
    sword8 j;
    byte ii;
    byte idx[4] = {0};
    byte n = key->params->n;
    byte len = key->params->len;
    WC_DECLARE_VAR(sk, byte, 4 * 16, key->heap);

    WC_ALLOC_VAR_EX(sk, byte, 4 * 16, key->heap, DYNAMIC_TYPE_SLHDSA,
        ret = MEMORY_E);
    if (ret == 0) {
        ii = 0;
        for (j = (sword8)SLHDSA_WM1; j >= 0; j--) {
            for (i = 0; i < len; i++) {
                if ((sword8)msg[i] == j) {
                    idx[ii++] = (byte)i;
                    if (ii == 4) {
                        ret = slhdsakey_hash_prf_idx_x4(pk_seed, sk_seed,
                            sk_addr, n, idx, sk, key->heap);
                        if (ret != 0) {
                            break;
                        }
                        ret = slhdsakey_chain_idx_16(key, sk, pk_seed, adrs,
                            addr, msg, idx, j, 4, sig);
                        if (ret != 0) {
                            break;
                        }
                        ii = 0;
                    }
                }
            }
        }
    }

    if (ret == 0) {
        ret = slhdsakey_hash_prf_idx_x4(pk_seed, sk_seed, sk_addr, n, idx, sk,
            key->heap);
    }
    if (ret == 0) {
        j = (sword8)min(min(msg[idx[0]], msg[idx[1]]), msg[idx[2]]);
        ret = slhdsakey_chain_idx_16(key, sk, pk_seed, adrs, addr, msg, idx, j,
            3, sig);
    }

    WC_FREE_VAR_EX(sk, key->heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}
#endif

#if !defined(WOLFSSL_SLHDSA_PARAM_NO_192)
/* Generate a WOTS+ signature, 32-byte hashed, on msg - iterating 4 hashes.
 *
 * FIPS 205. Section 5.2. Algorithm 7
 * wots_sign(M, SK.seed, PK.seed, ADRS)
 *  ...
 *  11: for i from 0 to len - 1 do
 *  12:     skADRS.setChainAddress(i)
 *  13:     sk <- PRF(PK.seed, SK.seed, skADRS)   > compute chain i secret value
 *  14:     ADRS.setChainAddress(i)
 *  15:     sig[i] <- chain(sk, 0, msg[i], PK.seed, ADRS)
 *                                             > compute chain i signature value
 *  16: end for
 *  17: return sig
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  msg      Encoded message with checksum.
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     HashAddress.
 * @param [in]  sk_adrs  PRF HashAddress.
 * @param [out] sig      Signature - (2.n + 3) hashes of length n.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_wots_sign_chain_x4_24(SlhDsaKey* key, const byte* msg,
    const byte* sk_seed, const byte* pk_seed, word32* adrs, byte* addr,
    byte* sk_addr, byte* sig)
{
    int ret = 0;
    int i;
    sword8 j;
    byte ii;
    byte idx[4] = {0};
    byte n = key->params->n;
    byte len = key->params->len;
    WC_DECLARE_VAR(sk, byte, 4 * 24, key->heap);

    WC_ALLOC_VAR_EX(sk, byte, 4 * 24, key->heap, DYNAMIC_TYPE_SLHDSA,
        ret = MEMORY_E);
    if (ret == 0) {
        ii = 0;
        for (j = (sword8)SLHDSA_WM1; j >= 0; j--) {
            for (i = 0; i < len; i++) {
                if ((sword8)msg[i] == j) {
                    idx[ii++] = (byte)i;
                    if (ii == 4) {
                        ret = slhdsakey_hash_prf_idx_x4(pk_seed, sk_seed,
                            sk_addr, n, idx, sk, key->heap);
                        if (ret != 0) {
                            break;
                        }
                        ret = slhdsakey_chain_idx_24(key, sk, pk_seed, adrs,
                            addr, msg, idx, j, 4, sig);
                        if (ret != 0) {
                            break;
                        }
                        ii = 0;
                    }
                }
            }
        }
    }

    if (ret == 0) {
        ret = slhdsakey_hash_prf_idx_x4(pk_seed, sk_seed, sk_addr, n, idx, sk,
            key->heap);
    }
    if (ret == 0) {
        j = (sword8)min(min(msg[idx[0]], msg[idx[1]]), msg[idx[2]]);
        ret = slhdsakey_chain_idx_24(key, sk, pk_seed, adrs, addr,
            msg, idx, j, 3, sig);
    }

    WC_FREE_VAR_EX(sk, key->heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}
#endif

#if !defined(WOLFSSL_SLHDSA_PARAM_NO_256)
/* Generate a WOTS+ signature, 32-byte hashed, on msg - iterating 4 hashes.
 *
 * FIPS 205. Section 5.2. Algorithm 7
 * wots_sign(M, SK.seed, PK.seed, ADRS)
 *  ...
 *  11: for i from 0 to len - 1 do
 *  12:     skADRS.setChainAddress(i)
 *  13:     sk <- PRF(PK.seed, SK.seed, skADRS)   > compute chain i secret value
 *  14:     ADRS.setChainAddress(i)
 *  15:     sig[i] <- chain(sk, 0, msg[i], PK.seed, ADRS)
 *                                             > compute chain i signature value
 *  16: end for
 *  17: return sig
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  msg      Encoded message with checksum.
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     HashAddress.
 * @param [in]  sk_adrs  PRF HashAddress.
 * @param [out] sig      Signature - (2.n + 3) hashes of length n.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_wots_sign_chain_x4_32(SlhDsaKey* key, const byte* msg,
    const byte* sk_seed, const byte* pk_seed, word32* adrs, byte* addr,
    byte* sk_addr, byte* sig)
{
    int ret = 0;
    int i;
    sword8 j;
    byte ii;
    byte idx[4] = {0};
    byte n = key->params->n;
    byte len = key->params->len;
    WC_DECLARE_VAR(sk, byte, 4 * 32, key->heap);

    WC_ALLOC_VAR_EX(sk, byte, 4 * 32, key->heap, DYNAMIC_TYPE_SLHDSA,
        ret = MEMORY_E);
    if (ret == 0) {
        ii = 0;
        for (j = (sword8)SLHDSA_WM1; j >= 0; j--) {
            for (i = 0; i < len; i++) {
                if ((sword8)msg[i] == j) {
                    idx[ii++] = (byte)i;
                    if (ii == 4) {
                        ret = slhdsakey_hash_prf_idx_x4(pk_seed, sk_seed,
                            sk_addr, n, idx, sk, key->heap);
                        if (ret != 0) {
                            break;
                        }
                        ret = slhdsakey_chain_idx_32(key, sk, pk_seed, adrs,
                            addr, msg, idx, j, 4, sig);
                        if (ret != 0) {
                            break;
                        }
                        ii = 0;
                    }
                }
            }
        }
    }

    if (ret == 0) {
        ret = slhdsakey_hash_prf_idx_x4(pk_seed, sk_seed, sk_addr, n, idx, sk,
            key->heap);
    }
    if (ret == 0) {
        j = (sword8)min(min(msg[idx[0]], msg[idx[1]]), msg[idx[2]]);
        ret = slhdsakey_chain_idx_32(key, sk, pk_seed, adrs, addr, msg, idx, j,
            3, sig);
    }
    if (ret == 0) {
        sig += len * n;
    }

    WC_FREE_VAR_EX(sk, key->heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}
#endif

/* Generate a WOTS+ signature on msg - iterating 4 hashes at a time.
 *
 * FIPS 205. Section 5.2. Algorithm 7
 * wots_sign(M, SK.seed, PK.seed, ADRS)
 *  ...
 *   8: skADRS <- ADRS       > copy address to create key generation key address
 *   9: skADRS.setTypeAndClear(WOTS_PRF)
 *  10: skADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
 *  11: for i from 0 to len - 1 do
 *  12:     skADRS.setChainAddress(i)
 *  13:     sk <- PRF(PK.seed, SK.seed, skADRS)   > compute chain i secret value
 *  14:     ADRS.setChainAddress(i)
 *  15:     sig[i] <- chain(sk, 0, msg[i], PK.seed, ADRS)
 *                                             > compute chain i signature value
 *  16: end for
 *  17: return sig
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  msg      Encoded message with checksum.
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     HashAddress.
 * @param [in]  sk_adrs  PRF HashAddress.
 * @param [out] sig      Signature - (2.n + 3) hashes of length n.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_wots_sign_chain_x4(SlhDsaKey* key, const byte* msg,
    const byte* sk_seed, const byte* pk_seed, word32* adrs, word32* sk_adrs,
    byte* sig)
{
    int ret = 0;
    byte sk_addr[SLHDSA_HA_SZ];
    byte addr[SLHDSA_HA_SZ];
    byte n = key->params->n;

    HA_SetHashAddress(sk_adrs, 0);
    HA_Encode(sk_adrs, sk_addr);
    HA_Encode(adrs, addr);

#if !defined(WOLFSSL_SLHDSA_PARAM_NO_128)
    if (n == WC_SLHDSA_N_128) {
        ret = slhdsakey_wots_sign_chain_x4_16(key, msg, sk_seed, pk_seed, adrs,
            addr, sk_addr, sig);
    }
    else
#endif
#if !defined(WOLFSSL_SLHDSA_PARAM_NO_192)
    if (n == 24) {
        ret = slhdsakey_wots_sign_chain_x4_24(key, msg, sk_seed, pk_seed, adrs,
            addr, sk_addr, sig);
    }
    else
#endif
#if !defined(WOLFSSL_SLHDSA_PARAM_NO_256)
    if (n == 32) {
        ret = slhdsakey_wots_sign_chain_x4_32(key, msg, sk_seed, pk_seed, adrs,
             addr, sk_addr, sig);
    }
    else
#endif
    if (ret == 0) {
        ret = NOT_COMPILED_IN;
    }

    return ret;
}
#endif

/* Generate a WOTS+ signature on an n-byte message.
 *
 * FIPS 205. Section 5.2. Algorithm 7
 * wots_sign(M, SK.seed, PK.seed, ADRS)
 *   1: csum <- 0
 *   2: msg <- base_2b(M , lgw , len1 )              > convert message to base w
 *   3: for i from 0 to len1 - 1 do
 *   4:     csum <- csum + w - 1 - msg[i]
 *   5: end for                                               > compute checksum
 *   6: csum <- csum << ((8 - ((len2.lgw) mod 8)) mod 8)
 *                                                > for lgw = 4, left shift by 4
 *   7: msg <- msg || base_2b(toByte(csum, upper(len2.lgw/8)), lgw , len2)
 *                                                           > convert to base w
 *   8: skADRS <- ADRS       > copy address to create key generation key address
 *   9: skADRS.setTypeAndClear(WOTS_PRF)
 *  10: skADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
 *  11: for i from 0 to len - 1 do
 *  12:     skADRS.setChainAddress(i)
 *  13:     sk <- PRF(PK.seed, SK.seed, skADRS)   > compute chain i secret value
 *  14:     ADRS.setChainAddress(i)
 *  15:     sig[i] <- chain(sk, 0, msg[i], PK.seed, ADRS)
 *                                             > compute chain i signature value
 *  16: end for
 *  17: return sig
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  m        n-bytes message.
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     HashAddress.
 * @param [out] sig      Signature - (2.n + 3) hashes of length n.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_wots_sign(SlhDsaKey* key, const byte* m,
    const byte* sk_seed, const byte* pk_seed, word32* adrs, byte* sig)
{
    int ret = WC_NO_ERR_TRACE(BAD_FUNC_ARG);
    word16 csum;
    HashAddress sk_adrs;
    byte n = key->params->n;
    byte len = key->params->len;
    int i;
    byte msg[SLHDSA_MAX_MSG_SZ];

    /* Step 1: Start csum at 0 */
    csum = 0;
    /* Step 3: For each byte in message. */
    for (i = 0; i < n * 2; i += 2) {
        /* Step 2: Append high order 4 bits to msg. */
        msg[i+0] = (byte)((m[i / 2] >> 4) & 0xf);
        /* Step 4: Calculate checksum with first lgw bits. */
        csum = (word16)(csum + SLHDSA_WM1 - msg[i + 0]);
        /* Step 2: Append low order 4 bits to msg. */
        msg[i+1] = (byte)( m[i / 2]       & 0xf);
        /* Step 4: Calculate checksum with next lgw bits. */
        csum = (word16)(csum + SLHDSA_WM1 - msg[i + 1]);
    }
    /* Steps 6-7: Encode bottom 12 bits of csum onto end of msg. */
    msg[i + 0] = (byte)((csum >> 8) & 0xf);
    msg[i + 1] = (byte)((csum >> 4) & 0xf);
    msg[i + 2] = (byte)( csum       & 0xf);

    /* Steps 8-10: Copy address for WOTS PRF. */
    HA_Copy(sk_adrs, adrs);
    HA_SetTypeAndClearNotKPA(sk_adrs, HA_WOTS_PRF);
#if defined(USE_INTEL_SPEEDUP) && !defined(WOLFSSL_WC_SLHDSA_SMALL)
    /* Steps 11-17: Generate signature from msg. */
    if (!SLHDSA_IS_SHA2(key->params->param) &&
            IS_INTEL_AVX2(cpuid_flags) &&
            (SAVE_VECTOR_REGISTERS2() == 0)) {
        ret = slhdsakey_wots_sign_chain_x4(key, msg, sk_seed, pk_seed, adrs,
            sk_adrs, sig);
        RESTORE_VECTOR_REGISTERS();
    }
    else
#endif
    {
        /* Step 11: For each value of msg. */
        for (i = 0; i < len; i++) {
            /* Step 12: Set chain address for WOTS PRF. */
            HA_SetChainAddress(sk_adrs, i);
            /* Step 13. PRF hash seeds and chain address. */
            ret = HASH_PRF(key, pk_seed, sk_seed, sk_adrs, n, sig);
            if (ret != 0) {
                break;
            }
            /* Step 14: Set chain address for WOTS HASH. */
            HA_SetChainAddress(adrs, i);
            /* Step 15. Chain hashes for msg value iterations. */
            ret = slhdsakey_chain(key, sig, 0, msg[i], pk_seed, adrs, sig);
            if (ret != 0) {
                break;
            }
            /* Step 15: Move to next hash in signature. */
            sig += n;
        }
    }

    return ret;
}
#endif

#if defined(USE_INTEL_SPEEDUP) && !defined(WOLFSSL_WC_SLHDSA_SMALL)
#if !defined(WOLFSSL_SLHDSA_PARAM_NO_128)
/* Computes 4 chains simultaneously from starts to w-1 when n=16.
 *
 * FIPS 205. Section 5.3. Algorithm 8.
 * wots_pkFromSig(sig, M, PK.seed, ADRS)
 *  ...
 *  10:     tmp[i] <- chain(sig[i], msg[i], w - 1 - msg[i], PK.seed, ADRS)
 *  ...
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sig      Signature - (2.n + 3) hashes of length n.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     WOTS HASH HashAddress.
 * @param [in]  msg      Encoded message with checksum.
 * @param [in]  idx      Indices of chains.
 * @param [in]  j        Shortest chain length already calculated.
 * @param [in]  cnt      Number of chains to complete.
 * @param [out] nodes    Buffer to place completed chains.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_chain_idx_to_max_16(SlhDsaKey* key, const byte* sig,
     const byte* pk_seed, word32* adrs, const byte* msg, byte* idx, int j,
     int cnt, byte* nodes)
{
    int ret = 0;
    byte node[4 * 16];
    byte addr[SLHDSA_HA_SZ];

    HA_SetChainAddress(adrs, idx[0]);
    HA_Encode(adrs, addr);

    XMEMCPY(node + 0 * 16, sig + idx[0] * 16, 16);
    if ((msg[idx[0]] != j) && (msg[idx[0]] != msg[idx[1]])) {
        ret = slhdsakey_chain(key, node, msg[idx[0]],
            (byte)(msg[idx[1]] - msg[idx[0]]), pk_seed, adrs, node);
    }
    if (ret == 0) {
        XMEMCPY(node + 1 * 16, sig + idx[1] * 16, 16);
        XMEMSET(node + 2 * 16, 0, sizeof(node) - 2 * 16);
        if ((msg[idx[1]] != j) && (msg[idx[1]] != msg[idx[2]])) {
            ret = slhdsakey_chain_idx_x4_16(node, msg[idx[1]],
                (word32)(msg[idx[2]] - msg[idx[1]]), pk_seed, addr, idx,
                key->heap);
        }
    }
    if (ret == 0) {
        XMEMCPY(node + 2 * 16, sig + idx[2] * 16, 16);
        if ((cnt > 3) && (msg[idx[2]] != j)) {
            ret = slhdsakey_chain_idx_x4_16(node, msg[idx[2]],
                (word32)(j - msg[idx[2]]), pk_seed, addr, idx, key->heap);
        }
    }
    if (ret == 0) {
        if (cnt > 3) {
            XMEMCPY(node + 3 * 16, sig + idx[3] * 16, 16);
        }
        if (j != SLHDSA_WM1) {
            ret = slhdsakey_chain_idx_x4_16(node, (word32)j,
                (word32)(SLHDSA_WM1 - j), pk_seed, addr, idx, key->heap);
        }
    }
    if (ret == 0) {
        XMEMCPY(nodes + idx[0] * 16, node + 0 * 16, 16);
        XMEMCPY(nodes + idx[1] * 16, node + 1 * 16, 16);
        XMEMCPY(nodes + idx[2] * 16, node + 2 * 16, 16);
        if (cnt > 3) {
            XMEMCPY(nodes + idx[3] * 16, node + 3 * 16, 16);
        }
    }

    return ret;
}
#endif

#if !defined(WOLFSSL_SLHDSA_PARAM_NO_192)
/* Computes 4 chains simultaneously from starts to w-1 when n=24.
 *
 * FIPS 205. Section 5.3. Algorithm 8.
 * wots_pkFromSig(sig, M, PK.seed, ADRS)
 *  ...
 *  10:     tmp[i] <- chain(sig[i], msg[i], w - 1 - msg[i], PK.seed, ADRS)
 *  ...
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sig      Signature - (2.n + 3) hashes of length n.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     WOTS HASH HashAddress.
 * @param [in]  msg      Encoded message with checksum.
 * @param [in]  idx      Indices of chains.
 * @param [in]  j        Shortest chain length already calculated.
 * @param [in]  cnt      Number of chains to complete.
 * @param [out] nodes    Buffer to place completed chains.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_chain_idx_to_max_24(SlhDsaKey* key, const byte* sig,
     const byte* pk_seed, word32* adrs, const byte* msg, byte* idx, int j,
     int cnt, byte* nodes)
{
    int ret = 0;
    byte node[4 * 24];
    byte addr[SLHDSA_HA_SZ];

    HA_SetChainAddress(adrs, idx[0]);
    HA_Encode(adrs, addr);

    XMEMCPY(node + 0 * 24, sig + idx[0] * 24, 24);
    if ((msg[idx[0]] != j) && (msg[idx[0]] != msg[idx[1]])) {
        ret = slhdsakey_chain(key, node, msg[idx[0]],
            (byte)(msg[idx[1]] - msg[idx[0]]), pk_seed, adrs, node);
    }
    if (ret == 0) {
        XMEMCPY(node + 1 * 24, sig + idx[1] * 24, 24);
        XMEMSET(node + 2 * 24, 0, sizeof(node) - 2 * 24);
        if ((msg[idx[1]] != j) && (msg[idx[1]] != msg[idx[2]])) {
            ret = slhdsakey_chain_idx_x4_24(node, msg[idx[1]],
                (word32)(msg[idx[2]] - msg[idx[1]]), pk_seed, addr, idx,
                key->heap);
        }
    }
    if (ret == 0) {
        XMEMCPY(node + 2 * 24, sig + idx[2] * 24, 24);
        if ((cnt > 3) && (msg[idx[2]] != j)) {
            ret = slhdsakey_chain_idx_x4_24(node, msg[idx[2]],
                (word32)(j - msg[idx[2]]), pk_seed, addr, idx, key->heap);
        }
    }
    if (ret == 0) {
        if (cnt > 3) {
            XMEMCPY(node + 3 * 24, sig + idx[3] * 24, 24);
        }
        if (j != SLHDSA_WM1) {
            ret = slhdsakey_chain_idx_x4_24(node, (word32)j,
                (word32)(SLHDSA_WM1 - j), pk_seed, addr, idx, key->heap);
        }
    }
    if (ret == 0) {
        XMEMCPY(nodes + idx[0] * 24, node + 0 * 24, 24);
        XMEMCPY(nodes + idx[1] * 24, node + 1 * 24, 24);
        XMEMCPY(nodes + idx[2] * 24, node + 2 * 24, 24);
        if (cnt > 3) {
            XMEMCPY(nodes + idx[3] * 24, node + 3 * 24, 24);
        }
    }

    return ret;
}
#endif

#if !defined(WOLFSSL_SLHDSA_PARAM_NO_256)
/* Computes 4 chains simultaneously from starts to w-1 when n=32.
 *
 * FIPS 205. Section 5.3. Algorithm 8.
 * wots_pkFromSig(sig, M, PK.seed, ADRS)
 *  ...
 *  10:     tmp[i] <- chain(sig[i], msg[i], w - 1 - msg[i], PK.seed, ADRS)
 *  ...
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sig      Signature - (2.n + 3) hashes of length n.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     WOTS HASH HashAddress.
 * @param [in]  msg      Encoded message with checksum.
 * @param [in]  idx      Indices of chains.
 * @param [in]  j        Shortest chain length already calculated.
 * parama [in]  cnt      Number of chains to complete.
 * @param [out] nodes    Buffer to place completed chains.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_chain_idx_to_max_32(SlhDsaKey* key, const byte* sig,
     const byte* pk_seed, word32* adrs, const byte* msg, byte* idx, int j,
     int cnt, byte* nodes)
{
    int ret = 0;
    byte node[4 * 32];
    byte addr[SLHDSA_HA_SZ];

    HA_SetChainAddress(adrs, idx[0]);
    HA_Encode(adrs, addr);

    XMEMCPY(node + 0 * 32, sig + idx[0] * 32, 32);
    if ((msg[idx[0]] != j) && (msg[idx[0]] != msg[idx[1]])) {
        ret = slhdsakey_chain(key, node, msg[idx[0]],
            (byte)(msg[idx[1]] - msg[idx[0]]), pk_seed, adrs, node);
    }
    if (ret == 0) {
        XMEMCPY(node + 1 * 32, sig + idx[1] * 32, 32);
        XMEMSET(node + 2 * 32, 0, sizeof(node) - 2 * 32);
        if ((msg[idx[1]] != j) && (msg[idx[1]] != msg[idx[2]])) {
            ret = slhdsakey_chain_idx_x4_32(node, msg[idx[1]],
                (word32)(msg[idx[2]] - msg[idx[1]]), pk_seed, addr, idx,
                key->heap);
        }
    }
    if (ret == 0) {
        XMEMCPY(node + 2 * 32, sig + idx[2] * 32, 32);
        if ((cnt > 3) && (msg[idx[2]] != j)) {
            ret = slhdsakey_chain_idx_x4_32(node, msg[idx[2]],
                (word32)(j - msg[idx[2]]), pk_seed, addr, idx, key->heap);
        }
    }
    if (ret == 0) {
        if (cnt > 3) {
            XMEMCPY(node + 3 * 32, sig + idx[3] * 32, 32);
        }
        if (j != SLHDSA_WM1) {
            ret = slhdsakey_chain_idx_x4_32(node, (word32)j,
                (word32)(SLHDSA_WM1 - j), pk_seed, addr, idx, key->heap);
        }
    }
    if (ret == 0) {
        XMEMCPY(nodes + idx[0] * 32, node + 0 * 32, 32);
        XMEMCPY(nodes + idx[1] * 32, node + 1 * 32, 32);
        XMEMCPY(nodes + idx[2] * 32, node + 2 * 32, 32);
        if (cnt > 3) {
            XMEMCPY(nodes + idx[3] * 32, node + 3 * 32, 32);
        }
    }

    return ret;
}
#endif
#endif

#if defined(USE_INTEL_SPEEDUP) && !defined(WOLFSSL_WC_SLHDSA_SMALL)
/* Computes a WOTS+ public key from a message and its signature.
 *
 * Computes four iteration hashes simultaneously.
 *
 * FIPS 205. Section 5.3. Algorithm 8.
 * wots_pkFromSig(sig, M, PK.seed, ADRS)
 *  ...
 *   8: for i from 0 to len - 1 do
 *   9:     ADRS.setChainAddress(i)
 *  ...
 *  11: end for
 *  12: wotspkADRS <- ADRS     > copy address to create WOTS+ public key address
 *  13: wotspkADRS.setTypeAndClear(WOTS_PK)
 *  14: wotspkADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
 *  15: pksig <- Tlen (PK.seed, wotspkADRS, tmp)
 *  16: return pksig
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sig      Signature - (2.n + 3) hashes of length n.
 * @param [in]  msg      Encoded message with checksum.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     WOTS HASH HashAddress.
 * @param [out] pk_sig   Root node - public key signature.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_wots_pk_from_sig_x4(SlhDsaKey* key, const byte* sig,
    const byte* msg, const byte* pk_seed, word32* adrs, byte* pk_sig)
{
    int ret = 0;
    HashAddress wotspk_adrs;
    byte n = key->params->n;
    byte len = key->params->len;
    WC_DECLARE_VAR(nodes, byte, SLHDSA_MAX_MSG_SZ * SLHDSA_MAX_N, key->heap);
    int hash_t_started = 0;

    WC_ALLOC_VAR_EX(nodes, byte, SLHDSA_MAX_MSG_SZ * SLHDSA_MAX_N, key->heap,
        DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E);
#if !defined(WOLFSSL_SLHDSA_PARAM_NO_128)
    if ((ret == 0) && (n == WC_SLHDSA_N_128)) {
        int i;
        sword8 j;
        byte ii = 0;
        byte idx[4] = {0};
        for (j = 0; j <= (sword8)SLHDSA_WM1; j++) {
            for (i = 0; i < len; i++) {
                if ((sword8)msg[i] == j) {
                    idx[ii++] = (byte)i;
                    if (ii == 4) {
                        ret = slhdsakey_chain_idx_to_max_16(key, sig,
                            pk_seed, adrs, msg, idx, j, 4, nodes);
                        if (ret != 0) {
                            break;
                        }
                        ii = 0;
                    }
                }
            }
        }

        if (ret == 0) {
            j = (sword8)max(max(msg[idx[0]], msg[idx[1]]), msg[idx[2]]);
            ret = slhdsakey_chain_idx_to_max_16(key, sig, pk_seed, adrs, msg,
                idx, j, 3, nodes);
        }
    }
    else
#endif
#if !defined(WOLFSSL_SLHDSA_PARAM_NO_192)
    if ((ret == 0) && (n == 24)) {
        int i;
        sword8 j;
        byte ii = 0;
        byte idx[4] = {0};
        for (j = 0; j <= (sword8)SLHDSA_WM1; j++) {
            for (i = 0; i < len; i++) {
                if ((sword8)msg[i] == j) {
                    idx[ii++] = (byte)i;
                    if (ii == 4) {
                        ret = slhdsakey_chain_idx_to_max_24(key, sig,
                            pk_seed, adrs, msg, idx, j, 4, nodes);
                        if (ret != 0) {
                            break;
                        }
                        ii = 0;
                    }
                }
            }
        }

        if (ret == 0) {
            j = (sword8)max(max(msg[idx[0]], msg[idx[1]]), msg[idx[2]]);
            ret = slhdsakey_chain_idx_to_max_24(key, sig, pk_seed, adrs, msg,
                idx, j, 3, nodes);
        }
    }
    else
#endif
#if !defined(WOLFSSL_SLHDSA_PARAM_NO_256)
    if ((ret == 0) && (n == 32)) {
        int i;
        sword8 j;
        byte ii = 0;
        byte idx[4] = {0};
        for (j = 0; j <= (sword8)SLHDSA_WM1; j++) {
            for (i = 0; i < len; i++) {
                if ((sword8)msg[i] == j) {
                    idx[ii++] = (byte)i;
                    if (ii == 4) {
                        ret = slhdsakey_chain_idx_to_max_32(key, sig,
                            pk_seed, adrs, msg, idx, j, 4, nodes);
                        if (ret != 0) {
                            break;
                        }
                        ii = 0;
                    }
                }
            }
        }

        if (ret == 0) {
            j = (sword8)max(max(msg[idx[0]], msg[idx[1]]), msg[idx[2]]);
            ret = slhdsakey_chain_idx_to_max_32(key, sig, pk_seed, adrs, msg,
                idx, j, 3, nodes);
        }
    }
    else
#endif
    {
        (void)msg;
        (void)key;
        if (ret == 0) {
            ret = NOT_COMPILED_IN;
        }
    }

    if (ret == 0) {
        HA_Copy(wotspk_adrs, adrs);
        HA_SetTypeAndClearNotKPA(wotspk_adrs, HA_WOTS_PK);
        ret = HASH_T_START_ADDR(key, pk_seed, wotspk_adrs, n);
    }
    if (ret == 0) {
        hash_t_started = 1;
        ret = HASH_T_UPDATE(key, nodes, (word32)len * n);
        sig += len * n;
    }
    if (ret == 0) {
        ret = HASH_T_FINAL(key, pk_sig, n);
    }
    if (hash_t_started) {
        HASH_T_FREE(key);
    }

    WC_FREE_VAR_EX(nodes, key->heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}
#endif

#if !defined(WOLFSSL_WC_SLHDSA_SMALL_MEM)
/* Computes a WOTS+ public key from a message and its signature.
 *
 * FIPS 205. Section 5.3. Algorithm 8.
 * wots_pkFromSig(sig, M, PK.seed, ADRS)
 *  ...
 *   8: for i from 0 to len - 1 do
 *   9:     ADRS.setChainAddress(i)
 *  10:     tmp[i] <- chain(sig[i], msg[i], w - 1 - msg[i], PK.seed, ADRS)
 *  11: end for
 *  12: wotspkADRS <- ADRS     > copy address to create WOTS+ public key address
 *  13: wotspkADRS.setTypeAndClear(WOTS_PK)
 *  14: wotspkADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
 *  15: pksig <- Tlen(PK.seed, wotspkADRS, tmp)
 *  16: return pksig
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sig      Signature - (2.n + 3) hashes of length n.
 * @param [in]  msg      Encoded message with checksum.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     WOTS HASH HashAddress.
 * @param [out] pk_sig   Root node - public key signature.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_wots_pk_from_sig_c(SlhDsaKey* key, const byte* sig,
    const byte* msg, const byte* pk_seed, word32* adrs, byte* pk_sig)
{
    int ret = 0;
    int i;
    byte n = key->params->n;
    byte len = key->params->len;
    HashAddress wotspk_adrs;
    WC_DECLARE_VAR(nodes, byte, SLHDSA_MAX_MSG_SZ * SLHDSA_MAX_N, key->heap);
    int hash_t_started = 0;

    WC_ALLOC_VAR_EX(nodes, byte, SLHDSA_MAX_MSG_SZ * SLHDSA_MAX_N, key->heap,
        DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E);
    if (ret == 0) {
        /* Step 8: For each value in msg. */
        for (i = 0; i < len; i++) {
            /* Step 9: Set chain address for WOTS HASH. */
            HA_SetChainAddress(adrs, i);
            /* Step 10: Chain the hash from the msg value to w-1. */
            ret = slhdsakey_chain(key, sig, msg[i], (byte)(SLHDSA_WM1 - msg[i]),
                pk_seed, adrs, nodes + i * n);
            if (ret != 0) {
                break;
            }
            /* Move on to next signature hash. */
            sig += n;
        }
    }
    if (ret == 0) {
        /* Step 12-14: Copy the address for WOTS PK. */
        HA_Copy(wotspk_adrs, adrs);
        HA_SetTypeAndClearNotKPA(wotspk_adrs, HA_WOTS_PK);
        /* Step 15: Hash the public key seed and WOTS PK address ... */
        ret = HASH_T_START_ADDR(key, pk_seed, wotspk_adrs, n);
    }
    if (ret == 0) {
        hash_t_started = 1;
        /* Step 15: Update with the nodes ... */
        ret = HASH_T_UPDATE(key, nodes, (word32)len * n);
    }
    if (ret == 0) {
        /* Step 15: Generate root node - public key signature. */
        ret = HASH_T_FINAL(key, pk_sig, n);
    }
    if (hash_t_started) {
        HASH_T_FREE(key);
    }

    WC_FREE_VAR_EX(nodes, key->heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}
#else
/* Computes a WOTS+ public key from a message and its signature.
 *
 * FIPS 205. Section 5.3. Algorithm 8.
 * wots_pkFromSig(sig, M, PK.seed, ADRS)
 *  ...
 *   8: for i from 0 to len - 1 do
 *   9:     ADRS.setChainAddress(i)
 *  10:     tmp[i] <- chain(sig[i], msg[i], w - 1 - msg[i], PK.seed, ADRS)
 *  11: end for
 *  12: wotspkADRS <- ADRS     > copy address to create WOTS+ public key address
 *  13: wotspkADRS.setTypeAndClear(WOTS_PK)
 *  14: wotspkADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
 *  15: pksig <- Tlen (PK.seed, wotspkADRS, tmp)
 *  16: return pksig
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sig      Signature - (2.n + 3) hashes of length n.
 * @param [in]  msg      Encoded message with checksum.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     WOTS HASH HashAddress.
 * @param [out] pk_sig   Root node - public key signature.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_wots_pk_from_sig_c(SlhDsaKey* key, const byte* sig,
    const byte* msg, const byte* pk_seed, word32* adrs, byte* pk_sig)
{
    int ret;
    int i;
    byte n = key->params->n;
    byte len = key->params->len;
    HashAddress wotspk_adrs;
    byte* node = pk_sig;
    int hash_t_started = 0;

    /* Step 12-14: Copy the address for WOTS PK. */
    HA_Copy(wotspk_adrs, adrs);
    HA_SetTypeAndClearNotKPA(wotspk_adrs, HA_WOTS_PK);
    /* Step 15: Hash the public key seed and WOTS PK address ... */
    ret = HASH_T_START_ADDR(key, pk_seed, wotspk_adrs, n);
    if (ret == 0) {
        hash_t_started = 1;
        /* Step 8: For each value in msg. */
        for (i = 0; i < len; i++) {
            /* Step 9: Set chain address for WOTS HASH. */
            HA_SetChainAddress(adrs, i);
            /* Step 10: Chain the hash from the msg value to w-1. */
            ret = slhdsakey_chain(key, sig, msg[i], (byte)(SLHDSA_WM1 - msg[i]),
                pk_seed, adrs, node);
            if (ret != 0) {
                break;
            }
            /* Step 15: Update with node ... */
            ret = HASH_T_UPDATE(key, node, n);
            if (ret != 0) {
                break;
            }
            /* Move on to next signature hash. */
            sig += n;
        }
    }
    if (ret == 0) {
        /* Step 15: Generate root node - public key signature. */
        ret = HASH_T_FINAL(key, pk_sig, n);
    }
    if (hash_t_started) {
        HASH_T_FREE(key);
    }

    return ret;
}
#endif

/* Computes a WOTS+ public key from a message and its signature.
 *
 * FIPS 205. Section 5.3. Algorithm 8.
 * wots_pkFromSig(sig, M, PK.seed, ADRS)
 *   1: csum <- 0
 *   2: msg <- base_2b(M , lgw , len1 )              > convert message to base w
 *   3: for i from 0 to len1 - 1 do
 *   4:     csum <- csum + w - 1 - msg[i]
 *   5: end for                                               > compute checksum
 *   6: csum <- csum << ((8 - ((len2.lgw) mod 8)) mod 8)
 *                                                > for lgw = 4, left shift by 4
 *   7: msg <- msg || base_2b(toByte(csum, upper(len2.lgw/8)), lgw , len2)
 *  ...
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sig      Signature - (2.n + 3) hashes of length n.
 * @param [in]  m        Message.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     WOTS HASH HashAddress.
 * @param [out] pk_sig   Root node - public key signature.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_wots_pk_from_sig(SlhDsaKey* key, const byte* sig,
    const byte* m, const byte* pk_seed, word32* adrs, byte* pk_sig)
{
    int ret;
    word16 csum;
    byte n = key->params->n;
    int i;
    byte msg[SLHDSA_MAX_MSG_SZ];

    /* Step 1: Start csum at 0 */
    csum = 0;
    /* Step 3: For each byte in message. */
    for (i = 0; i < n * 2; i += 2) {
        /* Step 2: Append high order 4 bits to msg. */
        msg[i+0] = (byte)((m[i / 2] >> 4) & 0xf);
        /* Step 4: Calculate checksum with first lgw bits. */
        csum = (word16)(csum + SLHDSA_WM1 - msg[i + 0]);
        /* Step 2: Append low order 4 bits to msg. */
        msg[i+1] = (byte)( m[i / 2]       & 0xf);
        /* Step 4: Calculate checksum with next lgw bits. */
        csum = (word16)(csum + SLHDSA_WM1 - msg[i + 1]);
    }
    /* Steps 6-7: Encode bottom 12 bits of csum onto end of msg. */
    msg[i + 0] = (byte)((csum >> 8) & 0xf);
    msg[i + 1] = (byte)((csum >> 4) & 0xf);
    msg[i + 2] = (byte)( csum       & 0xf);

    /* Steps 8-16. */
#if defined(USE_INTEL_SPEEDUP) && !defined(WOLFSSL_WC_SLHDSA_SMALL)
    if (!SLHDSA_IS_SHA2(key->params->param) &&
            IS_INTEL_AVX2(cpuid_flags) &&
            (SAVE_VECTOR_REGISTERS2() == 0)) {
        ret = slhdsakey_wots_pk_from_sig_x4(key, sig, msg, pk_seed, adrs,
            pk_sig);
        RESTORE_VECTOR_REGISTERS();
    }
    else
#endif
    {
        ret = slhdsakey_wots_pk_from_sig_c(key, sig, msg, pk_seed, adrs,
            pk_sig);
    }

    return ret;
}

/******************************************************************************
 * XMSS
 ******************************************************************************/

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
#ifndef WOLFSSL_WC_SLHDSA_RECURSIVE
/* Compute the root node of Merkle subtree of WOTS+ public keys.
 *
 * Algorithm 9 xmss_node(SK.seed, i, z, PK.seed, ADRS)
 *   1: if z = 0 then
 *   2:     ADRS.setTypeAndClear(WOTS_HASH)
 *   3:     ADRS.setKeyPairAddress(i)
 *   4:     node <- wots_pkGen(SK.seed, PK.seed, ADRS)
 *   5: else
 *   6:     lnode <- xmss_node(SK.seed, 2i, z - 1, PK.seed, ADRS)
 *   7:     rnode <- xmss_node(SK.seed, 2i + 1, z - 1, PK.seed, ADRS)
 *   8:     ADRS.setTypeAndClear(TREE)
 *   9:     ADRS.setTreeHeight(z)
 *  10:     ADRS.setTreeIndex(i)
 *  11:     node <- H(PK.seed, ADRS, lnode || rnode)
 *  12: end if
 *  13: return node
 *
 * @param [in]       key      SLH-DSA key.
 * @param [in]       sk_seed  Private key seed.
 * @param [in]       i        Node index.
 * @param [in]       z        Node height.
 * @param [in]       pk_seed  Public key seed.
 * @param [in, out]  adrs     HashAddress - WOTS HASH.
 * @param [out]      node     Root node.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_xmss_node(SlhDsaKey* key, const byte* sk_seed, int i,
    int z, const byte* pk_seed, word32* adrs, byte* node)
{
    int ret = 0;

    /* Step 1: Are we at the bottom of the subtree. */
    if (z == 0) {
        /* Step 2: Copy the address for WOTS HASH. */
        HA_SetTypeAndClearNotKPA(adrs, HA_WOTS_HASH);
        /* Step 3: Set key pair address. */
        HA_SetKeyPairAddress(adrs, i);
        /* Step 4: Generate WOTS+ public key. */
        ret = slhdsakey_wots_pkgen(key, sk_seed, pk_seed, adrs, node);
    }
    else {
        WC_DECLARE_VAR(nodes, byte, (SLHDSA_MAX_H_M + 2) * SLHDSA_MAX_N,
            key->heap);
        word32 j;
        word32 k;
        word32 m = (word32)1U << z;
        byte n = key->params->n;

        WC_ALLOC_VAR_EX(nodes, byte, (SLHDSA_MAX_H_M + 2) * SLHDSA_MAX_N,
            key->heap, DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E);
        if (ret == 0) {
            /* For each node at bottom of tree. */
            for (j = 0; j < m; j++) {
                /* Step 2: Copy the address for WOTS HASH. */
                HA_SetTypeAndClearNotKPA(adrs, HA_WOTS_HASH);
                /* Step 3: Set key pair address. */
                HA_SetKeyPairAddress(adrs, m * (word32)i + j);
                /* Step 4: Generate WOTS+ public key. */
                ret = slhdsakey_wots_pkgen(key, sk_seed, pk_seed, adrs,
                    nodes + ((word32)z - 1U + (j & 1U)) * n);
                if (ret != 0) {
                    break;
                }

                /* For intermediate nodes. */
                for (k = (word32)z - 1U; k > 0; k--) {
                    if (((j >> ((word32)z - 1U - k)) & 1U) == 1U) {
                        /* Step 6 and 7 have been done.  */
                        /* Steps 8-10: Step type, height and index for TREE. */
                        HA_SetTypeAndClear(adrs, HA_TREE);
                        HA_SetTreeHeight(adrs, (word32)z - k);
                        HA_SetTreeIndex(adrs,
                                        (m * (word32)i + j) >> ((word32)z - k));
                        /* Step 11: Calculate node from two below. */
                        ret = HASH_H(key, pk_seed, adrs, nodes + k * n, n,
                                nodes +
                                  (k - 1U + ((j >> ((word32)z - k)) & 1U)) * n);
                        if (ret != 0) {
                            break;
                        }
                    }
                    else {
                        break;
                    }
                }
                if (ret != 0) {
                    break;
                }
            }
            if (ret == 0) {
                /* Root node into output. */
                /* Steps 8-10: Step type, height and index for TREE. */
                HA_SetTypeAndClear(adrs, HA_TREE);
                HA_SetTreeHeight(adrs, z);
                HA_SetTreeIndex(adrs, i);
                /* Step 11: Calculate node from two below. */
                ret = HASH_H(key, pk_seed, adrs, nodes, n, node);
            }
        }

        WC_FREE_VAR_EX(nodes, key->heap, DYNAMIC_TYPE_SLHDSA);
    }

    return ret;
}
#else
/* Compute the root node of Merkle subtree of WOTS+ public keys.
 *
 * FIPS 205. Section 6.1. Algorithm 9.
 * xmss_node(SK.seed, i, z, PK.seed, ADRS)
 *   1: if z = 0 then
 *   2:     ADRS.setTypeAndClear(WOTS_HASH)
 *   3:     ADRS.setKeyPairAddress(i)
 *   4:     node <- wots_pkGen(SK.seed, PK.seed, ADRS)
 *   5: else
 *   6:     lnode <- xmss_node(SK.seed, 2i, z - 1, PK.seed, ADRS)
 *   7:     rnode <- xmss_node(SK.seed, 2i + 1, z - 1, PK.seed, ADRS)
 *   8:     ADRS.setTypeAndClear(TREE)
 *   9:     ADRS.setTreeHeight(z)
 *  10:     ADRS.setTreeIndex(i)
 *  11:     node <- H(PK.seed, ADRS, lnode || rnode)
 *  12: end if
 *  13: return node
 *
 * @param [in]       key      SLH-DSA key.
 * @param [in]       sk_seed  Private key seed.
 * @param [in]       i        Node index.
 * @param [in]       z        Node height.
 * @param [in]       pk_seed  Public key seed.
 * @param [in, out]  adrs     HashAddress - WOTS HASH.
 * @param [out]      node     Root node.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_xmss_node(SlhDsaKey* key, const byte* sk_seed, int i,
    int z, const byte* pk_seed, word32* adrs, byte* node)
{
    int ret;
    byte nodes[2 * SLHDSA_MAX_N];

    /* Step 1: Are we at the bottom of the subtree. */
    if (z == 0) {
        /* Step 2: Copy the address for WOTS HASH. */
        HA_SetTypeAndClearNotKPA(adrs, HA_WOTS_HASH);
        /* Step 3: Set key pair address. */
        HA_SetKeyPairAddress(adrs, i);
        /* Step 4: Generate WOTS+ public key. */
        ret = slhdsakey_wots_pkgen(key, sk_seed, pk_seed, adrs, node);
    }
    else {
        byte n = key->params->n;

        /* Step 6: Calculate left node recursively. */
        ret = slhdsakey_xmss_node(key, sk_seed, 2 * i, z - 1, pk_seed, adrs,
            nodes);
        if (ret == 0) {
            /* Step 7: Calculate right node recursively. */
            ret = slhdsakey_xmss_node(key, sk_seed, 2 * i + 1, z - 1, pk_seed,
                adrs, nodes + n);
        }
        if (ret == 0) {
            /* Steps 8-10: Step type, height and index for TREE. */
            HA_SetTypeAndClear(adrs, HA_TREE);
            HA_SetTreeHeight(adrs, z);
            HA_SetTreeIndex(adrs, i);
            /* Step 11: Calculate node from two below. */
            ret = HASH_H(key, pk_seed, adrs, nodes, n, node);
        }
    }

    return ret;
}
#endif

/* Generate XMSS signature.
 *
 * FIPS 205. Section 6.2. Algorithm 10.
 * xmss_sign(M SK.seed, idx PK.seed, ADRS)
 *   1: for j from 0 to h' - 1 do                    > build authentication path
 *   2:     k <- lower(idx/2^j) XOR 1
 *   3:     AUTH[j] <- xmss_node(SK.seed, k, j, PK.seed, ADRS)
 *   4: end for
 *   5: ADRS.setTypeAndClear(WOTS_HASH)
 *   6: ADRS.setKeyPairAddress(idx)
 *   7: sig <- wots_sign(M , SK.seed, PK.seed, ADRS)
 *   8: SIGXMSS <- sig || AUTH
 *   9: return SIGXMSS
 *
 * @param [in]  key       SLH-DSA key.
 * @param [in]  m         n-byte message.
 * @param [in]  sk_seed   Private key seed.
 * @param [in]  idx       Key pair address of WOTS hash.
 * @param [in]  pk_seed   Public key seed.
 * @param [in]  adrs      HashAddress.
 * @param [out] sig_xmss  XMSS signature.
 *                        len n-byte nodes and h' authentication nodes.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_xmss_sign(SlhDsaKey* key, const byte* m,
    const byte* sk_seed, word32 idx, const byte* pk_seed, word32* adrs,
    byte* sig_xmss)
{
    int ret = WC_NO_ERR_TRACE(BAD_FUNC_ARG);
    byte n = key->params->n;
    byte len = key->params->len;
    byte h_m = key->params->h_m;
    /* Step 8: Place authentication nodes after WOTS+ signature. */
    byte* auth = sig_xmss + (len * n);
    word32 i = idx;
    int j;

    /* Step 1: For each height of XMSS tree. */
    for (j = 0; j < h_m; j++) {
        /* Step 2: Calculate index of other node. */
        word32 k = i ^ 1;
        /* Step 3: Calculate authentication node. */
        ret = slhdsakey_xmss_node(key, sk_seed, (int)k, j, pk_seed, adrs,
            auth);
        if (ret != 0) {
            break;
        }
        /* Step 3: Move to next authentication node. */
        auth += n;
        /* Step 2: Update index. */
        i >>= 1;
    }

    if (ret == 0) {
        /* Step 5: Set address of WOTS HASH. */
        HA_SetTypeAndClearNotKPA(adrs, HA_WOTS_HASH);
        /* Step 6: Set key pair address into address. */
        HA_SetKeyPairAddress(adrs, idx);
        /* Step 7: WOTS+ sign message. */
        ret = slhdsakey_wots_sign(key, m, sk_seed, pk_seed, adrs, sig_xmss);
    }

    return ret;
}
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

/* Compute XMSS public key from XMSS signature.
 *
 * FIPS 205. Section 6.3. Algorithm 11.
 * xmss_pkFromSig(idx, SIGXMSS, M PK.seed, ADRS)
 *   1: ADRS.setTypeAndClear(WOTS_HASH)        > compute WOTS+ pk from WOTS+ sig
 *   2: ADRS.setKeyPairAddress(idx)
 *   3: sig <- SIGXMSS.getWOTSSig()                      > SIGXMSS [0 : len . n]
 *   4: AUTH <- SIGXMSS.getXMSSAUTH()       > SIGXMSS [len . n : (len + h') . n]
 *   5: node[0] <- wots_pkFromSig(sig, M, PK.seed, ADRS)
 *   6: ADRS.setTypeAndClear(TREE)         > compute root from WOTS+ pk and AUTH
 *   7: ADRS.setTreeIndex(idx
 *   8: for k from 0 to h' - 1 do
 *   9:     ADRS.setTreeHeight(k + 1)
 *  10:     if lower(idx/2^k) is even then
 *  11:         ADRS.setTreeIndex(ADRS.getTreeIndex()/2)
 *  12:         node[1] <- H(PK.seed, ADRS, node[0] || AUTH[k])
 *  13:     else
 *  14:         ADRS.setTreeIndex((ADRS.getTreeIndex() - 1)/2)
 *  15:         node[1] <- H(PK.seed, ADRS, AUTH[k] || node[0])
 *  16:     end if
 *  17:     node[0] <- node[1]
 *  18: end for
 *  19: return node[0]
 *
 * @param [in]  key       SLH-DSA key.
 * @param [in]  idx       Key pair address of WOTS hash.
 * @param [in]  sig_xmss  XMSS signature.
 *                        len n-byte nodes and h' authentication nodes.
 * @param [in]  m         n-byte message.
 * @param [in]  pk_seed   Public key seed.
 * @param [in]  adrs      HashAddress.
 * @param [out] node      XMSS public key.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_xmss_pk_from_sig(SlhDsaKey* key, word32 idx,
    const byte* sig_xmss, const byte* m, const byte* pk_seed, word32* adrs,
    byte* node)
{
    int ret;
    byte n = key->params->n;
    byte h_m = key->params->h_m;
    byte len = key->params->len;
    /* Step  3: Set pointer to first signature node. */
    const byte* sig = sig_xmss;
    /* Step 4: Set pointer to first authentication node. */
    const byte* auth = sig_xmss + (len * n);
    int k;

    /* Step 1: Set address type to WOTS HASH. */
    HA_SetTypeAndClear(adrs, HA_WOTS_HASH);
    /* Step 2: Set key pair address. */
    HA_SetKeyPairAddress(adrs, idx);
    /* Step 5: Compute WOTS+ public key from signature. */
    ret = slhdsakey_wots_pk_from_sig(key, sig, m, pk_seed, adrs, node);
    if (ret == 0) {
        /* Step 6: Set address type to TREE. */
        HA_SetTypeAndClear(adrs, HA_TREE);
        /* Step 2: Set key pair address. */
        HA_SetTreeIndex(adrs, idx);
        /* Step 8: For each height of the XMSS tree. */
        for (k = 0; k < h_m; k++) {
            /* Calculate which side the current and authentication nodes are. */
            byte side = idx & 1;
            /* Update tree index. */
            idx >>= 1;

            /* Step 9: Set tree height. */
            HA_SetTreeHeight(adrs, k + 1);
            /* Steps 11 and 14: Set tree index. */
            HA_SetTreeIndex(adrs, idx);
            /* Step 10: Check which order to put nodes. */
            if (side == 0) {
                /* Steps 12,17: Calculate node with sig node on right. */
                ret = HASH_H_2(key, pk_seed, adrs, node, auth, n, node);
            }
            else {
                /* Steps 15,17: Calculate node with sig node on left. */
                ret = HASH_H_2(key, pk_seed, adrs, auth, node, n, node);
            }
            if (ret != 0) {
                break;
            }
            /* Next authentication node. */
            auth += n;
        }
    }

    return ret;
}

/******************************************************************************
 * HT - HyperTree
 ******************************************************************************/

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
/* Generate hypertree signature.
 *
 * FIPS 205. Section 7.1. Algorithm 12.
 * ht_sign(M SK.seed, PK.seed, idxtree, idxleaf)
 *   1: ADRS <- toByte(0, 32)
 *   2: ADRS.setTreeAddress(idxtree)
 *   3: SIGtmp <- xmss_sign(x, SK.seed, idxleaf, PK.seed, ADRS)
 *   4: SIGHT <- SIGtmp
 *   5: root <- xmss_pkFromSig(idxleaf, SIGtmp, M, PK.seed, ADRS)
 *   6: for j from 1 to d - 1 do
 *   7:     idxleaf <- idxleaf mod 2^h'   > h' least significant bits of idxtree
 *   8:     idxtree <- idxtree >> h'
 *                               > remove least significant h' bits from idxtree
 *   9:     ADRS.setLayerAddress(j)
 *  10:    ADRS.setTreeAddress(idxtree)
 *  11:    SIGtmp <- xmss_sign(root, SK.seed, idxleaf, PK.seed, ADRS)
 *  12:    SIGHT <- SIGHT || SIGtmp
 *  13:    if j < d - 1 then
 *  14:        root <- xmss_pkFromSig(idxleaf, SIGtmp, root, PK.seed, ADRS)
 *  15:    end if
 *  16: end for
 *  17: return SIGHT
 *
 * @param [in]  key       SLH-DSA key.
 * @param [in]  pk_fors   FORS public key.
 * @param [in]  sk_seed   Private key seed.
 * @param [in]  pk_seed   Public key seed.
 * @param [in]  idx_tree  Tree address.
 * @param [in]  idx_leaf  Key pair address.
 * @param [out] sig_ht    Hypertree signature - d x n-byte nodes.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_ht_sign(SlhDsaKey* key, const byte* pk_fors,
    const byte* sk_seed, const byte* pk_seed, word32* idx_tree, word32 idx_leaf,
    byte* sig_ht)
{
    int ret;
    HashAddress adrs;
    byte root[SLHDSA_MAX_N];
    byte n = key->params->n;
    byte h_m = key->params->h_m;
    byte len = key->params->len;
    byte d = key->params->d;
    int j;
    word32 mask = ((word32)1U << h_m) - 1U;

    /* Step 1: Set address to all zeros. */
    HA_Init(adrs);
    /* Step 2: Set tree address. */
    HA_SetTreeAddress(adrs, idx_tree);
    /* Step 3: Compute XMSS signature. */
    ret = slhdsakey_xmss_sign(key, pk_fors, sk_seed, idx_leaf, pk_seed, adrs,
        sig_ht);
    if (ret == 0) {
        /* Step 5: Compute root/public key from signature. */
        ret = slhdsakey_xmss_pk_from_sig(key, idx_leaf, sig_ht, pk_fors,
            pk_seed, adrs, root);
        /* Step 4: Step hypertree signature over XMSS signature. */
        sig_ht += (h_m + len) * n;
    }
    if (ret == 0) {
        /* Step 6: For remaining depths. */
        for (j = 1; j < d; j++) {
            /* Step 7: Get bottom h' bits for index into tree. */
            idx_leaf = INDEX_TREE_MASK(idx_tree, mask);
            /* Step 8: Update tree index to exclude this subtree. */
            INDEX_TREE_SHIFT_DOWN(idx_tree, h_m);
            /* Step 9: Set layer address. */
            HA_SetLayerAddress(adrs, j);
            /* Step 10: Set tree index. */
            HA_SetTreeAddress(adrs, idx_tree);
            /* Step 11: Compute XMSS signature. */
            ret = slhdsakey_xmss_sign(key, root, sk_seed, idx_leaf, pk_seed,
                adrs, sig_ht);
            if (ret != 0) {
                break;
            }
            /* Step 13: Check if we need to calculate next root. */
            if (j < d) {
                /* Step 14: Compute root/public key from signature. */
                ret = slhdsakey_xmss_pk_from_sig(key, idx_leaf, sig_ht, root,
                    pk_seed, adrs, root);
                if (ret != 0) {
                    break;
                }
            }
            /* Step 12: Step hypertree signature over XMSS signature. */
            sig_ht += (h_m + len) * n;
        }
    }

    return ret;
}
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

/* Verify hypertree signature.
 *
 * FIPS 205. Section 7.2 Algorithm 13.
 * ht_verify(M SIGHT, PK.seed, idxtree, idxleaf, PK.root)
 *   1: ADRS <- toByte(0, 32)
 *   2: ADRS.setTreeAddress(idxtree)
 *   3: SIGtmp <- SIGHT.getXMSSSignature(0)          > SIGHT[0 : (h' + len) . n]
 *   4: node <- xmss_pkFromSig(idxleaf, SIGtmp, M, PK.seed, ADRS)
 *   5: for j from 1 to d - 1 do
 *   6:     idxleaf <- idxtree mod 2^h'   > h' least significant bits of idxtree
 *   7:     idxtree <- idxtree >> h'
 *                               > remove least significant h' bits from idxtree
 *   8:     ADRS.setLayerAddress(j)
 *   9:     ADRS.setTreeAddress(idxtree)
 *  10:     SIGtmp <- SIGHT .getXMSSSignature(j)
 *                            > SIGHT[h . (h' + len) . n : (j + 1)(h' + len . n]
 *  11:     node <- xmss_pkFromSig(idxleaf, SIGtmp, node, PK.seed, ADRS)
 *  12: end for
 *  13: if node = PK.root then
 *  14:     return true
 *  15: else
 *  16:     return false
 *  17: end if
 *
 * @param [in] key       SLH-DSA key.
 * @param [in] m         Message to verify.
 * @param [in] sig_ht    Hypertree signature.
 * @param [in] pk_seed   Public key seed.
 * @param [in] idx_tree  Tree address.
 * @param [in] idx_leaf  Key pair address.
 * @param [in] pk_root   Public key root node.
 * @return  0 on success.
 * @return  SIG_VERIFY_E when calculated node doesn't match public key node.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_ht_verify(SlhDsaKey* key, const byte* m,
    const byte* sig_ht, const byte* pk_seed, word32* idx_tree, word32 idx_leaf,
    const byte* pk_root)
{
    int ret;
    HashAddress adrs;
    byte node[SLHDSA_MAX_N];
    byte n = key->params->n;
    byte h_m = key->params->h_m;
    byte len = key->params->len;
    byte d = key->params->d;
    int j;
    /* For Step 6. */
    word32 mask = ((word32)1U << h_m) - 1U;

    /* Step 1: Set address to all zeros. */
    HA_Init(adrs);
    /* Step 2: Set tree address. */
    HA_SetTreeAddress(adrs, idx_tree);
    /* Step 4: Get public key node from XMSS signature. */
    ret = slhdsakey_xmss_pk_from_sig(key, idx_leaf, sig_ht, m, pk_seed, adrs,
        node);
    /* Step 3: Move over XMSS signature. */
    sig_ht += (h_m + len) * n;

    if (ret == 0) {
        /* Step 5: For remaining depths. */
        for (j = 1; j < d; j++) {
            /* Step 6: Get bottom h' bits for index into tree. */
            idx_leaf = INDEX_TREE_MASK(idx_tree, mask);
            /* Step 7: Update tree index to exclude this subtree. */
            INDEX_TREE_SHIFT_DOWN(idx_tree, h_m);
            /* Step 8: Set layer address. */
            HA_SetLayerAddress(adrs, j);
            /* Step 9: Set tree index. */
            HA_SetTreeAddress(adrs, idx_tree);
            /* Step 11: Get public key node from XMSS signature. */
            ret = slhdsakey_xmss_pk_from_sig(key, idx_leaf, sig_ht, node,
                pk_seed, adrs, node);
            if (ret != 0) {
                break;
            }
            /* Step 10: Move over XMSS signature. */
            sig_ht += (h_m + len) * n;
        }
    }
    /* Step 13: Compare computed node with public key root. */
    if ((ret == 0) && (XMEMCMP(node, pk_root, n) != 0)) {
        /* Step 16: Return signature verification failed. */
        ret = SIG_VERIFY_E;
    }

    return ret;
}

/******************************************************************************
 * FORS
 ******************************************************************************/

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
/* Generate FORS private-key value.
 *
 * FIPS 205. Section 8.1. Algorithm 14
 * fors_skGen(SK.seed, PK.seed, ADRS, idx)
 *   1: skADRS <- ADRS           > copy address to create key generation address
 *   2: skADRS.setTypeAndClear(FORS_PRF)
 *   3: skADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
 *   4: skADRS.setTreeIndex(idx)
 *   5: return PRF(PK.seed, SK.seed, skADRS)
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     HashAddress.
 * @param [in]  idx      Private key index.
 * @param [out] node     FORS private-key value.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_fors_sk_gen(SlhDsaKey* key, const byte* sk_seed,
    const byte* pk_seed, word32* adrs, word32 idx, byte* node)
{
    HashAddress sk_adrs;

    /* Step 1: Copy address to FORS PRF. */
    HA_Copy(sk_adrs, adrs);
    /* Steps 2-3: Set type and keep key pair address. */
    HA_SetTypeAndClearNotKPA(sk_adrs, HA_FORS_PRF);
    /* Step 4: Set tree index. */
    HA_SetTreeIndex(sk_adrs, idx);
    /* Step 5: Hash seeds and address. */
    return HASH_PRF(key, pk_seed, sk_seed, sk_adrs, key->params->n,
        node);
}

#if defined(USE_INTEL_SPEEDUP) && !defined(WOLFSSL_WC_SLHDSA_SMALL)
/* PRF hash 4 simultaneously.
 *
 * Each hash varies by the tree index with the first value in sequence passed
 * in.
 *
 * FIPS 205. Section 4.1.
 *   PRF(PK.seed, SK.seed, ADRS) (Bn x Bn x B32 -> Bn) is a PRF that is used to
 *   generate the secret values in WOTS+ and FORS private keys.
 * FIPS 205. Section 11.1.
 *   PRF(PK.seed, SK.seed, ADRS) = SHAKE256(PK.seed || ADRS || SK.seed, 8n)
 *
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  addr     Encoded HashAddress.
 * @param [in]  n        Number of bytes in hash output.
 * @param [in]  ti       Tree index start value.
 * @param [out] node     Buffer to hold hash output.
 * @param [in]  heap     Dynamic memory allocation hint.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_hash_prf_ti_x4(const byte* pk_seed, const byte* sk_seed,
    byte* addr, byte n, word32 ti, byte* node, void* heap)
{
    int ret = 0;
    word32 o = 0;
    WC_DECLARE_VAR(state, word64, 25 * 4, heap);

    (void)heap;

    WC_ALLOC_VAR_EX(state, word64, 25 * 4, heap, DYNAMIC_TYPE_SLHDSA,
        ret = MEMORY_E);
    if (ret == 0) {
        o = slhdsakey_shake256_set_seed_ha_hash_x4(state, pk_seed, addr,
            sk_seed, n);
        SHAKE256_SET_TREE_INDEX(state, o, ti);
        ret = SAVE_VECTOR_REGISTERS2();
        if (ret == 0) {
            sha3_blocksx4_avx2(state);
            RESTORE_VECTOR_REGISTERS();
            slhdsakey_shake256_get_hash_x4(state, node, n);
        }

        WC_FREE_VAR_EX(state, heap, DYNAMIC_TYPE_SLHDSA);
    }

    return ret;
}

/* F hash 4 simultaneously.
 *
 * Each hash varies by the tree index with the first value in sequence passed
 * in.
 *
 * FIPS 205. Section 4.1.
 *   F(PK.seed, ADRS, M1) (Bn x B32 x Bn -> Bn) is a hash function that takes an
 *   n-byte message as input and produces an n-byte output.
 * FIPS 205. Section 11.1.
 *   F(PK.seed, ADRS, M1) = SHAKE256(PK.seed || ADRS || M1 , 8n)
 *
 * @param [in]      pk_seed  Public key seed.
 * @param [in]      addr     Encoded HashAddress.
 * @param [in, out] node     On in, n-byte messages. On out, n-byte outputs.
 * @param [in]      n        Number of bytes in hash output.
 * @param [in]      ti       Tree index start value.
 * @param [in]      heap     Dynamic memory allocation hint.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_hash_f_ti_x4(const byte* pk_seed, byte* addr, byte* node,
    byte n, word32 ti, void* heap)
{
    int ret = 0;
    int i;
    word32 o = 0;
    WC_DECLARE_VAR(state, word64, 25 * 4, heap);

    (void)heap;

    WC_ALLOC_VAR_EX(state, word64, 25 * 4, heap, DYNAMIC_TYPE_SLHDSA,
        ret = MEMORY_E);
    if (ret == 0) {
        o = slhdsakey_shake256_set_seed_ha_x4(state, pk_seed, addr, n);
        SHAKE256_SET_TREE_INDEX(state, o, ti);
        for (i = 0; i < n / 8; i++) {
            state[o + 0] = ((word64*)(node + 0 * n))[i];
            state[o + 1] = ((word64*)(node + 1 * n))[i];
            state[o + 2] = ((word64*)(node + 2 * n))[i];
            state[o + 3] = ((word64*)(node + 3 * n))[i];
            o += 4;
        }
        SHAKE256_SET_END_X4(state, o);
        ret = SAVE_VECTOR_REGISTERS2();
        if (ret == 0) {
            sha3_blocksx4_avx2(state);
            RESTORE_VECTOR_REGISTERS();
            slhdsakey_shake256_get_hash_x4(state, node, n);
        }

        WC_FREE_VAR_EX(state, heap, DYNAMIC_TYPE_SLHDSA);
    }

    return ret;
}

/* H hash 4 simultaneously.
 *
 * Each hash varies by the tree index with the first value in sequence passed
 * in.
 *
 * FIPS 205. Section 4.1.
 *   H(PK.seed, ADRS, M2) (Bn x B32 x B2n -> Bn) is a special case of Tl that
 *   takes a 2n-byte message as input.
 * FIPS 205. Section 11.1.
 *   H(PK.seed, ADRS, M2) = SHAKE256(PK.seed || ADRS || M2, 8n)
 *
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  addr     Encoded HashAddress.
 * @param [in]  m        2n-byte message.
 * @param [in]  n        Number of bytes in hash output.
 * @param [in]  ti       Tree index start value.
 * @param [out] hash     Buffer to hold hash output.
 * @param [in]  heap     Dynamic memory allocation hint.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_hash_h_ti_x4(const byte* pk_seed, byte* addr,
    const byte* m, byte n, word32 ti, byte* hash, void* heap)
{
    int ret = 0;
    int i;
    word32 o = 0;
    WC_DECLARE_VAR(state, word64, 25 * 4, heap);

    (void)heap;

    WC_ALLOC_VAR_EX(state, word64, 25 * 4, heap, DYNAMIC_TYPE_SLHDSA,
        ret = MEMORY_E);
    if (ret == 0) {
        o = slhdsakey_shake256_set_seed_ha_x4(state, pk_seed, addr, n);
        SHAKE256_SET_TREE_INDEX(state, o, ti);
        for (i = 0; i < 2 * n / 8; i++) {
            state[o + 0] = ((const word64*)(m + 0 * n))[i];
            state[o + 1] = ((const word64*)(m + 2 * n))[i];
            state[o + 2] = ((const word64*)(m + 4 * n))[i];
            state[o + 3] = ((const word64*)(m + 6 * n))[i];
            o += 4;
        }
        SHAKE256_SET_END_X4(state, o);
        ret = SAVE_VECTOR_REGISTERS2();
        if (ret == 0) {
            sha3_blocksx4_avx2(state);
            RESTORE_VECTOR_REGISTERS();
            slhdsakey_shake256_get_hash_x4(state, hash, n);
        }

        WC_FREE_VAR_EX(state, heap, DYNAMIC_TYPE_SLHDSA);
    }

    return ret;
}

/* A ranges from 6-14. */
#if SLHDSA_MAX_A < 9
    /* Maximum node depth that determines the number of nodes stored and
     * hashed in one call. */
    #define SLHDSA_MAX_FORS_NODE_DEPTH      (SLHDSA_MAX_A-1)
#else
    /* Maximum node depth that determines the number of nodes stored and
     * hashed in one call. */
    #define SLHDSA_MAX_FORS_NODE_DEPTH      8
#endif
/* Maximum node depth that determines the number of nodes stored and
 * hashed in one call with an 8 depth tree below. */
#define SLHDSA_MAX_FORS_NODE_TOP_DEPTH  \
    (SLHDSA_MAX_A - SLHDSA_MAX_FORS_NODE_DEPTH)

/* Compute the root of a zero height Merkle subtree of FORS public values.
 *
 * Performs 4 hashes at the same time where possible.
 *
 * FIPS 205. Section 8.2. Algorithm 15.
 * fors_node(SK.seed, i, z, PK.seed, ADRS)
 *   1: if z = 0 then
 *   2:     sk <- fors_skGen(SK.seed, PK.seed, ADRS, i)
 *   3:     ADRS.setTreeHeight(0)
 *   4:     ADRS.setTreeIndex(i)
 *   5:     node <- F(PK.seed, ADRS, sk)
 *   6: else
 *  ...
 *  13: return node
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  i        Node index.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     FORS tree HashAddress.
 * @param [out] node     n-byte root node.
 * @return  0 on success.
 * @return  SHAKE-256 error return code on digest failure.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_fors_node_x4_z0(SlhDsaKey* key, const byte* sk_seed,
    word32 i, const byte* pk_seed, word32* adrs, byte* node)
{
    int ret;
    byte n = key->params->n;

    /* Step 2: Generate private key value for index. */
    ret = slhdsakey_fors_sk_gen(key, sk_seed, pk_seed, adrs, i, node);
    if (ret == 0) {
        /* Step 3: Set tree height to zero. */
        HA_SetTreeHeight(adrs, 0);
        /* Step 4: Set tree index. */
        HA_SetTreeIndex(adrs, i);
        /* Step 5: Compute node from public key seed, address and value. */
        ret = HASH_F(key, pk_seed, adrs, node, n, node);
    }

    return ret;
}

/* Compute the root of a one height Merkle subtree of FORS public values.
 *
 * Performs 4 hashes at the same time where possible.
 *
 * FIPS 205. Section 8.2. Algorithm 15.
 * fors_node(SK.seed, i, z, PK.seed, ADRS)
 *   1: if z = 0 then
 *   2:     sk <- fors_skGen(SK.seed, PK.seed, ADRS, i)
 *   3:     ADRS.setTreeHeight(0)
 *   4:     ADRS.setTreeIndex(i)
 *   5:     node <- F(PK.seed, ADRS, sk)
 *   6: else
 *   7:     lnode <- fors_node(SK.seed, 2i, z - 1, PK.seed, ADRS)
 *   8:     rnode <- fors_node(SK.seed, 2i + 1, z - 1, PK.seed, ADRS)
 *   9:     ADRS.setTreeHeight(z)
 *  10:     ADRS.setTreeIndex(i)
 *  11:     node <- H(PK.seed, ADRS, lnode || rnode)
 *  12: end if
 *  13: return node
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  i        Node index.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     FORS tree HashAddress.
 * @param [out] node     n-byte root node.
 * @return  0 on success.
 * @return  SHAKE-256 error return code on digest failure.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_fors_node_x4_z1(SlhDsaKey* key, const byte* sk_seed,
    word32 i, const byte* pk_seed, word32* adrs, byte* node)
{
    int ret;
    byte n = key->params->n;
    byte nodes[2 * SLHDSA_MAX_N];

    /* Step 7: Compute left node. */
    /* Step 2: Generate private key value for index. */
    ret = slhdsakey_fors_sk_gen(key, sk_seed, pk_seed, adrs, 2 * i + 0, nodes);
    if (ret == 0) {
        /* Step 3: Set tree height to zero. */
        HA_SetTreeHeight(adrs, 0);
        /* Step 4: Set tree index. */
        HA_SetTreeIndex(adrs, 2 * i + 0);
        /* Step 5: Compute node from public key seed, address and value. */
        ret = HASH_F(key, pk_seed, adrs, nodes, n, nodes);
    }
    /* Step 8: Compute right node. */
    if (ret == 0) {
        /* Step 2: Generate private key value for index. */
        ret = slhdsakey_fors_sk_gen(key, sk_seed, pk_seed, adrs, 2 * i + 1,
            nodes + n);
    }
    if (ret == 0) {
        /* Step 4: Set tree index. */
        HA_SetTreeIndex(adrs, 2 * i + 1);
        /* Step 5: Compute node from public key seed, address and value. */
        ret = HASH_F(key, pk_seed, adrs, nodes + n, n, nodes + n);
    }
    if (ret == 0) {
        /* Step 9: Set tree height. */
        HA_SetTreeHeight(adrs, 1);
        /* Step 10: Set tree index. */
        HA_SetTreeIndex(adrs, i);
        /* Step 11: Compute node from public key seed, address and nodes. */
        ret = HASH_H(key, pk_seed, adrs, nodes, n, node);
    }

    return ret;
}

/* Compute the root of a Merkle subtree of FORS public values.
 *
 * Performs 4 hashes at the same time where possible.
 *
 * FIPS 205. Section 8.2. Algorithm 15.
 * fors_node(SK.seed, i, z, PK.seed, ADRS)
 *   1: if z = 0 then
 *   2:     sk <- fors_skGen(SK.seed, PK.seed, ADRS, i)
 *   3:     ADRS.setTreeHeight(0)
 *   4:     ADRS.setTreeIndex(i)
 *   5:     node <- F(PK.seed, ADRS, sk)
 *   6: else
 *   7:     lnode <- fors_node(SK.seed, 2i, z - 1, PK.seed, ADRS)
 *   8:     rnode <- fors_node(SK.seed, 2i + 1, z - 1, PK.seed, ADRS)
 *   9:     ADRS.setTreeHeight(z)
 *  10:     ADRS.setTreeIndex(i)
 *  11:     node <- H(PK.seed, ADRS, lnode || rnode)
 *  12: end if
 *  13: return node
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  i        Node index.
 * @param [in]  z        Node height.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     FORS tree HashAddress.
 * @param [out] node     n-byte root node.
 * @return  0 on success.
 * @return  SHAKE-256 error return code on digest failure.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_fors_node_x4_low(SlhDsaKey* key, const byte* sk_seed,
    word32 i, word32 z, const byte* pk_seed, word32* adrs, byte* node)
{
    int ret = 0;
    byte n = key->params->n;
    HashAddress sk_adrs;
    byte addr[SLHDSA_HA_SZ];
    word32 j;
    word32 m = (word32)1U << z;
    WC_DECLARE_VAR(nodes, byte, (1 << SLHDSA_MAX_FORS_NODE_DEPTH) *
        SLHDSA_MAX_N, key->heap);

    WC_ALLOC_VAR_EX(nodes, byte, (1 << SLHDSA_MAX_FORS_NODE_DEPTH) *
        SLHDSA_MAX_N, key->heap, DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E);
    if (ret == 0) {
        byte sk_addr[SLHDSA_HA_SZ];

        HA_SetTreeHeight(adrs, 0);
        /* Copy address for FORS PRF. */
        HA_Copy(sk_adrs, adrs);
        /* Set type and keep key pair address. */
        HA_SetTypeAndClearNotKPA(sk_adrs, HA_FORS_PRF);
        /* Encode FORS PRF address for hashing. */
        HA_Encode(sk_adrs, sk_addr);
        /* Encode FORS tree address for hashing. */
        HA_Encode(adrs, addr);

        /* Step 2: Generate private key values for leaf indices. */
        for (j = 0; j < m; j += 4) {
            ret = slhdsakey_hash_prf_ti_x4(pk_seed, sk_seed, sk_addr, n,
                m * i + j, nodes + j * n, key->heap);
            if (ret != 0) {
                break;
            }
        }
    }
    if (ret == 0) {
        /* Step 3: Set tree height to zero. */
        HA_SetTreeHeight((word32*)addr, 0);
        /* Step 4-5: Set tree indices and compute leaf node. */
        for (j = 0; j < m; j += 4) {
            ret = slhdsakey_hash_f_ti_x4(pk_seed, addr, nodes + j * n, n,
                m * i + j, key->heap);
            if (ret != 0) {
                break;
            }
        }
    }
    if (ret == 0) {
        word32 k;
        for (k = 1; k < z - 1; k++) {
            m >>= 1;
            /* Step 9: Set tree height. */
            HA_SetTreeHeightBE(addr, k);
            /* Step 10-11: Set tree index and compute nodes. */
            for (j = 0; j < m; j += 4) {
                ret = slhdsakey_hash_h_ti_x4(pk_seed, addr, nodes + 2 * j * n,
                    n, m * i + j, nodes + j * n, key->heap);
                if (ret != 0) {
                    break;
                }
            }
            if (ret != 0) {
                break;
            }
        }
    }
    /* Step 7: Compute left node. */
    if (ret == 0) {
        /* Step 9: Set tree height. */
        HA_SetTreeHeight(adrs, z - 1);
        /* Step 10: Set tree index. */
        HA_SetTreeIndex(adrs, 2 * i + 0);
        /* Step 11: Compute node from public key seed, address and nodes. */
        ret = HASH_H(key, pk_seed, adrs, nodes, n, nodes);
    }
    /* Step 8: Compute right node. */
    if (ret == 0) {
        /* Step 10: Set tree index. */
        HA_SetTreeIndex(adrs, 2 * i + 1);
        /* Step 11: Compute node from public key seed, address and nodes. */
        ret = HASH_H(key, pk_seed, adrs, nodes + 2 * n, n,
            nodes + 1 * n);
    }
    if (ret == 0) {
        /* Step 9: Set tree height. */
        HA_SetTreeHeight(adrs, z);
        /* Step 10: Set tree index. */
        HA_SetTreeIndex(adrs, i);
        /* Step 11: Compute node from public key seed, address and nodes. */
        ret = HASH_H(key, pk_seed, adrs, nodes, n, node);
    }

    WC_FREE_VAR_EX(nodes, key->heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}

#if SLHDSA_MAX_FORS_NODE_DEPTH < SLHDSA_MAX_A-1
/* Compute the root of a Merkle subtree of FORS public values for large heights.
 *
 * Performs 4 hashes at the same time where possible.
 *
 * FIPS 205. Section 8.2. Algorithm 15.
 * fors_node(SK.seed, i, z, PK.seed, ADRS)
 *   1: if z = 0 then
 *   2:     sk <- fors_skGen(SK.seed, PK.seed, ADRS, i)
 *   3:     ADRS.setTreeHeight(0)
 *   4:     ADRS.setTreeIndex(i)
 *   5:     node <- F(PK.seed, ADRS, sk)
 *   6: else
 *   7:     lnode <- fors_node(SK.seed, 2i, z - 1, PK.seed, ADRS)
 *   8:     rnode <- fors_node(SK.seed, 2i + 1, z - 1, PK.seed, ADRS)
 *   9:     ADRS.setTreeHeight(z)
 *  10:     ADRS.setTreeIndex(i)
 *  11:     node <- H(PK.seed, ADRS, lnode || rnode)
 *  12: end if
 *  13: return node
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  i        Node index.
 * @param [in]  z        Node height.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     FORS tree HashAddress.
 * @param [out] node     n-byte root node.
 * @return  0 on success.
 * @return  SHAKE-256 error return code on digest failure.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_fors_node_x4_high(SlhDsaKey* key, const byte* sk_seed,
    word32 i, word32 z, const byte* pk_seed, word32* adrs, byte* node)
{
    int ret = 0;
    byte n = key->params->n;
    word32 j;
    word32 z2 = z % SLHDSA_MAX_FORS_NODE_DEPTH;
    word32 m;
    WC_DECLARE_VAR(nodes, byte, (1 << SLHDSA_MAX_FORS_NODE_TOP_DEPTH) *
        SLHDSA_MAX_N, key->heap);

    WC_ALLOC_VAR_EX(nodes, byte, (1 << SLHDSA_MAX_FORS_NODE_TOP_DEPTH) *
        SLHDSA_MAX_N, key->heap, DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E);
    if (ret == 0) {
        if (z2 == 0) {
            z2 = SLHDSA_MAX_FORS_NODE_DEPTH;
        }
        m = (word32)1U << z2;
        /* Steps 7-8: Compute left and right nodes. */
        for (j = 0; j < m; j++) {
            ret = slhdsakey_fors_node_x4_low(key, sk_seed, m * i + j, z - z2,
                pk_seed, adrs, nodes + j * n);
            if (ret != 0) {
                break;
            }
        }
    }
    if ((ret == 0) && (z2 > 2)) {
        word32 k;
        for (k = z - z2 + 1; k < z - 1; k++) {
            byte addr[SLHDSA_HA_SZ];

            m >>= 1;
            /* Step 9: Set tree height. */
            HA_SetTreeHeight(adrs, k);
            /* Encode FORS tree address for hashing. */
            HA_Encode(adrs, addr);
            /* Step 10-11: Set tree index and compute nodes. */
            for (j = 0; j < m; j += 4) {
                ret = slhdsakey_hash_h_ti_x4(pk_seed, addr, nodes + 2 * j * n,
                    n, m * i + j, nodes + j * n, key->heap);
                if (ret != 0) {
                    break;
                }
            }
            if (ret != 0) {
                break;
            }
        }
    }
    /* Step 7: Compute left node. */
    if ((ret == 0) && (z2 > 1)) {
        /* Step 9: Set tree height. */
        HA_SetTreeHeight(adrs, z - 1);
        /* Step 10: Set tree index. */
        HA_SetTreeIndex(adrs, 2 * i + 0);
        /* Step 11: Compute node from public key seed, address and nodes. */
        ret = HASH_H(key, pk_seed, adrs, nodes, n, nodes);
    }
    /* Step 8: Compute right node. */
    if ((ret == 0) && (z2 > 1)) {
        /* Step 10: Set tree index. */
        HA_SetTreeIndex(adrs, 2 * i + 1);
        /* Step 11: Compute node from public key seed, address and nodes. */
        ret = HASH_H(key, pk_seed, adrs, nodes + 2 * n, n,
            nodes + 1 * n);
    }
    if (ret == 0) {
        /* Step 9: Set tree height. */
        HA_SetTreeHeight(adrs, z);
        /* Step 10: Set tree index. */
        HA_SetTreeIndex(adrs, i);
        /* Step 11: Compute node from public key seed, address and nodes. */
        ret = HASH_H(key, pk_seed, adrs, nodes, n, node);
    }

    WC_FREE_VAR_EX(nodes, key->heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}
#endif

/* Compute the root of a Merkle subtree of FORS public values.
 *
 * Performs 4 hashes at the same time where possible.
 *
 * FIPS 205. Section 8.2. Algorithm 15.
 * fors_node(SK.seed, i, z, PK.seed, ADRS)
 *   1: if z = 0 then
 *   2:     sk <- fors_skGen(SK.seed, PK.seed, ADRS, i)
 *   3:     ADRS.setTreeHeight(0)
 *   4:     ADRS.setTreeIndex(i)
 *   5:     node <- F(PK.seed, ADRS, sk)
 *   6: else
 *   7:     lnode <- fors_node(SK.seed, 2i, z - 1, PK.seed, ADRS)
 *   8:     rnode <- fors_node(SK.seed, 2i + 1, z - 1, PK.seed, ADRS)
 *   9:     ADRS.setTreeHeight(z)
 *  10:     ADRS.setTreeIndex(i)
 *  11:     node <- H(PK.seed, ADRS, lnode || rnode)
 *  12: end if
 *  13: return node
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  i        Node index.
 * @param [in]  z        Node height.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     FORS tree HashAddress.
 * @param [out] node     n-byte root node.
 * @return  0 on success.
 * @return  SHAKE-256 error return code on digest failure.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_fors_node_x4(SlhDsaKey* key, const byte* sk_seed, word32 i,
    word32 z, const byte* pk_seed, word32* adrs, byte* node)
{
    int ret = 0;

    /* Step 1: Check if we are at leaf node. */
    if (z == 0) {
        ret = slhdsakey_fors_node_x4_z0(key, sk_seed, i, pk_seed, adrs, node);
    }
    /* Step 6: 1 level above leaf node. */
    else if (z == 1) {
        ret = slhdsakey_fors_node_x4_z1(key, sk_seed, i, pk_seed, adrs, node);
    }
    /* Step 6: 2-MAX_DEPTH levels above leaf node. */
    else if ((z >= 2) && (z <= SLHDSA_MAX_FORS_NODE_DEPTH)) {
        ret = slhdsakey_fors_node_x4_low(key, sk_seed, i, z, pk_seed, adrs,
            node);
    }
#if SLHDSA_MAX_FORS_NODE_DEPTH < SLHDSA_MAX_A-1
    /* Step 6: More than MAX_DEPTH levels above leaf node. */
    else {
        ret = slhdsakey_fors_node_x4_high(key, sk_seed, i, z, pk_seed, adrs,
            node);
    }
#endif

    return ret;
}
#endif

#if !defined(WOLFSSL_WC_SLHDSA_RECURSIVE)
/* Compute the root of a Merkle subtree of FORS public values.
 *
 * Iterative implementation.
 *
 * FIPS 205. Section 8.2. Algorithm 15.
 * fors_node(SK.seed, i, z, PK.seed, ADRS)
 *   1: if z = 0 then
 *   2:     sk <- fors_skGen(SK.seed, PK.seed, ADRS, i)
 *   3:     ADRS.setTreeHeight(0)
 *   4:     ADRS.setTreeIndex(i)
 *   5:     node <- F(PK.seed, ADRS, sk)
 *   6: else
 *   7:     lnode <- fors_node(SK.seed, 2i, z - 1, PK.seed, ADRS)
 *   8:     rnode <- fors_node(SK.seed, 2i + 1, z - 1, PK.seed, ADRS)
 *   9:     ADRS.setTreeHeight(z)
 *  10:     ADRS.setTreeIndex(i)
 *  11:     node <- H(PK.seed, ADRS, lnode || rnode)
 *  12: end if
 *  13: return node
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  i        Node index.
 * @param [in]  z        Node height.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     FORS tree HashAddress.
 * @param [out] node     n-byte root node.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_fors_node_c(SlhDsaKey* key, const byte* sk_seed, word32 i,
    word32 z, const byte* pk_seed, word32* adrs, byte* node)
{
    int ret = 0;
    byte n = key->params->n;

    /* Step 1: Check if we are at leaf node. */
    if (z == 0) {
        /* Step 2: Generate private key value for index. */
        ret = slhdsakey_fors_sk_gen(key, sk_seed, pk_seed, adrs, i, node);
        if (ret == 0) {
            /* Step 3: Set tree height to zero. */
            HA_SetTreeHeight(adrs, 0);
            /* Step 4: Set tree index. */
            HA_SetTreeIndex(adrs, i);
            /* Step 5: Compute node from public key seed, address and value. */
            ret = HASH_F(key, pk_seed, adrs, node, n, node);
        }
    }
    /* Step 6: Non leaf node. */
    else {
        WC_DECLARE_VAR(nodes, byte, (SLHDSA_MAX_A + 1) * SLHDSA_MAX_N,
            key->heap);
        word32 j;
        word32 k;
        word32 m = (word32)1U << z;

        WC_ALLOC_VAR_EX(nodes, byte, (SLHDSA_MAX_A + 1) * SLHDSA_MAX_N,
            key->heap, DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E);
        if (ret == 0) {
            /* For all leaf nodes. */
            for (j = 0; j < m; j++) {
                word32 o = ((word32)z - 1U + (j & 1U)) * n;
                /* Step 2: Generate private key value for index. */
                ret = slhdsakey_fors_sk_gen(key, sk_seed, pk_seed, adrs,
                    m * (word32)i + j, nodes + o);
                if (ret != 0) {
                    break;
                }
                /* Step 3: Set tree height to zero. */
                HA_SetTreeHeight(adrs, 0);
                /* Step 4: Set tree index. */
                HA_SetTreeIndex(adrs, m * (word32)i + j);
                /* Step 5: Compute node from public key seed, address and value.
                 */
                ret = HASH_F(key, pk_seed, adrs, nodes + o, n,
                    nodes + o);
                if (ret != 0) {
                    break;
                }

                /* For each intermediate node as soon as left and right have
                 * been computed. */
                for (k = (word32)z - 1U; k > 0; k--) {
                    /* Check if this is the right node at a height. */
                    if (((j >> ((word32)z - 1U - k)) & 1U) == 1U) {
                        /* Step 9: Set tree height. */
                        HA_SetTreeHeight(adrs, (word32)z - k);
                        /* Step 10: Set tree index. */
                        HA_SetTreeIndex(adrs,
                                        (m * (word32)i + j) >> ((word32)z - k));
                        /* Step 11: Compute node from public key seed, address
                         * and left and right nodes. */
                        ret = HASH_H(key, pk_seed, adrs, nodes + k * n, n,
                                nodes +
                                  (k - 1U + ((j >> ((word32)z - k)) & 1U)) * n);
                        if (ret != 0) {
                            break;
                        }
                    }
                    /* Left node - can go no higher. */
                    else {
                        break;
                    }
                }
            }
            if (ret == 0) {
                /* Step 9: Set tree height. */
                HA_SetTreeHeight(adrs, z);
                /* Step 10: Set tree index. */
                HA_SetTreeIndex(adrs, i);
                /* Step 11: Compute node from public key seed, address
                 * and nodes. */
                ret = HASH_H(key, pk_seed, adrs, nodes, n, node);
            }
        }

        WC_FREE_VAR_EX(nodes, key->heap, DYNAMIC_TYPE_SLHDSA);
    }

    return ret;
}
#else
/* Compute the root of a Merkle subtree of FORS public values.
 *
 * Recursive implementation.
 *
 * FIPS 205. Section 8.2. Algorithm 15.
 * fors_node(SK.seed, i, z, PK.seed, ADRS)
 *   1: if z = 0 then
 *   2:     sk <- fors_skGen(SK.seed, PK.seed, ADRS, i)
 *   3:     ADRS.setTreeHeight(0)
 *   4:     ADRS.setTreeIndex(i)
 *   5:     node <- F(PK.seed, ADRS, sk)
 *   6: else
 *   7:     lnode <- fors_node(SK.seed, 2i, z - 1, PK.seed, ADRS)
 *   8:     rnode <- fors_node(SK.seed, 2i + 1, z - 1, PK.seed, ADRS)
 *   9:     ADRS.setTreeHeight(z)
 *  10:     ADRS.setTreeIndex(i)
 *  11:     node <- H(PK.seed, ADRS, lnode || rnode)
 *  12: end if
 *  13: return node
 *
 * @param [in]  key      SLH-DSA key.
 * @param [in]  sk_seed  Private key seed.
 * @param [in]  i        Node index.
 * @param [in]  z        Node height.
 * @param [in]  pk_seed  Public key seed.
 * @param [in]  adrs     FORS tree HashAddress.
 * @param [out] node     n-byte root node.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_fors_node_c(SlhDsaKey* key, const byte* sk_seed, word32 i,
    word32 z, const byte* pk_seed, word32* adrs, byte* node)
{
    int ret;
    byte n = key->params->n;

    /* Step 1: Check if we are at leaf node. */
    if (z == 0) {
        /* Step 2: Generate private key value for index. */
        ret = slhdsakey_fors_sk_gen(key, sk_seed, pk_seed, adrs, i, node);
        if (ret == 0) {
            /* Step 3: Set tree height to zero. */
            HA_SetTreeHeight(adrs, 0);
            /* Step 4: Set tree index. */
            HA_SetTreeIndex(adrs, i);
            /* Step 5: Compute node from public key seed, address and value. */
            ret = HASH_F(key, pk_seed, adrs, node, n, node);
        }
    }
    else {
        byte nodes[2 * SLHDSA_MAX_N];

        /* Step 7: Compute left node. */
        ret = slhdsakey_fors_node_c(key, sk_seed, 2 * i + 0, z - 1, pk_seed,
            adrs, nodes);
        if (ret == 0) {
            /* Step 8: Compute right node. */
            ret = slhdsakey_fors_node_c(key, sk_seed, 2 * i + 1, z - 1, pk_seed,
                adrs, nodes + n);
        }
        if (ret == 0) {
            /* Step 9: Set tree height. */
            HA_SetTreeHeight(adrs, z);
            /* Step 10: Set tree index. */
            HA_SetTreeIndex(adrs, i);
            /* Step 11: Compute node from public key seed, address and nodes. */
            ret = HASH_H(key, pk_seed, adrs, nodes, n, node);
        }
    }

    return ret;
}
#endif

/* Generate FORS signature.
 *
 * FIPS 205. Section 8.3. Algorithm 16.
 * fors_sign(md SK.seed, PK.seed, ADRS)
 *   1: SIGFORS = NULL         > initialize SIGFORS as a zero-length byte string
 *   2: indices <- base_2b(md, a, k)
 *   3: for i from 0 to k - 1 do                    > compute signature elements
 *   4:     SIGFORS <- SIGFORS ||
 *                     fors_skGen(SK.seed, PK.seed, ADRS, i . 2^a + indices)
 *   5:     for j from 0 to a - 1 do                         > compute auth path
 *   6:         s <- lower(indices[i]/2^j) XOR 1
 *   7:         AUTH[j] <- fors_node(SK.seed, i . 2^(a-j) + s, j, PK.seed, ADRS)
 *   8:     end for
 *   9:     SIGFORS <- SIGFORS || AUTH
 *  10: end for
 *  11: return SIGFORS
 *
 * @param [in]       key       SLH-DSA key.
 * @param [in]       md        Message digest.
 * @param [in]       sk_seed   Private key seed.
 * @param [in]       pk_seed   Public key seed.
 * @param [inm out]  adrs      FORS tree HashAddress.
 * @param [out]      sig_fors  FORS signature.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_fors_sign(SlhDsaKey* key, const byte* md,
    const byte* sk_seed, const byte* pk_seed, word32* adrs, byte* sig_fors)
{
    int ret = WC_NO_ERR_TRACE(BAD_FUNC_ARG);
    word16 indices[SLHDSA_MAX_INDICES_SZ];
    int i;
    int j;
    byte n = key->params->n;
    byte a = key->params->a;
    byte k = key->params->k;

    /* Step 2: Convert message digest to base 2^a. */
    slhdsakey_base_2b(md, a, k, indices);

    /* Step 3: For each index: */
    for (i = 0; i < k; i++) {
        /* Step 4: Generate FORS private key value into signature. */
        ret = slhdsakey_fors_sk_gen(key, sk_seed, pk_seed, adrs,
            ((word32)i << a) + indices[i], sig_fors);
        if (ret != 0) {
            break;
        }
        /* Step 4: Move over private key value. */
        sig_fors += n;

    #if defined(USE_INTEL_SPEEDUP) && !defined(WOLFSSL_WC_SLHDSA_SMALL)
        if (!SLHDSA_IS_SHA2(key->params->param) &&
                IS_INTEL_AVX2(cpuid_flags) &&
                CAN_SAVE_VECTOR_REGISTERS()) {
            word16 idx = indices[i];
            /* Step 5: For each bit: */
            for (j = 0; j < a; j++) {
                /* Calculate side. */
                word32 s = idx ^ 1;
                /* Step 7: Compute authentication node into signature. */
                ret = slhdsakey_fors_node_x4(key, sk_seed,
                    ((word32)i << (a - j)) + s, (word32)j, pk_seed, adrs,
                    sig_fors);
                if (ret != 0) {
                    break;
                }
                /* Step 9: Move signature to after authentication node. */
                sig_fors += n;
                /* Update tree index. */
                idx >>= 1;
            }
        }
        else
    #endif
        {
            word16 idx = indices[i];
            /* Step 5: For each bit: */
            for (j = 0; j < a; j++) {
                /* Calculate side. */
                word32 s = idx ^ 1;
                /* Step 7: Compute authentication node into signature. */
                ret = slhdsakey_fors_node_c(key, sk_seed,
                    ((word32)i << (a - j)) + s, (word32)j, pk_seed, adrs,
                    sig_fors);
                if (ret != 0) {
                    break;
                }
                /* Step 9: Move signature to after authentication node. */
                sig_fors += n;
                /* Update tree index. */
                idx >>= 1;
            }
        }
        if (ret != 0) {
            break;
        }
    }

    return ret;
}
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

#if defined(USE_INTEL_SPEEDUP) && !defined(WOLFSSL_WC_SLHDSA_SMALL)
/* F hash 4 simultaneously.
 *
 * Each hash varies by the tree index with the values passed in.
 * Each n-byte message in sig_fors is offset by so x n bytes.
 *
 * FIPS 205. Section 4.1.
 *   F(PK.seed, ADRS, M1) (Bn x B32 x Bn -> Bn) is a hash function that takes an
 *   n-byte message as input and produces an n-byte output.
 * FIPS 205. Section 11.1.
 *   F(PK.seed, ADRS, M1) = SHAKE256(PK.seed || ADRS || M1 , 8n)
 *
 * @param [in]  pk_seed   Public key seed.
 * @param [in]  addr      Encoded HashAddress.
 * @param [in]  sig_fors  n-byte messages.
 * @param [in]  so        Tree index start value.
 * @param [in]  n         Number of bytes in hash output.
 * @param [in]  ti        Tree index start value.
 * @param [out] node      n-byte hash outputs.
 * @param [in]  heap      Dynamic memory allocation hint.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_hash_f_ti4_x4(const byte* pk_seed, byte* addr,
    const byte* sig_fors, int so, byte n, word32* ti, byte* node, void* heap)
{
    int ret = 0;
    int i;
    word32 o = 0;
    WC_DECLARE_VAR(state, word64, 25 * 4, heap);

    (void)heap;

    WC_ALLOC_VAR_EX(state, word64, 25 * 4, heap, DYNAMIC_TYPE_SLHDSA,
        ret = MEMORY_E);
    if (ret == 0) {
        o = slhdsakey_shake256_set_seed_ha_x4(state, pk_seed, addr, n);
        SHAKE256_SET_TREE_INDEX_IDX(state, o, ti);
        for (i = 0; i < n / 8; i++) {
            state[o + 0] = ((const word64*)(sig_fors + 0 * so * n))[i];
            state[o + 1] = ((const word64*)(sig_fors + 1 * so * n))[i];
            state[o + 2] = ((const word64*)(sig_fors + 2 * so * n))[i];
            state[o + 3] = ((const word64*)(sig_fors + 3 * so * n))[i];
            o += 4;
        }
        SHAKE256_SET_END_X4(state, o);
        ret = SAVE_VECTOR_REGISTERS2();
        if (ret == 0) {
            sha3_blocksx4_avx2(state);
            RESTORE_VECTOR_REGISTERS();
            slhdsakey_shake256_get_hash_x4(state, node, n);
        }

        WC_FREE_VAR_EX(state, heap, DYNAMIC_TYPE_SLHDSA);
    }

    return ret;
}

/* H hash 4 simultaneously with two buffers holding two halves of messages.
 *
 * Each hash varies by the tree index with the first value in sequence passed
 * in.
 * Each n-byte message in sig_fors is offset by so x n bytes.
 *
 * FIPS 205. Section 4.1.
 *   H(PK.seed, ADRS, M2) (Bn x B32 x B2n -> Bn) is a special case of Tl that
 *   takes a 2n-byte message as input.
 * FIPS 205. Section 11.1.
 *   H(PK.seed, ADRS, M2) = SHAKE256(PK.seed || ADRS || M2, 8n)
 *
 * @param [in]      pk_seed   Public key seed.
 * @param [in]      addr      Encoded HashAddress.
 * @param [in, out] node      On in, n-byte messages. On out, hash output.
 * @param [in]      sig_fors  n-byte messages.
 * @param [in]      so        Tree index start value.
 * @param [in]      bit       Bits to indicate which order of node/sig_fors.
 * @param [in]      n         Number of bytes in hash output.
 * @param [in]      ti        Tree index start value.
 * @param [in]      heap      Dynamic memory allocation hint.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_hash_h_2_x4(const byte* pk_seed, byte* addr, byte* node,
    const byte* sig_fors, int so, word32* bit, byte n, word32 th, word32* ti,
    void* heap)
{
    int ret = 0;
    int i;
    word32 j;
    word32 o = 0;
    WC_DECLARE_VAR(state, word64, 25 * 4, heap);

    (void)heap;

    WC_ALLOC_VAR_EX(state, word64, 25 * 4, heap, DYNAMIC_TYPE_SLHDSA,
        ret = MEMORY_E);
    if (ret == 0) {
        o = slhdsakey_shake256_set_seed_ha_x4(state, pk_seed, addr, n);
        SHAKE256_SET_TREE_HEIGHT(state, o, th);
        SHAKE256_SET_TREE_INDEX_IDX(state, o, ti);
        for (i = 0; i < n / 8; i++) {
            for (j = 0; j < 4; j++) {
                if (bit[j] == 0) {
                    state[o + j] = ((const word64*)(node + j * n))[i];
                }
                else {
                    state[o + j] =
                        ((const word64*)(sig_fors + j * (word32)so * n))[i];
                }
            }
            o += 4;
        }
        for (i = 0; i < n / 8; i++) {
            for (j = 0; j < 4; j++) {
                if (bit[j] == 0) {
                    state[o + j] =
                        ((const word64*)(sig_fors + j * (word32)so * n))[i];
                }
                else {
                    state[o + j] = ((const word64*)(node + j * n))[i];
                }
            }
            o += 4;
        }
        SHAKE256_SET_END_X4(state, o);
        ret = SAVE_VECTOR_REGISTERS2();
        if (ret == 0) {
            sha3_blocksx4_avx2(state);
            RESTORE_VECTOR_REGISTERS();
            slhdsakey_shake256_get_hash_x4(state, node, n);
        }

        WC_FREE_VAR_EX(state, heap, DYNAMIC_TYPE_SLHDSA);
    }

    return ret;
}

/* Compute ith FORS public key from ith FORS signature.
 *
 * 4 hashes computed simultaneously.
 *
 * FIPS 205. Section 8.4 Algorithm 17.
 * fors_pkFromSig(SIGFORS, md, PK.seed, ADRS)
 *  ...
 *   4:     ADRS.setTreeHeight(0)                                 > compute leaf
 *   5:     ADRS.setTreeIndex(i . 2^a + indices[i])
 *   6:     node[0] <- F(PK.seed, ADRS, sk)
 *   7:     auth <- SIGFORS.getAUTH(i)
 *                     > SIGFORS [(i . (a + 1) + 1) . n : (i + 1) . (a + 1) . n]
 *   8:     for j from 0 to a - 1 do           > compute root from leaf and AUTH
 *   9:         ADRS.setTreeHeight(j + 1)
 *  10:         if lower(indices[i]/(2^j)) is even then
 *  11:             ADRS.setTreeIndex(ADRS.getTreeIndex()/2)
 *  12:             node[1] <- H(PK.seed, ADRS, node[0] || auth[i])
 *  13:         else
 *  14:             ADRS.setTreeIndex((ADRS.getTreeIndex() - 1)/2)
 *  15:             node[1] <- H(PK.seed, ADRS, auth[j] || node[0])
 *  16:         end if
 *  17:         node[0] <- node[1]
 *  18:     end for
 *  19:     root[i] <- node[0]
 *  ...
 *
 * @param [in]  key       SLH-DSA key.
 * @param [in]  sig_fors  FORS signature.
 * @param [in]  pk_seed   Public key seed.
 * @param [in]  addr      Encoded HashAddress.
 * @param [in]  indices   Base 2^a values from message digest.
 * @param [in]  i         Index.
 * @param [out] node      Root node of ith tree.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 */
static int slhdsakey_fors_pk_from_sig_i_x4(SlhDsaKey* key, const byte* sig_fors,
    const byte* pk_seed, byte* addr, const word16* indices, int i, byte* node)
{
    int ret;
    int j;
    int k;
    byte n = key->params->n;
    byte a = key->params->a;
    word32 ti[4];
    word32 bit[4];

    /* Step 5: Calculate the index of each hash ... */
    ti[0] = ((word32)(i + 0) << a) + indices[i + 0];
    ti[1] = ((word32)(i + 1) << a) + indices[i + 1];
    ti[2] = ((word32)(i + 2) << a) + indices[i + 2];
    ti[3] = ((word32)(i + 3) << a) + indices[i + 3];
    /* Steps 4-6: Compute nodes.  */
    ret = slhdsakey_hash_f_ti4_x4(pk_seed, addr, sig_fors, 1 + a, n, ti, node,
        key->heap);
    if (ret == 0) {
        /* Step 7: Move on to authentication nodes. */
        sig_fors += n;
        /* Step 8: For each level: */
        for (j = 0; j < a; j++) {
            /* Calculate which order of node and sig_fors for each hash. */
            for (k = 0; k < 4; k++) {
                bit[k] = ti[k] & 1;
                ti[k] /= 2;
            }
            /* Steps 9-17: 4 hash with tree indices. */
            ret = slhdsakey_hash_h_2_x4(pk_seed, addr, node, sig_fors, 1 + a,
                bit, n, (word32)(j + 1), ti, key->heap);
            if (ret != 0) {
                break;
            }
            /* Move on to next authentication node. */
            sig_fors += n;
        }
    }

    return ret;
}

/* Compute ith FORS public key from ith FORS signature.
 *
 * 4 hashes computed simultaneously.
 *
 * FIPS 205. Section 8.4 Algorithm 17.
 * fors_pkFromSig(SIGFORS, md, PK.seed, ADRS)
 *  ...
 *   2: for i from 0 to k - 1 do
 *   3:     sk <- SIGFORS.getSK(i)
 *                           > SIGFORS [i . (a + 1) . n : (i . (a + 1) + 1) . n]
 *   4:     ADRS.setTreeHeight(0)                                 > compute leaf
 *   5:     ADRS.setTreeIndex(i . 2^a + indices[i])
 *   6:     node[0] <- F(PK.seed, ADRS, sk)
 *   7:     auth <- SIGFORS.getAUTH(i)
 *                     > SIGFORS [(i . (a + 1) + 1) . n : (i + 1) . (a + 1) . n]
 *   8:     for j from 0 to a - 1 do           > compute root from leaf and AUTH
 *   9:         ADRS.setTreeHeight(j + 1)
 *  10:         if lower(indices[i]/(2^j)) is even then
 *  11:             ADRS.setTreeIndex(ADRS.getTreeIndex()/2)
 *  12:             node[1] <- H(PK.seed, ADRS, node[0] || auth[i])
 *  13:         else
 *  14:             ADRS.setTreeIndex((ADRS.getTreeIndex() - 1)/2)
 *  15:             node[1] <- H(PK.seed, ADRS, auth[j] || node[0])
 *  16:         end if
 *  17:         node[0] <- node[1]
 *  18:     end for
 *  19:     root[i] <- node[0]
 *  20: end for
 * ...
 *  24: pk <- Tk(PK.seed, forspkADRS, root)        > compute the FORS public key
 * ...
 *
 * @param [in]  key       SLH-DSA key.
 * @param [in]  sig_fors  FORS signature.
 * @param [in]  indices   Base 2^a values from message digest.
 * @param [in]  pk_seed   Public key seed.
 * @param [in]  adrs      Encoded HashAddress.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_fors_pk_from_sig_x4(SlhDsaKey* key, const byte* sig_fors,
    const word16* indices, const byte* pk_seed, word32* adrs)
{
    int ret = 0;
    int i;
    int j;
    byte n = key->params->n;
    byte a = key->params->a;
    byte k = key->params->k;
    byte addr[SLHDSA_HA_SZ];
    WC_DECLARE_VAR(node, byte, SLHDSA_MAX_INDICES_SZ * SLHDSA_MAX_N, key->heap);

    WC_ALLOC_VAR_EX(node, byte, SLHDSA_MAX_INDICES_SZ * SLHDSA_MAX_N, key->heap,
        DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E);
    if (ret == 0) {
        /* Step 4: Set tree height for address.  */
        HA_SetTreeHeight(adrs, 0);
        /* Encode address for multiple hashing. */
        HA_Encode(adrs, addr);

        /* Step 2: Do multiple of 4 iterations. */
        for (i = 0; i < k-3; i += 4) {
            /* Steps 4-19: Compute public key root for signature at index. */
            ret = slhdsakey_fors_pk_from_sig_i_x4(key, sig_fors, pk_seed, addr,
                indices, i, node + i * n);
            if (ret != 0) {
                break;
            }
            /* Move on to next signatures. */
            sig_fors += 4 * (1 + a) * n;
        }
    }
    if (ret == 0) {
        /* Step 2: Do remaining iterations. */
        for (; i < k; i++) {
            /* Step 5: Calculate index ...  */
            word32 idx = ((word32)i << a) + indices[i];

            /* Step 4: Set tree height for address.  */
            HA_SetTreeHeight(adrs, 0);
            /* Step 5: Set tree index for address.  */
            HA_SetTreeIndex(adrs, idx);
            /* Step 6: Compute node from public key seed, address and value. */
            ret = HASH_F(key, pk_seed, adrs, sig_fors, n, node + i * n);
            if (ret != 0) {
                break;
            }
            /* Step 7: Move to authentication nodes. */
            sig_fors += n;

            /* Step 8: For all heights: */
            for (j = 0; j < a; j++) {
                /* Step 10: Calculate side ... */
                word32 side = idx & 1;

                /* Step 11/14: Update tree index value ... */
                idx >>= 1;
                /* Step 9: Set tree height. */
                HA_SetTreeHeight(adrs, j + 1);
                /* Step 11/14: Set tree index. */
                HA_SetTreeIndex(adrs, idx);
                /* Step 10: Check which side node is on. */
                if (side == 0) {
                    /* Step 12: Hash node || auth node. */
                    ret = HASH_H_2(key, pk_seed, adrs, node + i * n,
                        sig_fors, n, node + i * n);
                }
                else {
                    /* Step 15: Hash auth node || node. */
                    ret = HASH_H_2(key, pk_seed, adrs, sig_fors,
                        node + i * n, n, node + i * n);
                }
                if (ret != 0) {
                    break;
                }
                /* Move on to next authentication node. */
                sig_fors += n;
            }
            if (ret != 0) {
                break;
            }
        }
    }
    if (ret == 0) {
        /* Step 24: Add more root nodes to hash ... */
        ret = HASH_T_UPDATE(key, node, (word32)i * n);
    }

    WC_FREE_VAR_EX(node, key->heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}
#endif

#if !defined(WOLFSSL_WC_SLHDSA_SMALL_MEM)
/* Compute FORS public key from FORS signature.
 *
 * 4 hashes computed simultaneously.
 *
 * FIPS 205. Section 8.4 Algorithm 17.
 * fors_pkFromSig(SIGFORS, md, PK.seed, ADRS)
 *  ...
 *   2: for i from 0 to k - 1 do
 *   3:     sk <- SIGFORS.getSK(i)
 *                           > SIGFORS [i . (a + 1) . n : (i . (a + 1) + 1) . n]
 *   4:     ADRS.setTreeHeight(0)                                 > compute leaf
 *   5:     ADRS.setTreeIndex(i . 2^a + indices[i])
 *   6:     node[0] <- F(PK.seed, ADRS, sk)
 *   7:     auth <- SIGFORS.getAUTH(i)
 *                     > SIGFORS [(i . (a + 1) + 1) . n : (i + 1) . (a + 1) . n]
 *   8:     for j from 0 to a - 1 do           > compute root from leaf and AUTH
 *   9:         ADRS.setTreeHeight(j + 1)
 *  10:         if lower(indices[i]/(2^j)) is even then
 *  11:             ADRS.setTreeIndex(ADRS.getTreeIndex()/2)
 *  12:             node[1] <- H(PK.seed, ADRS, node[0] || auth[i])
 *  13:         else
 *  14:             ADRS.setTreeIndex((ADRS.getTreeIndex() - 1)/2)
 *  15:             node[1] <- H(PK.seed, ADRS, auth[j] || node[0])
 *  16:         end if
 *  17:         node[0] <- node[1]
 *  18:     end for
 *  19:     root[i] <- node[0]
 *  20: end for
 * ...
 *  24: pk <- Tk(PK.seed, forspkADRS, root)        > compute the FORS public key
 * ...
 *
 * @param [in]  key       SLH-DSA key.
 * @param [in]  sig_fors  FORS signature.
 * @param [in]  indices   Base 2^a values from message digest.
 * @param [in]  pk_seed   Public key seed.
 * @param [in]  adrs      HashAddress.
 * @param [out] pk_fors   FORS public key from signature.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_fors_pk_from_sig_c(SlhDsaKey* key, const byte* sig_fors,
    const word16* indices, const byte* pk_seed, word32* adrs, byte* pk_fors)
{
    int ret = 0;
    int i = 0;
    int j;
    byte n = key->params->n;
    byte a = key->params->a;
    byte k = key->params->k;
    WC_DECLARE_VAR(node, byte, SLHDSA_MAX_INDICES_SZ * SLHDSA_MAX_N, key->heap);

    (void)pk_fors;

    WC_ALLOC_VAR_EX(node, byte, SLHDSA_MAX_INDICES_SZ * SLHDSA_MAX_N, key->heap,
        DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E);
    if (ret == 0) {
        /* Step 2: For all indices: */
        for (i = 0; i < k; i++) {
            /* Step 5: Calculate index ...  */
            word32 idx = ((word32)i << a) + indices[i];

            /* Step 4: Set tree height for address.  */
            HA_SetTreeHeight(adrs, 0);
            /* Step 5: Set tree index for address.  */
            HA_SetTreeIndex(adrs, idx);
            /* Step 6: Compute node from public key seed, address and value. */
            ret = HASH_F(key, pk_seed, adrs, sig_fors, n, node + i * n);
            if (ret != 0) {
                break;
            }
            /* Step 7: Move to authentication nodes. */
            sig_fors += n;

            /* Step 8: For all heights: */
            for (j = 0; j < a; j++) {
                /* Step 10: Calculate side ... */
                word32 bit = idx & 1;

                /* Step 11/14: Update tree index value ... */
                idx >>= 1;
                /* Step 9: Set tree height. */
                HA_SetTreeHeight(adrs, j + 1);
                /* Step 11/14: Set tree index. */
                HA_SetTreeIndex(adrs, idx);
                /* Step 10: Check which side node is on. */
                if (bit == 0) {
                    /* Step 12: Hash node || auth node. */
                    ret = HASH_H_2(key, pk_seed, adrs, node + i * n,
                        sig_fors, n, node + i * n);
                }
                else {
                    /* Step 15: Hash auth node || node. */
                    ret = HASH_H_2(key, pk_seed, adrs, sig_fors,
                        node + i * n, n, node + i * n);
                }
                if (ret != 0) {
                    break;
                }
                /* Move on to next authentication node. */
                sig_fors += n;
            }
            if (ret != 0) {
                break;
            }
        }
    }
    if (ret == 0) {
        /* Step 24: Add more root nodes to hash ... */
        ret = HASH_T_UPDATE(key, node, (word32)i * n);
    }

    WC_FREE_VAR_EX(node, key->heap, DYNAMIC_TYPE_SLHDSA);
    return ret;
}
#else
/* Compute FORS public key from FORS signature.
 *
 * Update hash one node at a time to save stack.
 *
 * FIPS 205. Section 8.4 Algorithm 17.
 * fors_pkFromSig(SIGFORS, md, PK.seed, ADRS)
 *  ...
 *   2: for i from 0 to k - 1 do
 *   3:     sk <- SIGFORS.getSK(i)
 *                           > SIGFORS [i . (a + 1) . n : (i . (a + 1) + 1) . n]
 *   4:     ADRS.setTreeHeight(0)                                 > compute leaf
 *   5:     ADRS.setTreeIndex(i . 2^a + indices[i])
 *   6:     node[0] <- F(PK.seed, ADRS, sk)
 *   7:     auth <- SIGFORS.getAUTH(i)
 *                     > SIGFORS [(i . (a + 1) + 1) . n : (i + 1) . (a + 1) . n]
 *   8:     for j from 0 to a - 1 do           > compute root from leaf and AUTH
 *   9:         ADRS.setTreeHeight(j + 1)
 *  10:         if lower(indices[i]/(2^j)) is even then
 *  11:             ADRS.setTreeIndex(ADRS.getTreeIndex()/2)
 *  12:             node[1] <- H(PK.seed, ADRS, node[0] || auth[i])
 *  13:         else
 *  14:             ADRS.setTreeIndex((ADRS.getTreeIndex() - 1)/2)
 *  15:             node[1] <- H(PK.seed, ADRS, auth[j] || node[0])
 *  16:         end if
 *  17:         node[0] <- node[1]
 *  18:     end for
 *  19:     root[i] <- node[0]
 *  20: end for
 * ...
 *  24: pk <- Tk(PK.seed, forspkADRS, root)        > compute the FORS public key
 * ...
 *
 * @param [in]  key       SLH-DSA key.
 * @param [in]  sig_fors  FORS signature.
 * @param [in]  indices   Base 2^a values from message digest.
 * @param [in]  pk_seed   Public key seed.
 * @param [in]  adrs      HashAddress.
 * @param [out] node      Root node of ith tree.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_fors_pk_from_sig_c(SlhDsaKey* key, const byte* sig_fors,
    const word16* indices, const byte* pk_seed, word32* adrs, byte* node)
{
    int ret;
    int i;
    int j;
    byte n = key->params->n;
    byte a = key->params->a;
    byte k = key->params->k;

    /* Step 2: For all indices: */
    for (i = 0; i < k; i++) {
        /* Step 5: Calculate index ...  */
        word32 idx = ((word32)i << a) + indices[i];

        /* Step 4: Set tree height for address.  */
        HA_SetTreeHeight(adrs, 0);
        /* Step 5: Set tree index for address.  */
        HA_SetTreeIndex(adrs, idx);
        /* Step 6: Compute node from public key seed, address and value. */
        ret = HASH_F(key, pk_seed, adrs, sig_fors, n, node);
        if (ret != 0) {
            break;
        }
        /* Step 7: Move to authentication nodes. */
        sig_fors += n;

        /* Step 8: For all heights: */
        for (j = 0; j < a; j++) {
            /* Step 10: Calculate side ... */
            word32 bit = idx & 1;

            /* Step 11/14: Update tree index value ... */
            idx >>= 1;
            /* Step 9: Set tree height. */
            HA_SetTreeHeight(adrs, j + 1);
            /* Step 11/14: Set tree index. */
            HA_SetTreeIndex(adrs, idx);
            /* Step 10: Check which side node is on. */
            if (bit == 0) {
                /* Step 12: Hash node || auth node. */
                ret = HASH_H_2(key, pk_seed, adrs, node, sig_fors, n,
                    node);
            }
            else {
                /* Step 15: Hash auth node || node. */
                ret = HASH_H_2(key, pk_seed, adrs, sig_fors, node, n,
                    node);
            }
            if (ret != 0) {
                break;
            }
            /* Move on to next authentication node. */
            sig_fors += n;
        }
        if (ret == 0) {
            /* Step 24: Add root node to hash ... */
            ret = HASH_T_UPDATE(key, node, n);
        }
        if (ret != 0) {
            break;
        }
    }

    return ret;
}
#endif

/* Compute FORS public key from FORS signature.
 *
 * 4 hashes computed simultaneously.
 *
 * FIPS 205. Section 8.4 Algorithm 17.
 * fors_pkFromSig(SIGFORS, md, PK.seed, ADRS)
 *   1: indices <- base_2b(md, a, k)
 *  ...
 *  21: forspkADRS <- ADRS    > copy address to create a FORS public-key address
 *  22: forspkADRS.setTypeAndClear(FORS_ROOTS)
 *  23: forspkADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
 *  24: pk <- Tk(PK.seed, forspkADRS, root)        > compute the FORS public key
 *  25: return pk
 *
 * @param [in]  key       SLH-DSA key.
 * @param [in]  sig_fors  FORS signature.
 * @param [in]  md        Message digest.
 * @param [in]  pk_seed   Public key seed.
 * @param [in]  addr      Encoded HashAddress.
 * @param [out] pk_fors   FORS public key form signature.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_fors_pk_from_sig(SlhDsaKey* key, const byte* sig_fors,
    const byte* md, const byte* pk_seed, word32* adrs, byte* pk_fors)
{
    int ret;
    word16 indices[SLHDSA_MAX_INDICES_SZ];
    HashAddress forspk_adrs;
    byte n = key->params->n;
    byte a = key->params->a;
    byte k = key->params->k;
    int hash_t_started = 0;

    /* Step 1: Get indices from byte array. */
    slhdsakey_base_2b(md, a, k, indices);

    /* Step 21: Create address to FORS roots */
    HA_Copy(forspk_adrs, adrs);
    /* Steps 22-23: Set type and clear all but key pair address. */
    HA_SetTypeAndClearNotKPA(forspk_adrs, HA_FORS_ROOTS);
    /* Step 24: Add public key seed and FORS roots address to hash ... */
    ret = HASH_T_START_ADDR(key, pk_seed, forspk_adrs, n);

    if (ret == 0) {
        hash_t_started = 1;
    }

    /* Steps 2-20: Compute roots and add to hash. */
#if defined(USE_INTEL_SPEEDUP) && !defined(WOLFSSL_WC_SLHDSA_SMALL)
    if ((ret == 0) && !SLHDSA_IS_SHA2(key->params->param) &&
            IS_INTEL_AVX2(cpuid_flags) &&
            (SAVE_VECTOR_REGISTERS2() == 0)) {
        ret = slhdsakey_fors_pk_from_sig_x4(key, sig_fors, indices, pk_seed,
            adrs);
        RESTORE_VECTOR_REGISTERS();
    }
    else
#endif
    if (ret == 0) {
        ret = slhdsakey_fors_pk_from_sig_c(key, sig_fors, indices, pk_seed,
            adrs, pk_fors);
    }

    if (ret == 0) {
        /* Step 24. Compute FORS public key. */
        ret = HASH_T_FINAL(key, pk_fors, n);
    }

    if (hash_t_started) {
        HASH_T_FREE(key);
    }

    return ret;
}

/******************************************************************************
 * SLH-DSA API
 ******************************************************************************/

/* Initialize an SLH-DSA key.
 *
 * @param [in] key    SLH-DSA key.
 * @param [in] param  SLH-DSA parameter set to use.
 * @param [in] heap   Dynamic memory allocation hint.
 * @param [in] devId  Device Id.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key is NULL.
 * @return  NOT_COMPILED_IN when parameter set not compiled in.
 * @return  SHAKE-256 error return code on digest initialization failure.
 */
int wc_SlhDsaKey_Init(SlhDsaKey* key, enum SlhDsaParam param, void* heap,
    int devId)
{
    int ret = 0;
    int idx = -1;

    /* Validate parameters. */
    if (key == NULL) {
        ret = BAD_FUNC_ARG;
    }
    if (ret == 0) {
        int i;

        /* Find parameters in available parameter list. */
        for (i = 0; i < SLHDSA_PARAM_LEN; i++) {
            if (param == SlhDsaParams[i].param) {
                idx = i;
                break;
            }
        }
        if (idx == -1) {
            /* Parameter set not compiled in.  */
            ret = NOT_COMPILED_IN;
        }
    }
    if (ret == 0) {
        /* Zeroize key. */
        XMEMSET(key, 0, sizeof(SlhDsaKey));

        /* Set the parameters into key early so SLHDSA_IS_SHA2 works. */
        key->params = &SlhDsaParams[idx];
        /* Set heap hint to use with all allocations. */
        key->heap = heap;
    #ifdef WOLF_CRYPTO_CB
        /* Set device context and id. */
        key->devCtx = NULL;
        key->devId = devId;
    #endif
    #ifdef WOLF_PRIVATE_KEY_ID
        key->idLen = 0;
        key->labelLen = 0;
    #endif

#ifdef WOLFSSL_SLHDSA_SHA2
        if (SLHDSA_IS_SHA2(param)) {
            /* Initialize SHA2 hash objects. */
            ret = wc_InitSha256(&key->hash.sha2.sha256);
            if (ret == 0)
                key->hash.sha2.sha256_inited = 1;
            if ((ret == 0) && (key->params->n > 16)) {
                ret = wc_InitSha512(&key->hash.sha2.sha512);
                if (ret == 0)
                    key->hash.sha2.sha512_inited = 1;
            }
        }
        else
#endif
        {
            /* Initialize SHAKE-256 objects. */
            ret = wc_InitShake256(&key->hash.shk.shake, key->heap,
                INVALID_DEVID);
            if (ret == 0) {
                ret = wc_InitShake256(&key->hash.shk.shake2, key->heap,
                    INVALID_DEVID);
            }
        }
    }
    (void)devId;

#if defined(USE_INTEL_SPEEDUP)
    /* Ensure the CPU features are known. */
    cpuid_get_flags_ex(&cpuid_flags);
#endif

    return ret;
}

#ifdef WOLF_PRIVATE_KEY_ID
/* Initialize an SLH-DSA key with a device key id.
 *
 * @param [in] key    SLH-DSA key.
 * @param [in] param  SLH-DSA parameter set to use.
 * @param [in] id     Device-side key handle bytes.
 * @param [in] len    Length of id in bytes.
 * @param [in] heap   Dynamic memory allocation hint.
 * @param [in] devId  Device Id.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key is NULL or when id is NULL with len > 0.
 * @return  BUFFER_E when len is negative or larger than SLHDSA_MAX_ID_LEN.
 */
int wc_SlhDsaKey_Init_id(SlhDsaKey* key, enum SlhDsaParam param,
    const unsigned char* id, int len, void* heap, int devId)
{
    int ret = 0;

    if (key == NULL) {
        ret = BAD_FUNC_ARG;
    }
    /* Reject id == NULL with len > 0. */
    if ((ret == 0) && (id == NULL) && (len > 0)) {
        ret = BAD_FUNC_ARG;
    }
    if ((ret == 0) && ((len < 0) || (len > SLHDSA_MAX_ID_LEN))) {
        ret = BUFFER_E;
    }

    if (ret == 0) {
        ret = wc_SlhDsaKey_Init(key, param, heap, devId);
    }
    if ((ret == 0) && (id != NULL) && (len > 0)) {
        XMEMCPY(key->id, id, (size_t)len);
        key->idLen = len;
    }

    return ret;
}

/* Initialize an SLH-DSA key with a device key label.
 *
 * Label length is taken via XSTRLEN; embedded NULs terminate the label.
 *
 * @param [in] key    SLH-DSA key.
 * @param [in] param  SLH-DSA parameter set to use.
 * @param [in] label  NUL-terminated device-side key label.
 * @param [in] heap   Dynamic memory allocation hint.
 * @param [in] devId  Device Id.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key or label is NULL.
 * @return  BUFFER_E when label is empty or longer than SLHDSA_MAX_LABEL_LEN.
 */
int wc_SlhDsaKey_Init_label(SlhDsaKey* key, enum SlhDsaParam param,
    const char* label, void* heap, int devId)
{
    int ret = 0;
    int labelLen = 0;

    if ((key == NULL) || (label == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    if (ret == 0) {
        labelLen = (int)XSTRLEN(label);
        if ((labelLen == 0) || (labelLen > SLHDSA_MAX_LABEL_LEN)) {
            ret = BUFFER_E;
        }
    }

    if (ret == 0) {
        ret = wc_SlhDsaKey_Init(key, param, heap, devId);
    }
    if (ret == 0) {
        XMEMCPY(key->label, label, (size_t)labelLen);
        key->labelLen = labelLen;
    }

    return ret;
}
#endif /* WOLF_PRIVATE_KEY_ID */

/* Free the SLH-DSA key.
 *
 * @param [in] key  SLH-DSA key. Cannot be used after this call.
 */
void wc_SlhDsaKey_Free(SlhDsaKey* key)
{
    if (key == NULL)
        return;

#if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_FREE)
    if ((key->params != NULL) && (key->devId != INVALID_DEVID)) {
        (void)wc_CryptoCb_Free(key->devId, WC_ALGO_TYPE_PK,
                               WC_PK_TYPE_PQC_SIG_KEYGEN,
                               WC_PQC_SIG_TYPE_SLHDSA,
                               (void*)key);
    }
#endif

    if (key->params != NULL) {
        /* Ensure the private key data is zeroized. */
        ForceZero(key->sk, (size_t)key->params->n * 2);
#ifdef WOLFSSL_SLHDSA_SHA2
        if (SLHDSA_IS_SHA2(key->params->param)) {
            /* Dispose of the SHA2 hash objects. */
            if (key->hash.sha2.sha256_inited) {
                wc_Sha256Free(&key->hash.sha2.sha256);
                key->hash.sha2.sha256_inited = 0;
            }
            if (key->hash.sha2.sha256_2_inited) {
                wc_Sha256Free(&key->hash.sha2.sha256_2);
                key->hash.sha2.sha256_2_inited = 0;
            }
            if (key->hash.sha2.sha256_mid_inited) {
                wc_Sha256Free(&key->hash.sha2.sha256_mid);
                key->hash.sha2.sha256_mid_inited = 0;
            }
            if (key->hash.sha2.sha512_inited) {
                wc_Sha512Free(&key->hash.sha2.sha512);
                key->hash.sha2.sha512_inited = 0;
            }
            if (key->hash.sha2.sha512_2_inited) {
                wc_Sha512Free(&key->hash.sha2.sha512_2);
                key->hash.sha2.sha512_2_inited = 0;
            }
            if (key->hash.sha2.sha512_mid_inited) {
                wc_Sha512Free(&key->hash.sha2.sha512_mid);
                key->hash.sha2.sha512_mid_inited = 0;
            }
        }
        else
#endif
        {
            /* Dispose of the SHAKE-256 objects. */
            wc_Shake256_Free(&key->hash.shk.shake2);
            wc_Shake256_Free(&key->hash.shk.shake);
        }
    }

#ifdef WOLF_PRIVATE_KEY_ID
    key->idLen = 0;
    key->labelLen = 0;
#endif
#ifdef WOLF_CRYPTO_CB
    key->devCtx = NULL;
    key->devId = INVALID_DEVID;
#endif
    /* Marks the key freed; subsequent Frees become no-ops. */
    key->params = NULL;
}

/* Set the HashAddress based on message digest data.
 *
 * FIPS 205. Section 9.2. Algorithm 19.
 * slh_sign_internal(M, SK, addrnd)
 *   1: ADRS <- toByte(0, 32)
 *  ...
 *   7: tmp_idxtree <- digest [upper(k.a / 8) : upper(k.a / 8) +
 *                                              upper((h - h/d) / 8)]
 *                                             > next upper((h - h/d) / 8) bytes
 *   8: tmp_idxleaf <- digest [upper(k.a / 8) + upper((h - h/d) / 8) :
 *                             upper(k.a / 8) + upper((h - h/d) / 8) +
 *                             upper(h / 8d) ]
 *                                                    > next upper(h / 8d) bytes
 *   9: idxtree <- toInt(tmp_idxtree, upper((h-h/d) / 8)) mod 2^(h-h/d)
 *  10: idxleaf <- toInt(tmp_idxleaf, upper(h / 8d)) mode 2^(h/d)
 *  11: ADRS.setTreeAddress(idxtree)
 *  12: ADRS.setTypeAndClear(FORS_TREE)
 *  13: ADRS.setKeyPairAddress(idxleaf)
 *  ...
 *
 * FIPS 205. Section 9.3. Algorithm 20.
 * slh_verify_internal(M, SIG, PK)
 *   4: ADRS <- toByte(0, 32)
 *  ...
 *  10: tmp_idxtree <- digest [upper(k.a / 8) : upper(k.a / 8) +
 *                                              upper((h - h/d) / 8)]
 *                                             > next upper((h - h/d) / 8) bytes
 *  11: tmp_idxleaf <- digest [upper(k.a / 8) + upper((h - h/d) / 8) :
 *                             upper(k.a / 8) + upper((h - h/d) / 8) +
 *                             upper(h / 8d) ]
 *                                                    > next upper(h / 8d) bytes
 *  12: idxtree <- toInt(tmp_idxtree, upper((h-h/d) / 8)) mod 2^(h-h/d)
 *  13: idxleaf <- toInt(tmp_idxleaf, upper(h / 8d)) mode 2^(h/d)
 *  14: ADRS.setTreeAddress(idxtree)
 *  15: ADRS.setTypeAndClear(FORS_TREE)
 *  16: ADRS.setKeyPairAddress(idxleaf)
 *  ...
 *
 * @param [in]  key   SLH-DSA key.
 * @param [in]  md    Message digest.
 * @param [out] adrs  FORS tree HashAddress.
 * @param [out] t     Tree index as 3 32-bit integers.
 * @param [out] l     Tree leaf index.
 */
static void slhdsakey_set_ha_from_md(SlhDsaKey* key, const byte* md,
    HashAddress adrs, word32* t, word32* l)
{
    const byte* p;
    int bits;

    /* Step 1/4: Set address to all zeroes. */
    HA_Init(adrs);
    /* Step 7/10: Get pointer to tree index data. */
    p = md + key->params->dl1 + (key->params->dl2 - 8);
    /* Step 9/12: Convert tree index data to an integer ... */
    t[0] = 0;
    ato32(p + 0, &t[1]);
    ato32(p + 4, &t[2]);
    /* Step 9/12: Mask off any extra high bits. */
    bits = key->params->h  - (key->params->h / key->params->d);
    if (bits < 64) {
        t[1] &= ((word32)1U << (bits - 32)) - 1U;
    }

    /* Step 8/11: Get pointer to tree leaf index data. */
    p = md + key->params->dl1 + key->params->dl2 + (key->params->dl3 - 4);
    /* Step 10/13: Convert tree leaf index data to an integer ... */
    ato32(p, l);
    /* Step 10/13: Mask off any extra high bits. */
    bits = key->params->h / key->params->d;
    *l &= ((word32)1U << bits) - 1U;

    /* Step 11/14: Set the tree index into address. */
    HA_SetTreeAddress(adrs, t);
    /* Step 12/15: Set type of address and clear except key pair address. */
    HA_SetTypeAndClearNotKPA(adrs, HA_FORS_TREE);
    /* Step 13/16: Set key pair address. */
    HA_SetKeyPairAddress(adrs, *l);
}

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
/* Generate an SLH-DSA key with a random number generator.
 *
 * FIPS 205. Section 10.1. Algorithm 21.
 * slh_keygen()
 *   1: SK.seed <-$- Bn     > set SK.seed, SK.prf, and PK.seed to random n-byte
 *   2: SK.prf <-$- Bn          > strings using an approved random bit generator
 *   3: PK.seed <-$- Bn
 *   4: if SK.seed = NULL or SK.prf = NULL or PK.seed = NULL then
 *   5:     return falsity
 *                 > return an error indication if random bit generation failed
 *   6: end if
 *   7: return slh_keygen_internal(SK.seed, SK.prf, PK.seed)
 *
 * @param [in] key  SLH-DSA key.
 * @param [in] rng  Random number generator.
 * @return  0 on success.
 * @return  RNG error code when random number generation fails.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
int wc_SlhDsaKey_MakeKey(SlhDsaKey* key, WC_RNG* rng)
{
    int ret = 0;

    /* Validate parameters. */
    if ((key == NULL) || (key->params == NULL) || (rng == NULL)) {
        ret = BAD_FUNC_ARG;
    }

#ifdef WOLF_CRYPTO_CB
    if (ret == 0) {
    #ifndef WOLF_CRYPTO_CB_FIND
        if (key->devId != INVALID_DEVID)
    #endif
        {
            /* size is the SlhDsaParam enum (S/F variants are distinct). */
            ret = wc_CryptoCb_MakePqcSignatureKey(rng,
                WC_PQC_SIG_TYPE_SLHDSA, (int)key->params->param, key);
            if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE))
                return ret;
            /* fall-through when unavailable */
            ret = 0;
        }
    }
#endif

    if (ret == 0) {
        /* Steps 1-5: Generate the 3 random hashes. */
        ret = wc_RNG_GenerateBlock(rng, key->sk, 3U * key->params->n);
    }
    if (ret == 0) {
        byte n = key->params->n;

        /* Step 7: Make the key with the random  */
        ret = wc_SlhDsaKey_MakeKeyWithRandom(key, key->sk, n, key->sk + n, n,
            key->sk + 2 * n, n);
    }

    return ret;
}

/* Generate an SLH-DSA key pair.
 *
 * FIPS 205. Section 9.1. Algorithm 18.
 * slh_keygen_internal(SK.seed, SK.prf, PK.seed)
 *   1: ADRS <- toByte(0, 32)
 *                        > generate the public key for the top-level XMSS tree
 *   2: ADRS.setLayerAddress(d - 1)
 *   3: PK.root <- xmss_node(SK.seed, 0, h' , PK.seed, ADRS)
 *   4: return ( (SK.seed, SK.prf, PK.seed, PK.root), (PK.seed, PK.root) )
 *
 * @param [in] key          SLH-DSA key.
 * @param [in] sk_seed      Private key seed.
 * @param [in] sk_seed_len  Length of private key seed.
 * @param [in] sk_prf       Private key PRF seed.
 * @param [in] sk_prf_len   Length of private key PRF seed.
 * @param [in] pk_seed      Public key seed.
 * @param [in] pk_seed_len  Length of public key seed.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key or key's parameters is NULL.
 * @return  BAD_FUNC_ARG when sk_seed is NULL or length is not n.
 * @return  BAD_FUNC_ARG when sk_prf is NULL or length is not n.
 * @return  BAD_FUNC_ARG when pk_seed is NULL or length is not n.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
int wc_SlhDsaKey_MakeKeyWithRandom(SlhDsaKey* key, const byte* sk_seed,
    word32 sk_seed_len, const byte* sk_prf, word32 sk_prf_len,
    const byte* pk_seed, word32 pk_seed_len)
{
    int ret = 0;

    /* Validate parameters. */
    if ((key == NULL) || (key->params == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    /* Ensure private key seed is passed in and is the right length. */
    else if ((sk_seed == NULL) || (sk_seed_len != key->params->n)) {
        ret = BAD_FUNC_ARG;
    }
    /* Ensure public key PRF seed is passed in and is the right length. */
    else if ((sk_prf == NULL) || (sk_prf_len != key->params->n)) {
        ret = BAD_FUNC_ARG;
    }
    /* Ensure public key seed is passed in and is the right length. */
    else if ((pk_seed == NULL) || (pk_seed_len != key->params->n)) {
        ret = BAD_FUNC_ARG;
    }
    else {
        byte n = key->params->n;
        HashAddress adrs;

        /* Step 4: Copy the seeds into the key if they didn't come from the key.
         */
        if (sk_seed != key->sk) {
            XMEMCPY(key->sk        , sk_seed, n);
            XMEMCPY(key->sk +     n, sk_prf , n);
            XMEMCPY(key->sk + 2 * n, pk_seed, n);
        }

#ifdef WOLFSSL_SLHDSA_SHA2
        /* Pre-compute SHA2 midstates now that PK.seed is set. */
        if (SLHDSA_IS_SHA2(key->params->param)) {
            ret = slhdsakey_precompute_sha2_midstates(key);
        }
        if (ret != 0) {
            return ret;
        }
#endif

        /* Step 1: Set address to all zeroes. */
        HA_Init(adrs);
        /* Step 2: Set the address layer to the top of the subtree. */
        HA_SetLayerAddress(adrs, key->params->d - 1);
        /* Step 3: Compute the root node. */
        ret = slhdsakey_xmss_node(key, sk_seed, 0, key->params->h_m, pk_seed,
             adrs, &key->sk[3 * n]);
        if (ret == 0) {
            key->flags = WC_SLHDSA_FLAG_BOTH_KEYS;
        }
    }

    return ret;
}

/* Generate an SLH-DSA signature.
 *
 * FIPS 205. Section 9.2. Algorithm 19.
 * slh_sign_internal(M, SK, addrnd)
 *  ...
 *                                              upper((h - h/d) / 8)]
 *                                             > next upper((h - h/d) / 8) bytes
 *   8: tmp_idxleaf <- digest [upper(k.a / 8) + upper((h - h/d) / 8) :
 *                             upper(k.a / 8) + upper((h - h/d) / 8) +
 *                             upper(h / 8d) ]
 *                                                    > next upper(h / 8d) bytes
 *   9: idxtree <- toInt(tmp_idxtree, upper((h-h/d) / 8)) mod 2^(h-h/d)
 *  10: idxleaf <- toInt(tmp_idxleaf, upper(h / 8d)) mode 2^(h/d)
 *  11: ADRS.setTreeAddress(idxtree)
 *  12: ADRS.setTypeAndClear(FORS_TREE)
 *  13: ADRS.setKeyPairAddress(idxleaf)
 *  14: SIGFORS <- fors_sign(md, SK.seed, PK.seed, ADRS)
 *  15: SIG <- SIG || SIGFORS
 *  16: PKFORS <- fors_pkFromSig(SIGFORS, md, PK.seed, ADRS)      > get FORS key
 *  17: SIGHT <- ht_sign(PKFORS , SK.seed, PK.seed, idxtree , idxleaf )
 *  18: SIG <- SIG || SIGHT
 *  19: return SIG
 *
 * @param [in]  key  SLH-DSA key.
 * @param [in]  md   Message digest.
 * @param [out] sig  Signature buffer.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_sign(SlhDsaKey* key, byte* md, byte* sig)
{
    int ret;
    HashAddress adrs;
    word32 t[3];
    word32 l;
    byte pk_fors[SLHDSA_MAX_N];
    byte n = key->params->n;

    /* Steps 1, 7-13: Set address based on message digest. */
    slhdsakey_set_ha_from_md(key, md, adrs, t, &l);

    /* Step 14: FORS sign message. */
    ret = slhdsakey_fors_sign(key, md, key->sk, key->sk + 2 * n, adrs, sig);
    if (ret == 0) {
        /* Step 16: FORS public key from signature. */
        ret = slhdsakey_fors_pk_from_sig(key, sig, md, key->sk + 2 * n, adrs,
            pk_fors);
        /* Step 15: Move over signature data. */
        sig += key->params->k * (1 + key->params->a) * n;
    }
    if (ret == 0) {
        /* Steps 17-18: Hypertree sign FORS public key. */
        ret = slhdsakey_ht_sign(key, pk_fors, key->sk, key->sk + 2 * n, t, l,
            sig);
    }

    return ret;
}

/* Lower-level sign: slh_sign_internal(M, SK, addrnd).
 *
 * Takes M directly and performs PRF_msg, H_msg, and the FORS + hypertree
 * signing -- Algorithm 19 without the M' construction of Algorithm 22.
 *
 * FIPS 205. Section 9.2. Algorithm 19.
 * slh_sign_internal(M, SK, addrnd)
 *   2: opt_rand <- addrnd
 *   3: R <- PRFmsg(SK.prf, opt_rand, M)
 *   4: SIG <- R
 *   5: digest <- Hmsg(R, PK.seed, PK.root, M)
 *   6: md <- digest[0 : upper(k*a / 8)]
 *  ...
 *
 * @param [in]      key     SLH-DSA key (private key must be set).
 * @param [in]      m       Message (goes directly to PRF_msg and H_msg).
 * @param [in]      mSz     Length of message in bytes.
 * @param [out]     sig     Buffer to hold signature.
 * @param [in, out] sigSz   On in, buffer length. On out, signature length.
 * @param [in]      addRnd  opt_rand (PK.seed for deterministic).
 * @return  0 on success.
 */
static int slhdsakey_sign_internal_msg(SlhDsaKey* key, const byte* m,
    word32 mSz, byte* sig, word32* sigSz, const byte* addRnd)
{
    int ret = 0;

    /* Validate parameters. */
    if ((key == NULL) || (key->params == NULL) || (m == NULL) ||
            (sig == NULL) || (sigSz == NULL) || (addRnd == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    else if (*sigSz < key->params->sigLen) {
        ret = BAD_LENGTH_E;
    }
    else if ((key->flags & WC_SLHDSA_FLAG_PRIVATE) == 0) {
        ret = MISSING_KEY;
    }
    if (ret == 0) {
        byte md[SLHDSA_MAX_MD];
        byte n = key->params->n;

#ifdef WOLFSSL_SLHDSA_SHA2
        if (SLHDSA_IS_SHA2(key->params->param)) {
            /* SHA2: PRF_msg = Trunc_n(HMAC(SK.prf, opt_rand || M)).
             * Internal interface: no M' header, pass whole M directly. */
            ret = slhdsakey_prf_msg_sha2(key, key->sk + n, addRnd,
                NULL, NULL, 0, m, mSz, n, sig);
            if (ret == 0) {
                /* SHA2: H_msg via MGF1. No header for internal interface. */
                ret = slhdsakey_h_msg_sha2(key, sig,
                    NULL, NULL, 0, m, mSz,
                    md, (word32)key->params->dl1 + key->params->dl2 +
                    key->params->dl3);
                sig += n;
            }
        }
        else
#endif
        {
            /* SHAKE: PRF_msg = SHAKE256(SK.prf || opt_rand || M, 8n). */
            {
                wc_Shake tmpShake;
                ret = wc_InitShake256(&tmpShake, NULL, INVALID_DEVID);
                if (ret == 0) ret = wc_Shake256_Update(&tmpShake, key->sk + n, n);
                if (ret == 0) ret = wc_Shake256_Update(&tmpShake, addRnd, n);
                if (ret == 0) ret = wc_Shake256_Update(&tmpShake, m, mSz);
                if (ret == 0) ret = wc_Shake256_Final(&tmpShake, sig, n);
                wc_Shake256_Free(&tmpShake);
            }
            /* SHAKE: H_msg = SHAKE256(R || PK.seed || PK.root || M, ...). */
            if (ret == 0) {
                ret = wc_InitShake256(&key->hash.shk.shake, NULL, INVALID_DEVID);
            }
            if (ret == 0) {
                ret = wc_Shake256_Update(&key->hash.shk.shake, sig, n);
                sig += n;
            }
            if (ret == 0) {
                ret = wc_Shake256_Update(&key->hash.shk.shake,
                    key->sk + 2U * n, 2U * n);
            }
            if (ret == 0) {
                ret = wc_Shake256_Update(&key->hash.shk.shake, m, mSz);
            }
            if (ret == 0) {
                ret = wc_Shake256_Final(&key->hash.shk.shake, md,
                    (word32)key->params->dl1 + key->params->dl2 +
                    key->params->dl3);
            }
        }
        if (ret == 0) {
            ret = slhdsakey_sign(key, md, sig);
        }
        if (ret == 0) {
            *sigSz = key->params->sigLen;
        }
    }

    return ret;
}

/* Upper-level sign: construct M' from ctx + msg, then call internal.
 *
 * FIPS 205. Section 10.2.1. Algorithm 22.
 * slh_sign(M, ctx, SK)
 *   8: M' <- toByte(0, 1) || toByte(|ctx|, 1) || ctx || M
 *   9: SIG <- slh_sign_internal(M', SK, addrnd)
 *
 * @param [in]      key    SLH-DSA key.
 * @param [in]      ctx    Context of signing.
 * @param [in]      ctxSz  Length of context in bytes.
 * @param [in]      msg    Message to sign.
 * @param [in]      msgSz  Length of message in bytes.
 * @param [out]     sig    Buffer to hold signature.
 * @param [in, out] sigSz  On in, length of signature buffer.
 *                         On out, length of signature data.
 * @param [in]      addRnd opt_rand (PK.seed for deterministic, random otherwise).
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key, key's parameters, msg, sig, sigSz or addRnd
 *          is NULL.
 * @return  BAD_FUNC_ARG when ctx is NULL but ctx length is greater than 0.
 * @return  BAD_LENGTH_E when sigSz is less than required signature length.
 * @return  MISSING_KEY when private key not set.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_sign_external(SlhDsaKey* key, const byte* ctx, byte ctxSz,
    const byte* msg, word32 msgSz, byte* sig, word32* sigSz,
    const byte* addRnd)
{
    int ret = 0;

    /* Validate parameters. */
    if ((key == NULL) || (key->params == NULL) ||
            ((ctx == NULL) && (ctxSz > 0)) || (msg == NULL) || (sig == NULL) ||
            (sigSz == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    /* Check sig buffer is large enough to hold generated signature. */
    else if (*sigSz < key->params->sigLen) {
        ret = BAD_LENGTH_E;
    }
    /* Alg 22, Step 5: Check addrnd is not NULL. */
    else if (addRnd == NULL) {
        /* Alg 22, Step 6: Return error. */
        ret = BAD_FUNC_ARG;
    }
    /* Check we have a private key to sign with. */
    else if ((key->flags & WC_SLHDSA_FLAG_PRIVATE) == 0) {
        ret = MISSING_KEY;
    }
    if (ret == 0) {
        byte md[SLHDSA_MAX_MD];
        byte hdr[2];
        byte n = key->params->n;

        /* Alg 22, Step 8: M' = toByte(0,1) || toByte(|ctx|,1) || ctx || M.
         * We stream the M' components into PRF_msg and H_msg. */
        hdr[0] = 0;
        hdr[1] = ctxSz;

#ifdef WOLFSSL_SLHDSA_SHA2
        if (SLHDSA_IS_SHA2(key->params->param)) {
            /* SHA2: PRF_msg via HMAC. */
            ret = slhdsakey_prf_msg_sha2(key, key->sk + n, addRnd, hdr, ctx,
                ctxSz, msg, msgSz, n, sig);
            if (ret == 0) {
                /* SHA2: H_msg via MGF1. */
                ret = slhdsakey_h_msg_sha2(key, sig, hdr, ctx, ctxSz, msg,
                    msgSz, md, (word32)key->params->dl1 + key->params->dl2 +
                    key->params->dl3);
                /* Move over randomizer. */
                sig += n;
            }
        }
        else
#endif
        {
            /* SHAKE: PRF_msg streaming with M' = hdr || ctx || msg. */
            ret = slhdsakey_hash_start(&key->hash.shk.shake, key->sk + n, n);
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, addRnd, n);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, hdr,
                    sizeof(hdr));
            }
            if ((ret == 0) && (ctxSz > 0)) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, ctx, ctxSz);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, msg, msgSz);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_final(&key->hash.shk.shake, sig, n);
            }
            /* SHAKE: H_msg streaming. */
            if (ret == 0) {
                ret = slhdsakey_hash_start(&key->hash.shk.shake, sig, n);
                sig += n;
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake,
                    key->sk + 2U * n, 2U * n);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, hdr,
                    sizeof(hdr));
            }
            if ((ret == 0) && (ctxSz > 0)) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, ctx, ctxSz);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, msg, msgSz);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_final(&key->hash.shk.shake, md,
                    (word32)key->params->dl1 + key->params->dl2 +
                    key->params->dl3);
            }
        }
        if (ret == 0) {
            /* Alg 19. Steps 7-19 */
            ret = slhdsakey_sign(key, md, sig);
        }
        if (ret == 0) {
            /* Return the signature size generated. */
            *sigSz = key->params->sigLen;
        }
    }

    return ret;
}

/* Generate a deterministic SLH-DSA signature.
 *
 * addrnd is the public key seed.
 *
 * @param [in]      key    SLH-DSA key.
 * @param [in]      ctx    Context of signing.
 * @param [in]      ctxSz  Length of context in bytes.
 * @param [in]      msg    Message to sign.
 * @param [in]      msgSz  Length of message in bytes.
 * @param [out]     sig    Buffer to hold signature.
 * @param [in, out] sigSz  On in, length of signature buffer.
 *                         On out, length of signature data.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key, key's parameters, msg or sig is NULL.
 * @return  BAD_FUNC_ARG when ctx is NULL but ctx length is greater than 0.
 * @return  BAD_LENGTH_E when sigSz is less than required signature length.
 * @return  MISSING_KEY when private key not set.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
int wc_SlhDsaKey_SignDeterministic(SlhDsaKey* key, const byte* ctx, byte ctxSz,
    const byte* msg, word32 msgSz, byte* sig, word32* sigSz)
{
    int ret;

    /* Validate parameters that will be used in this function. */
    if ((key == NULL) || (key->params == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    else {
        /* Pure sign. */
        ret = slhdsakey_sign_external(key, ctx, ctxSz, msg, msgSz, sig, sigSz,
            key->sk + 2 * key->params->n);
    }

    return ret;
}

/* Generate a pure SLH-DSA signature.
 *
 * @param [in]      key     SLH-DSA key.
 * @param [in]      ctx     Context of signing.
 * @param [in]      ctxSz   Length of context in bytes.
 * @param [in]      msg     Message to sign.
 * @param [in]      msgSz   Length of message in bytes.
 * @param [out]     sig     Buffer to hold signature.
 * @param [in, out] sigSz   On in, length of signature buffer.
 *                          On out, length of signature data.
 * @param [in]      addRnd  Additional random for signature.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key, key's parameters, msg, sig or addrnd is NULL.
 * @return  BAD_FUNC_ARG when ctx is NULL but ctx length is greater than 0.
 * @return  BAD_LENGTH_E when sigSz is less than required signature length.
 * @return  MISSING_KEY when private key not set.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
int wc_SlhDsaKey_SignWithRandom(SlhDsaKey* key, const byte* ctx, byte ctxSz,
    const byte* msg, word32 msgSz, byte* sig, word32* sigSz, const byte* addRnd)
{
    /* Pure sign. */
    return slhdsakey_sign_external(key, ctx, ctxSz, msg, msgSz, sig, sigSz,
        addRnd);
}

/* Generate a pure SLH-DSA signature with a random number generator.
 *
 * @param [in]      key     SLH-DSA key.
 * @param [in]      ctx     Context of signing.
 * @param [in]      ctxSz   Length of context in bytes.
 * @param [in]      msg     Message to sign.
 * @param [in]      msgSz   Length of message in bytes.
 * @param [out]     sig     Buffer to hold signature.
 * @param [in, out] sigSz   On in, length of signature buffer.
 *                          On out, length of signature data.
 * @param [in]      rng     Random number generator.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key, key's parameters, msg, sig, sigSz or rng is
 *          NULL.
 * @return  BAD_FUNC_ARG when ctx is NULL but ctx length is greater than 0.
 * @return  BAD_LENGTH_E when sigSz is less than required signature length.
 * @return  MISSING_KEY when private key not set.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
int wc_SlhDsaKey_Sign(SlhDsaKey* key, const byte* ctx, byte ctxSz,
    const byte* msg, word32 msgSz, byte* sig, word32* sigSz, WC_RNG* rng)
{
    int ret = 0;
    byte addRnd[SLHDSA_MAX_N];

    /* Validate parameters before generating random. */
    if ((key == NULL) || (key->params == NULL) ||
            ((ctx == NULL) && (ctxSz > 0)) || (msg == NULL) || (sig == NULL) ||
            (sigSz == NULL) || (rng == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    /* Check sig buffer is large enough to hold generated signature. */
    else if (*sigSz < key->params->sigLen) {
        ret = BAD_LENGTH_E;
    }
    /* Check we have a private key to sign with. */
    else if ((key->flags & WC_SLHDSA_FLAG_PRIVATE) == 0) {
        ret = MISSING_KEY;
    }

#ifdef WOLF_CRYPTO_CB
    if (ret == 0) {
    #ifndef WOLF_CRYPTO_CB_FIND
        if (key->devId != INVALID_DEVID)
    #endif
        {
            ret = wc_CryptoCb_PqcSign(msg, msgSz, sig, sigSz, ctx, ctxSz,
                WC_HASH_TYPE_NONE, rng, WC_PQC_SIG_TYPE_SLHDSA, key);
            if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE))
                return ret;
            /* fall-through when unavailable */
            ret = 0;
        }
    }
#endif

    if (ret == 0) {
        /* Generate n bytes of random. */
        ret = wc_RNG_GenerateBlock(rng, addRnd, key->params->n);
    }
    if (ret == 0) {
        /* Pure sign. */
        ret = wc_SlhDsaKey_SignWithRandom(key, ctx, ctxSz, msg, msgSz, sig,
            sigSz, addRnd);
    }

    ForceZero(addRnd, sizeof(addRnd));

    return ret;
}

/* Sign using the FIPS 205 internal interface (Algorithm 19) -- M' provided
 * directly by the caller, deterministic variant (opt_rand = PK.seed).
 *
 * Used for HashSLH-DSA implementations that build M' externally and for ACVP
 * signatureInterface=internal test vectors.
 *
 * @param [in]      key       SLH-DSA key.
 * @param [in]      mprime    M' message (already in internal format).
 * @param [in]      mprimeSz  Length of M' in bytes.
 * @param [out]     sig       Buffer to hold signature.
 * @param [in, out] sigSz     On in, buffer length. On out, signature length.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key, key's parameters, mprime, sig or sigSz is
 *          NULL.
 * @return  BAD_LENGTH_E when sigSz is less than required signature length.
 * @return  MISSING_KEY when private key not set.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
int wc_SlhDsaKey_SignMsgDeterministic(SlhDsaKey* key, const byte* mprime,
    word32 mprimeSz, byte* sig, word32* sigSz)
{
    int ret = 0;

    if ((key == NULL) || (key->params == NULL) || (mprime == NULL) ||
            (sig == NULL) || (sigSz == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    else if (*sigSz < key->params->sigLen) {
        ret = BAD_LENGTH_E;
    }
    else if ((key->flags & WC_SLHDSA_FLAG_PRIVATE) == 0) {
        ret = MISSING_KEY;
    }
    if (ret == 0) {
        ret = slhdsakey_sign_internal_msg(key, mprime, mprimeSz, sig, sigSz,
            key->sk + 2 * key->params->n);
    }

    return ret;
}

/* Sign using the FIPS 205 internal interface (Algorithm 19) -- M' provided
 * directly by the caller, with explicit randomness.
 *
 * Used for HashSLH-DSA implementations that build M' externally and for ACVP
 * signatureInterface=internal test vectors.
 *
 * @param [in]      key       SLH-DSA key.
 * @param [in]      mprime    M' message (already in internal format).
 * @param [in]      mprimeSz  Length of M' in bytes.
 * @param [out]     sig       Buffer to hold signature.
 * @param [in, out] sigSz     On in, buffer length. On out, signature length.
 * @param [in]      addRnd    opt_rand value.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key, key's parameters, mprime, sig, sigSz or
 *          addRnd is NULL.
 * @return  BAD_LENGTH_E when sigSz is less than required signature length.
 * @return  MISSING_KEY when private key not set.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
int wc_SlhDsaKey_SignMsgWithRandom(SlhDsaKey* key, const byte* mprime,
    word32 mprimeSz, byte* sig, word32* sigSz, const byte* addRnd)
{
    int ret = 0;

    if ((key == NULL) || (key->params == NULL) || (mprime == NULL) ||
            (sig == NULL) || (sigSz == NULL) || (addRnd == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    else if (*sigSz < key->params->sigLen) {
        ret = BAD_LENGTH_E;
    }
    else if ((key->flags & WC_SLHDSA_FLAG_PRIVATE) == 0) {
        ret = MISSING_KEY;
    }
    if (ret == 0) {
        ret = slhdsakey_sign_internal_msg(key, mprime, mprimeSz, sig, sigSz,
            addRnd);
    }

    return ret;
}

#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

/* Verify SLH-DSA signature.
 *
 * FIPS 205. Section 9.3. Algorithm 20.
 * slh_verify_internal(M, SIG, PK)
 *  ...
 *   6: SIGFORS <- SIG.getSIG_FORS()               > SIG[n : (1 + k(1 + a)) . n]
 *   7: SIGHT <- SIG.getSIG_HT()
 *                  > SIG[(1 + k(1 + a)) . n : (1 + k(1 + a) + h + d . len) . n]
 *  ...
 *  17: PKFORS <- fors_pkFromSig(SIGFORS, md, PK.seed, ADRS)
 *  18: return ht_verify(PKFORS, SIGHT, PK.seed, idxtree, idxleaf, PK.root)
 *
 * @param [in] key  SLH-DSA key.
 * @param [in] md   Message digest.
 * @param [in] sig  Signature data.
 * @return  0 on success.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_verify(SlhDsaKey* key, byte* md, const byte* sig)
{
    int ret;
    HashAddress adrs;
    word32 t[3];
    word32 l;
    byte pk_fors[SLHDSA_MAX_N];
    byte n = key->params->n;

    /* Steps 4, 10-16: Set address based on message digest. */
    slhdsakey_set_ha_from_md(key, md, adrs, t, &l);

    /* Step 6: Move pointer to FORS signature. */
    sig += n;
    /* Step 17: Get FORS public key from FORS signature. */
    ret = slhdsakey_fors_pk_from_sig(key, sig, md, key->sk + 2 * n, adrs,
        pk_fors);
    /* Step 7: Move pointer to hypertree signature. */
    sig += key->params->k * (1 + key->params->a) * n;
    if (ret == 0) {
        /* Step 18: Verify hypertree signature. */
        ret = slhdsakey_ht_verify(key, pk_fors, sig, key->sk + 2 * n, t, l,
            key->sk + 3 * n);
    }

    return ret;
}

/* Verify SLH-DSA signature.
 *
 * FIPS 205. Section 9.3. Algorithm 20.
 * slh_verify_internal(M, SIG, PK)
 *   1: if |SIG| != (1 + k(1 + a) + h + d . len . n then
 *   2:     return false
 *   3: end if
 *  ...
 *   5: R <- SIG.getR()                                             > SIG[0 : n]
 *  ...
 *   8: digest <- Hmsg (R, PK.seed, PK.root, M)         > compute message digest
 *   9: md <- digest [0 : upper(k.a / 8)]           > first upper(k.a / 8) bytes
 *  ...
 *
 * FIPS 205. Section 10.3. Algorithm 24.
 * slh_verify(M, SIG, ctx, PK)
 *   1: if |ctx| > 255 then
 *   2:     return false
 *   3: end if
 *   4: M' <- toByte(0, 1) || toByte(|ctx|, 1) || ctx
 *   5: return slh_verify_internal(M', SIG, PK)
 *
 * Note: ctx length is of type byte which means it can never be more than 255.
 *
 * @param [in] key    SLH-DSA key.
 * @param [in] ctx    Context of signing.
 * @param [in] ctxSz  Length of context in bytes.
 * @param [in] msg    Message to sign.
 * @param [in] msgSz  Length of message in bytes.
 * @param [in] sig    Signature data.
 * @param [in] sigSz  Length of signature in bytes.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key, key's parameters, msg or sig is NULL.
 * @return  BAD_FUNC_ARG when ctx is NULL but ctxSz is greater than 0.
 * @return  BAD_LENGTH_E when signature size does not match parameters.
 * @return  MISSING_KEY when public key not set.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
int wc_SlhDsaKey_Verify(SlhDsaKey* key, const byte* ctx, byte ctxSz,
    const byte* msg, word32 msgSz, const byte* sig, word32 sigSz)
{
    int ret = 0;

    /* Validate parameters. */
    if ((key == NULL) || (key->params == NULL) ||
            ((ctx == NULL) && (ctxSz > 0)) || (msg == NULL) ||
            (sig == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    /* Alg 20, Step 1: Check signature length is the expect length. */
    else if (sigSz != key->params->sigLen) {
        /* Alg 20, Step 2: Return error  */
        ret = BAD_LENGTH_E;
    }
    /* Check we have a public key to verify with. */
    else if ((key->flags & WC_SLHDSA_FLAG_PUBLIC) == 0) {
        ret = MISSING_KEY;
    }

#ifdef WOLF_CRYPTO_CB
    if (ret == 0) {
    #ifndef WOLF_CRYPTO_CB_FIND
        if (key->devId != INVALID_DEVID)
    #endif
        {
            int res = 0;
            ret = wc_CryptoCb_PqcVerify(sig, sigSz, msg, msgSz, ctx, ctxSz,
                WC_HASH_TYPE_NONE, &res, WC_PQC_SIG_TYPE_SLHDSA, key);
            if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) {
                if (ret != 0)
                    return ret;
                return (res == 1) ? 0 : SIG_VERIFY_E;
            }
            /* fall-through when unavailable */
            ret = 0;
        }
    }
#endif

    if (ret == 0) {
        byte md[SLHDSA_MAX_MD];
        byte n = key->params->n;
        byte hdr[2];

        /* Alg 24, Step 4: Make M' header. */
        hdr[0] = 0;
        hdr[1] = ctxSz;

#ifdef WOLFSSL_SLHDSA_SHA2
        if (SLHDSA_IS_SHA2(key->params->param)) {
            /* SHA2: H_msg via MGF1 (no PRF_msg for verify). */
            ret = slhdsakey_h_msg_sha2(key, sig, hdr, ctx, ctxSz, msg, msgSz,
                md, (word32)key->params->dl1 + key->params->dl2 +
                key->params->dl3);
        }
        else
#endif
        {
            /* SHAKE: H_msg streaming. */
            ret = slhdsakey_hash_start(&key->hash.shk.shake, sig, n);
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake,
                    key->sk + 2U * n, 2U * n);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, hdr,
                    sizeof(hdr));
            }
            if ((ret == 0) && (ctxSz > 0)) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, ctx, ctxSz);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, msg, msgSz);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_final(&key->hash.shk.shake, md,
                    (word32)key->params->dl1 + key->params->dl2 +
                    key->params->dl3);
            }
        }
        if (ret == 0) {
            /* Alg 24, Step 5: Verify M'.
             * Alg 20, Steps 4,6-18: Verify digest. */
            ret = slhdsakey_verify(key, md, sig);
        }
    }

    return ret;
}

/* Verify SLH-DSA signature using internal interface -- M' provided directly.
 *
 * FIPS 205. Section 9.3. Algorithm 20.
 * slh_verify_internal(M', SIG, PK)
 *
 * @param [in] key       SLH-DSA key.
 * @param [in] mprime    M' message (already in internal format).
 * @param [in] mprimeSz  Length of M' in bytes.
 * @param [in] sig       Signature data.
 * @param [in] sigSz     Length of signature in bytes.
 * @return  0 on success.
 * @return  SIG_VERIFY_E on verification failure.
 */
int wc_SlhDsaKey_VerifyMsg(SlhDsaKey* key, const byte* mprime,
    word32 mprimeSz, const byte* sig, word32 sigSz)
{
    int ret = 0;

    /* Validate parameters. */
    if ((key == NULL) || (key->params == NULL) || (mprime == NULL) ||
            (sig == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    else if (sigSz != key->params->sigLen) {
        ret = BAD_LENGTH_E;
    }
    else if ((key->flags & WC_SLHDSA_FLAG_PUBLIC) == 0) {
        ret = MISSING_KEY;
    }
    if (ret == 0) {
        byte md[SLHDSA_MAX_MD];
        byte n = key->params->n;

#ifdef WOLFSSL_SLHDSA_SHA2
        if (SLHDSA_IS_SHA2(key->params->param)) {
            /* SHA2: H_msg. Internal interface: no M' header, pass whole
             * message directly. */
            ret = slhdsakey_h_msg_sha2(key, sig,
                NULL, NULL, 0, mprime, mprimeSz,
                md, (word32)key->params->dl1 + key->params->dl2 +
                key->params->dl3);
        }
        else
#endif
        {
            /* SHAKE: H_msg = SHAKE(R || PK.seed || PK.root || M). */
            ret = slhdsakey_hash_start(&key->hash.shk.shake, sig, n);
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake,
                    key->sk + 2U * n, 2U * n);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake,
                    mprime, mprimeSz);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_final(&key->hash.shk.shake, md,
                    (word32)key->params->dl1 + key->params->dl2 +
                    key->params->dl3);
            }
        }
        if (ret == 0) {
            ret = slhdsakey_verify(key, md, sig);
        }
    }

    return ret;
}

/* All HashSLH-DSA hash OIDs are DER-encoded as tag(0x06) + length(0x09) + 9
 * bytes, so any approved hash OID is exactly 11 bytes. The PRF_msg / H_msg
 * input for the SHA-2 path is the concatenation OID || PHM, bounded by
 * SLHDSA_OID_MAX_LEN + WC_MAX_DIGEST_SIZE. The PHM buffer fits in
 * WC_MAX_DIGEST_SIZE bytes because slhdsakey_validate_prehash enforces
 * hashSz == expectedLen[hashType] for every supported hashType and every
 * supported expectedLen is <= WC_MAX_DIGEST_SIZE. The largest FIPS 205
 * approved PHM is 64 bytes (SHA-512 digest size, also the SHAKE256 PHM
 * length fixed at 512 bits per Section 10.2.2). The static assert below
 * catches a future hash being added whose digest exceeds the bound. The
 * literal 64 is used directly because WC_SHA512_DIGEST_SIZE is only
 * defined when SHA-512 is compiled in. */
#define SLHDSA_OID_MAX_LEN              11
#define SLHDSA_LARGEST_APPROVED_PHM_LEN 64
#define SLHDSA_PHMSG_MAX_LEN            (SLHDSA_OID_MAX_LEN + \
                                         WC_MAX_DIGEST_SIZE)
wc_static_assert(WC_MAX_DIGEST_SIZE >= SLHDSA_LARGEST_APPROVED_PHM_LEN);

#ifdef WOLFSSL_SHA224
/* OID for SHA-224 for hash signing/verification. */
static const byte slhdsakey_oid_sha224[] = {
    0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04
};
#endif
#ifndef NO_SHA256
/* OID for SHA-256 for hash signing/verification. */
static const byte slhdsakey_oid_sha256[] = {
    0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01
};
#endif
#ifdef WOLFSSL_SHA384
/* OID for SHA-384 for hash signing/verification. */
static const byte slhdsakey_oid_sha384[] = {
    0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02
};
#endif
#ifdef WOLFSSL_SHA512
/* OID for SHA-512 for hash signing/verification. */
static const byte slhdsakey_oid_sha512[] = {
    0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03
};
#ifndef WOLFSSL_NOSHA512_224
/* OID for SHA-512/224 for hash signing/verification. */
static const byte slhdsakey_oid_sha512_224[] = {
    0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x05
};
#endif
#ifndef WOLFSSL_NOSHA512_256
/* OID for SHA-512/256 for hash signing/verification. */
static const byte slhdsakey_oid_sha512_256[] = {
    0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x06
};
#endif
#endif
#ifdef WOLFSSL_SHAKE128
/* OID for SHAKE-128 for hash signing/verification. */
static const byte slhdsakey_oid_shake128[] = {
    0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0b
};
#endif
#ifdef WOLFSSL_SHAKE256
/* OID for SHAKE-256 for hash signing/verification. */
static const byte slhdsakey_oid_shake256[] = {
    0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0c
};
#endif
#ifdef WOLFSSL_SHA3
#ifndef WOLFSSL_NOSHA3_224
/* OID for SHA3-224 for hash signing/verification. */
static const byte slhdsakey_oid_sha3_224[] = {
    0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x07
};
#endif
#ifndef WOLFSSL_NOSHA3_256
/* OID for SHA3-256 for hash signing/verification. */
static const byte slhdsakey_oid_sha3_256[] = {
    0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x08
};
#endif
#ifndef WOLFSSL_NOSHA3_384
/* OID for SHA3-384 for hash signing/verification. */
static const byte slhdsakey_oid_sha3_384[] = {
    0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x09
};
#endif
#ifndef WOLFSSL_NOSHA3_512
/* OID for SHA3-512 for hash signing/verification. */
static const byte slhdsakey_oid_sha3_512[] = {
    0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0a
};
#endif
#endif

/* Validate the caller-supplied pre-hashed digest length and look up the
 * corresponding OID for the chosen hash algorithm.
 *
 * The HashSLH-DSA family takes the digest as input rather than the full
 * message. This mirrors the wc_MlDsaKey_*Ctx_Hash interface and matches the
 * convention used by NIST ACVP signatureInterface=external / preHash test
 * vectors and other libraries (OpenSSL HASH-ML-DSA, leancrypto SLH-DSA,
 * mldsa-native pre_hash_internal). The expected digest length is fixed by
 * FIPS 205 Section 10.2.2 and equals wc_HashGetDigestSize(hashType) for the
 * fixed-output hashes; for SHAKE128/256 the standard fixes the XOF output to
 * 256/512 bits respectively. Callers feed the caller-supplied digest buffer
 * directly into the M' construction -- there is no copy.
 *
 * @param [in]  hashSz    Length of the caller-supplied digest in bytes.
 * @param [in]  hashType  Hash algorithm identifier (selects OID and length).
 * @param [out] oid       OID data for hash algorithm.
 * @param [out] oidLen    Length of OID data for hash algorithm.
 * @return  0 on success.
 * @return  BAD_LENGTH_E when hashSz does not equal the expected digest size.
 * @return  NOT_COMPILED_IN when hash algorithm not supported.
 */
static int slhdsakey_validate_prehash(word32 hashSz,
    enum wc_HashType hashType, const byte** oid, byte* oidLen)
{
    int ret = 0;
    word32 expectedLen = 0;

    switch ((int)hashType) {
    #ifdef WOLFSSL_SHA224
        case WC_HASH_TYPE_SHA224:
            *oid = slhdsakey_oid_sha224;
            *oidLen = (byte)sizeof(slhdsakey_oid_sha224);
            expectedLen = WC_SHA224_DIGEST_SIZE;
            break;
    #endif
    #ifndef NO_SHA256
        case WC_HASH_TYPE_SHA256:
            *oid = slhdsakey_oid_sha256;
            *oidLen = (byte)sizeof(slhdsakey_oid_sha256);
            expectedLen = WC_SHA256_DIGEST_SIZE;
            break;
    #endif
    #ifdef WOLFSSL_SHA384
        case WC_HASH_TYPE_SHA384:
            *oid = slhdsakey_oid_sha384;
            *oidLen = (byte)sizeof(slhdsakey_oid_sha384);
            expectedLen = WC_SHA384_DIGEST_SIZE;
            break;
    #endif
#ifdef WOLFSSL_SHA512
        case WC_HASH_TYPE_SHA512:
            *oid = slhdsakey_oid_sha512;
            *oidLen = (byte)sizeof(slhdsakey_oid_sha512);
            expectedLen = WC_SHA512_DIGEST_SIZE;
            break;
    #ifndef WOLFSSL_NOSHA512_224
        case WC_HASH_TYPE_SHA512_224:
            *oid = slhdsakey_oid_sha512_224;
            *oidLen = (byte)sizeof(slhdsakey_oid_sha512_224);
            expectedLen = WC_SHA512_224_DIGEST_SIZE;
            break;
    #endif
    #ifndef WOLFSSL_NOSHA512_256
        case WC_HASH_TYPE_SHA512_256:
            *oid = slhdsakey_oid_sha512_256;
            *oidLen = (byte)sizeof(slhdsakey_oid_sha512_256);
            expectedLen = WC_SHA512_256_DIGEST_SIZE;
            break;
    #endif
#endif
    #ifdef WOLFSSL_SHAKE128
        case WC_HASH_TYPE_SHAKE128:
            *oid = slhdsakey_oid_shake128;
            *oidLen = (byte)sizeof(slhdsakey_oid_shake128);
            /* FIPS 205 Section 10.2.2 fixes SHAKE128 PHM length at 256 bits. */
            expectedLen = WC_SHA3_256_DIGEST_SIZE;
            break;
    #endif
    #ifdef WOLFSSL_SHAKE256
        case WC_HASH_TYPE_SHAKE256:
            *oid = slhdsakey_oid_shake256;
            *oidLen = (byte)sizeof(slhdsakey_oid_shake256);
            /* FIPS 205 Section 10.2.2 fixes SHAKE256 PHM length at 512 bits. */
            expectedLen = WC_SHA3_512_DIGEST_SIZE;
            break;
    #endif
    #ifdef WOLFSSL_SHA3
    #ifndef WOLFSSL_NOSHA3_224
        case WC_HASH_TYPE_SHA3_224:
            *oid = slhdsakey_oid_sha3_224;
            *oidLen = (byte)sizeof(slhdsakey_oid_sha3_224);
            expectedLen = WC_SHA3_224_DIGEST_SIZE;
            break;
    #endif
    #ifndef WOLFSSL_NOSHA3_256
        case WC_HASH_TYPE_SHA3_256:
            *oid = slhdsakey_oid_sha3_256;
            *oidLen = (byte)sizeof(slhdsakey_oid_sha3_256);
            expectedLen = WC_SHA3_256_DIGEST_SIZE;
            break;
    #endif
    #ifndef WOLFSSL_NOSHA3_384
        case WC_HASH_TYPE_SHA3_384:
            *oid = slhdsakey_oid_sha3_384;
            *oidLen = (byte)sizeof(slhdsakey_oid_sha3_384);
            expectedLen = WC_SHA3_384_DIGEST_SIZE;
            break;
    #endif
    #ifndef WOLFSSL_NOSHA3_512
        case WC_HASH_TYPE_SHA3_512:
            *oid = slhdsakey_oid_sha3_512;
            *oidLen = (byte)sizeof(slhdsakey_oid_sha3_512);
            expectedLen = WC_SHA3_512_DIGEST_SIZE;
            break;
    #endif
    #endif
        default:
            ret = NOT_COMPILED_IN;
            break;
    }

    if ((ret == 0) && (hashSz != expectedLen)) {
        ret = BAD_LENGTH_E;
    }

    return ret;
}

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
/* Generate pre-hash SLH-DSA signature.
 *
 * FIPS 205. Section 10.2.2. Algorithm 23.
 * hash_slh_sign(M, ctx, PH, SK)
 *   1: if |ctx| > 255 then
 *   2:  return falsity
 *                > return an error indication if the context string is too long
 *   3: end if
 *   4: addrnd <-$- Bn    > skip lines 4 through 7 for the deterministic variant
 *   5: if addrnd = NULL then
 *   6:     return falsity
 *                  > return an error indication if random bit generation failed
 *   7: end if
 *   8: switch PH do
 *   9:     case SHA-256:
 *  10:         OID <- toByte(0x0609608648016503040201, 11)
 *                                                      > 2.16.840.1.101.3.4.2.1
 *  11:         PHM <- SHA-256(M)
 *  12:     case SHA-512:
 *  13:         OID <- toByte(0x0609608648016503040203, 11)
 *                                                      > 2.16.840.1.101.3.4.2.3
 *  14:         PHM <- SHA-512(M)
 *  15:     case SHAKE128:
 *  16:         OID <- toByte(0x060960864801650304020B, 11)
 *                                                     > 2.16.840.1.101.3.4.2.11
 *  17:         PHM <- SHAKE128(M, 256)
 *  18:     case SHAKE256:
 *  19:         OID <- toByte(0x060960864801650304020C, 11)
 *                                                     > 2.16.840.1.101.3.4.2.12
 *  20:         PHM <- SHAKE256(M , 512)
 *  21:     case ...                     > other approved hash functions or XOFs
 *  22:         ...
 *  23: end switch
 *  24: M' <- toByte(1, 1) || toByte(|ctx|, 1) || ctx || OID || PHM
 *                                   > omit addrnd for the deterministic variant
 *  25: SIG <- slh_sign_internal(M', SK, addrnd)
 *  26: return SIG
 *
 * FIPS 205. Section 9.2. Algorithm 19.
 * slh_sign_internal(M, SK, addrnd)
 *  ...
 *   2: opt_rand <- addrnd
 *                > substitute opt_rand <- PK.seed for the deterministic variant
 *   3: R <- PRFmsg (SK.prf, opt_rand, M)                  > generate randomizer
 *   4: SIG <- R
 *   5: digest <- Hmsg(R, PK.seed, PK.root, M)          > compute message digest
 *   6: md <- digest [0 : upper(k.a / 8)]          > first upper(k.a / 8)] bytes
 *  ...
 *
 * Note: ctx length is of type byte which means it can never be more than 255.
 *
 * The caller MUST pre-hash the application message with hashType before
 * calling and pass the digest as hash. hashSz must equal the digest size of
 * hashType (32 for SHAKE128, 64 for SHAKE256 per FIPS 205 Section 10.2.2).
 *
 * @param [in]      key       SLH-DSA key.
 * @param [in]      ctx       Context of signing.
 * @param [in]      ctxSz     Length of context in bytes.
 * @param [in]      hash      Pre-hashed message digest to sign.
 * @param [in]      hashSz    Length of digest in bytes.
 * @param [in]      hashType  Hash algorithm used for pre-hash (selects OID).
 * @param [out]     sig       Buffer to hold signature.
 * @param [in, out] sigSz     On in, length of signature buffer.
 *                            On out, length of signature data.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key, key's parameters, hash, sig, sigSz or addRnd
 *          is NULL.
 * @return  BAD_FUNC_ARG when ctx is NULL but ctx length is greater than 0.
 * @return  BAD_LENGTH_E when sigSz is less than required signature length, or
 *          when hashSz does not equal the digest size for hashType.
 * @return  NOT_COMPILED_IN when hash algorithm is not supported.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
static int slhdsakey_signhash_external(SlhDsaKey* key, const byte* ctx,
    byte ctxSz, const byte* hash, word32 hashSz, enum wc_HashType hashType,
    byte* sig, word32* sigSz, const byte* addRnd)
{
    int ret = 0;
    const byte* oid = NULL;
    byte oidLen = 0;

    /* Validate parameters. */
    if ((key == NULL) || (key->params == NULL) ||
            ((ctx == NULL) && (ctxSz > 0)) || (hash == NULL) || (sig == NULL) ||
            (sigSz == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    /* Check sig buffer is large enough to hold generated signature. */
    else if (*sigSz < key->params->sigLen) {
        ret = BAD_LENGTH_E;
    }
    /* Alg 23, Step 5: Check addrnd is not NULL. */
    else if (addRnd == NULL) {
        /* Alg 23, Step 6: Return error. */
        ret = BAD_FUNC_ARG;
    }
    if (ret == 0) {
        /* Alg 23, Steps 8-23: Validate caller-supplied pre-hashed digest length
         * and select OID for the chosen hash algorithm. */
        ret = slhdsakey_validate_prehash(hashSz, hashType, &oid, &oidLen);
    }
    if (ret == 0) {
        byte n = key->params->n;
        byte md[SLHDSA_MAX_MD];
        byte hdr[2];

        /* Alg 23, Step 24: Set first two bytes to pass to hash ... */
        hdr[0] = 1;
        hdr[1] = ctxSz;

#ifdef WOLFSSL_SLHDSA_SHA2
        if (SLHDSA_IS_SHA2(key->params->param)) {
            /* SHA2: Build oid||hash as message for PRF_msg/H_msg. */
            byte phMsg[SLHDSA_PHMSG_MAX_LEN];
            word32 phMsgLen = (word32)oidLen + hashSz;

            XMEMCPY(phMsg, oid, oidLen);
            XMEMCPY(phMsg + oidLen, hash, hashSz);

            ret = slhdsakey_prf_msg_sha2(key, key->sk + n, addRnd, hdr, ctx,
                ctxSz, phMsg, phMsgLen, n, sig);
            if (ret == 0) {
                ret = slhdsakey_h_msg_sha2(key, sig, hdr, ctx, ctxSz, phMsg,
                    phMsgLen, md, (word32)key->params->dl1 + key->params->dl2 +
                    key->params->dl3);
                sig += n;
            }
        }
        else
#endif
        {
            /* SHAKE: PRF_msg streaming. */
            ret = slhdsakey_hash_start(&key->hash.shk.shake, key->sk + n, n);
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, addRnd, n);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, hdr,
                    sizeof(hdr));
            }
            if ((ret == 0) && (ctxSz > 0)) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, ctx, ctxSz);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, oid, oidLen);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, hash, hashSz);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_final(&key->hash.shk.shake, sig, n);
            }
            /* SHAKE: H_msg streaming. */
            if (ret == 0) {
                ret = slhdsakey_hash_start(&key->hash.shk.shake, sig, n);
                sig += n;
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake,
                    key->sk + 2U * n, 2U * n);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, hdr,
                    sizeof(hdr));
            }
            if ((ret == 0) && (ctxSz > 0)) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, ctx, ctxSz);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, oid, oidLen);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, hash, hashSz);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_final(&key->hash.shk.shake, md,
                    (word32)key->params->dl1 + key->params->dl2 +
                    key->params->dl3);
            }
        }
        if (ret == 0) {
            /* Alg 19. Steps 7-19 */
            ret = slhdsakey_sign(key, md, sig);
        }
        if (ret == 0) {
            /* Return the signature size generated. */
            *sigSz = key->params->sigLen;
        }
    }

    return ret;
}

/* Generate a deterministic HashSLH-DSA signature.
 *
 * addrnd is the public key seed. The caller MUST pre-hash the application
 * message with hashType before calling and pass the digest as hash; hashSz
 * must equal the digest size of hashType (32 for SHAKE128, 64 for SHAKE256
 * per FIPS 205 Section 10.2.2).
 *
 * @param [in]      key       SLH-DSA key.
 * @param [in]      ctx       Context of signing.
 * @param [in]      ctxSz     Length of context in bytes.
 * @param [in]      hash      Pre-hashed message digest to sign.
 * @param [in]      hashSz    Length of digest in bytes.
 * @param [in]      hashType  Hash algorithm used for pre-hash (selects OID).
 * @param [out]     sig       Buffer to hold signature.
 * @param [in, out] sigSz     On in, length of signature buffer.
 *                            On out, length of signature data.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key, key's parameters, hash, sig or sigSz is NULL.
 * @return  BAD_FUNC_ARG when ctx is NULL but ctx length is greater than 0.
 * @return  BAD_LENGTH_E when sigSz is less than required signature length, or
 *          when hashSz does not equal the digest size for hashType.
 * @return  MISSING_KEY when private key not set.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
int wc_SlhDsaKey_SignHashDeterministic(SlhDsaKey* key, const byte* ctx,
    byte ctxSz, const byte* hash, word32 hashSz, enum wc_HashType hashType,
    byte* sig, word32* sigSz)
{
    int ret;

    /* Validate parameters that will be used in this function. */
    if ((key == NULL) || (key->params == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    /* Check we have a private key to sign with. */
    else if ((key->flags & WC_SLHDSA_FLAG_PRIVATE) == 0) {
        ret = MISSING_KEY;
    }
    else {
        /* HashSLH-DSA sign with caller-supplied digest. */
        ret = slhdsakey_signhash_external(key, ctx, ctxSz, hash, hashSz,
            hashType, sig, sigSz, key->sk + 2 * key->params->n);
    }

    return ret;
}

/* Generate a HashSLH-DSA signature with explicit randomness.
 *
 * The caller MUST pre-hash the application message with hashType before
 * calling and pass the digest as hash; hashSz must equal the digest size of
 * hashType (32 for SHAKE128, 64 for SHAKE256 per FIPS 205 Section 10.2.2).
 *
 * @param [in]      key       SLH-DSA key.
 * @param [in]      ctx       Context of signing.
 * @param [in]      ctxSz     Length of context in bytes.
 * @param [in]      hash      Pre-hashed message digest to sign.
 * @param [in]      hashSz    Length of digest in bytes.
 * @param [in]      hashType  Hash algorithm used for pre-hash (selects OID).
 * @param [out]     sig       Buffer to hold signature.
 * @param [in, out] sigSz     On in, length of signature buffer.
 *                            On out, length of signature data.
 * @param [in]      addRnd    Additional random for signature.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key, key's parameters, hash, sig, sigSz or addrnd
 *          is NULL.
 * @return  BAD_FUNC_ARG when ctx is NULL but ctx length is greater than 0.
 * @return  BAD_LENGTH_E when sigSz is less than required signature length, or
 *          when hashSz does not equal the digest size for hashType.
 * @return  MISSING_KEY when private key not set.
 * @return  NOT_COMPILED in when hash algorithm is not supported.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
int wc_SlhDsaKey_SignHashWithRandom(SlhDsaKey* key, const byte* ctx, byte ctxSz,
    const byte* hash, word32 hashSz, enum wc_HashType hashType, byte* sig,
    word32* sigSz, const byte* addRnd)
{
    /* HashSLH-DSA sign with caller-supplied digest. */
    return slhdsakey_signhash_external(key, ctx, ctxSz, hash, hashSz, hashType,
        sig, sigSz, addRnd);
}

/* Generate a HashSLH-DSA signature using an RNG for added randomness.
 *
 * The caller MUST pre-hash the application message with hashType before
 * calling and pass the digest as hash; hashSz must equal the digest size of
 * hashType (32 for SHAKE128, 64 for SHAKE256 per FIPS 205 Section 10.2.2).
 *
 * @param [in]      key     SLH-DSA key.
 * @param [in]      ctx     Context of signing.
 * @param [in]      ctxSz   Length of context in bytes.
 * @param [in]      hash    Pre-hashed message digest to sign.
 * @param [in]      hashSz  Length of digest in bytes.
 * @param [in]      hashType  Hash algorithm used for pre-hash (selects OID).
 * @param [out]     sig     Buffer to hold signature.
 * @param [in, out] sigSz   On in, length of signature buffer.
 *                          On out, length of signature data.
 * @param [in]      rng     Random number generator.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key, key's parameters, hash, sig, sigSz or rng is
 *          NULL.
 * @return  BAD_FUNC_ARG when ctx is NULL but ctx length is greater than 0.
 * @return  BAD_LENGTH_E when hashSz does not equal the digest size for
 *          hashType.
 * @return  MISSING_KEY when private key not set.
 * @return  NOT_COMPILED in when hash algorithm is not supported.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
int wc_SlhDsaKey_SignHash(SlhDsaKey* key, const byte* ctx, byte ctxSz,
    const byte* hash, word32 hashSz, enum wc_HashType hashType, byte* sig,
    word32* sigSz, WC_RNG* rng)
{
    int ret = 0;
    byte addRnd[SLHDSA_MAX_N];

    /* Validate parameters before generating random.
     * hashSz / hashType validation lives in the internal worker and therefore
     * runs after wc_RNG_GenerateBlock. A call with a bad hashSz/hashType will
     * waste n bytes of DRBG output before the error is reported (similar to
     * ML-DSA pre-hash handling). */
    if ((key == NULL) || (key->params == NULL) ||
            ((ctx == NULL) && (ctxSz > 0)) || (hash == NULL) || (sig == NULL) ||
            (sigSz == NULL) || (rng == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    /* Check sig buffer is large enough to hold generated signature. */
    else if (*sigSz < key->params->sigLen) {
        ret = BAD_LENGTH_E;
    }
    /* Check we have a private key to sign with. */
    else if ((key->flags & WC_SLHDSA_FLAG_PRIVATE) == 0) {
        ret = MISSING_KEY;
    }
    /* First sanity check on hashType; the downstream prehash validator does
     * the detailed check for the actual type. */
    else if ((word32)hashType > (word32)WC_HASH_TYPE_MAX) {
        ret = BAD_FUNC_ARG;
    }

#ifdef WOLF_CRYPTO_CB
    if (ret == 0) {
    #ifndef WOLF_CRYPTO_CB_FIND
        if (key->devId != INVALID_DEVID)
    #endif
        {
            ret = wc_CryptoCb_PqcSign(hash, hashSz, sig, sigSz, ctx, ctxSz,
                (word32)hashType, rng, WC_PQC_SIG_TYPE_SLHDSA, key);
            if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE))
                return ret;
            /* fall-through when unavailable */
            ret = 0;
        }
    }
#endif

    if (ret == 0) {
        /* Generate n bytes of random. */
        ret = wc_RNG_GenerateBlock(rng, addRnd, key->params->n);
    }
    if (ret == 0) {
        /* HashSLH-DSA sign with caller-supplied digest. */
        ret = wc_SlhDsaKey_SignHashWithRandom(key, ctx, ctxSz, hash, hashSz,
            hashType, sig, sigSz, addRnd);
    }

    ForceZero(addRnd, sizeof(addRnd));

    return ret;
}
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

/* Verify SLH-DSA signature.
 *
 * FIPS 205. Section 9.3. Algorithm 20.
 * slh_verify_internal(M, SIG, PK)
 *   1: if |SIG| != (1 + k(1 + a) + h + d . len . n then
 *   2:     return false
 *   3: end if
 *  ...
 *   5: R <- SIG.getR()                                             > SIG[0 : n]
 *  ...
 *   8: digest <- Hmsg (R, PK.seed, PK.root, M)         > compute message digest
 *   9: md <- digest [0 : upper(k.a / 8)]           > first upper(k.a / 8) bytes
 * ...
 *
 * FIPS 205. Section 10.3. Algorithm 25.
 * hash_slh_verify(M, SIG, ctx, PH, PK)
 *   1: if |ctx| > 255 then
 *   2:     return false
 *   3: end if
 *   4: switch PH do
 *   5:     case SHA-256:
 *   6:         OID <- toByte(0x0609608648016503040201, 11)
 *                                                      > 2.16.840.1.101.3.4.2.1
 *   7:         PHM <- SHA-256(M)
 *   8:     case SHA-512:
 *   9:         OID <- toByte(0x0609608648016503040203, 11)
 *                                                      > 2.16.840.1.101.3.4.2.3
 *  10:         PHM <- SHA-512(M)
 *  11:     case SHAKE128:
 *  12:         OID <- toByte(0x060960864801650304020B, 11)
 *                                                     > 2.16.840.1.101.3.4.2.11
 *  13:         PHM <- SHAKE128(M, 256)
 *  14:     case SHAKE256:
 *  15:         OID <- toByte(0x060960864801650304020C, 11)
 *                                                     > 2.16.840.1.101.3.4.2.12
 *  16:         PHM <- SHAKE256(M , 512)
 *  17:     case ...                     > other approved hash functions or XOFs
 *  18:         ...
 *  19: end switch
 *  20: M' <- toByte(1, 1) || toByte(|ctx|, 1) || ctx || OID || PHM
 *  21: return slh_verify_internal(M', SIG, PK)
 *
 * The caller MUST pre-hash the application message with hashType before
 * calling and pass the digest as hash; hashSz must equal the digest size of
 * hashType (32 for SHAKE128, 64 for SHAKE256 per FIPS 205 Section 10.2.2).
 *
 * @param [in] key       SLH-DSA key.
 * @param [in] ctx       Context of signing.
 * @param [in] ctxSz     Length of context in bytes.
 * @param [in] hash      Pre-hashed message digest to verify against.
 * @param [in] hashSz    Length of digest in bytes.
 * @param [in] hashType  Hash algorithm used for pre-hash (selects OID).
 * @param [in] sig       Signature data.
 * @param [in] sigSz     Length of signature in bytes.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key, key's parameters, hash or sig is NULL.
 * @return  BAD_FUNC_ARG when ctx is NULL but ctx length is greater than 0.
 * @return  BAD_LENGTH_E when signature size does not match parameters, or
 *          when hashSz does not equal the digest size for hashType.
 * @return  MISSING_KEY when public key not set.
 * @return  NOT_COMPILED in when hash algorithm is not supported.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
int wc_SlhDsaKey_VerifyHash(SlhDsaKey* key, const byte* ctx, byte ctxSz,
    const byte* hash, word32 hashSz, enum wc_HashType hashType, const byte* sig,
    word32 sigSz)
{
    int ret = 0;
    const byte* oid = NULL;
    byte oidLen = 0;

    /* Validate parameters. */
    if ((key == NULL) || (key->params == NULL) ||
            ((ctx == NULL) && (ctxSz > 0)) || (hash == NULL) || (sig == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    /* Alg 20, Step 1: Check signature length is the expect length. */
    else if (sigSz != key->params->sigLen) {
        /* Alg 20, Step 2: Return error  */
        ret = BAD_LENGTH_E;
    }
    /* Check we have a public key to verify with. */
    else if ((key->flags & WC_SLHDSA_FLAG_PUBLIC) == 0) {
        ret = MISSING_KEY;
    }
    /* First sanity check on hashType; the downstream prehash validator does
     * the detailed check for the actual type. */
    else if ((word32)hashType > (word32)WC_HASH_TYPE_MAX) {
        ret = BAD_FUNC_ARG;
    }

#ifdef WOLF_CRYPTO_CB
    if (ret == 0) {
    #ifndef WOLF_CRYPTO_CB_FIND
        if (key->devId != INVALID_DEVID)
    #endif
        {
            int res = 0;
            ret = wc_CryptoCb_PqcVerify(sig, sigSz, hash, hashSz, ctx, ctxSz,
                (word32)hashType, &res, WC_PQC_SIG_TYPE_SLHDSA, key);
            if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) {
                if (ret != 0)
                    return ret;
                return (res == 1) ? 0 : SIG_VERIFY_E;
            }
            /* fall-through when unavailable */
            ret = 0;
        }
    }
#endif

    if (ret == 0) {
        /* Alg 25, Steps 4-19: Validate caller-supplied pre-hashed digest length
         * and select OID for the chosen hash algorithm. */
        ret = slhdsakey_validate_prehash(hashSz, hashType, &oid, &oidLen);
    }
    if (ret == 0) {
        byte n = key->params->n;
        byte md[SLHDSA_MAX_MD];
        byte hdr[2];

        /* Alg 25, Step 20: Make M' header. */
        hdr[0] = 1;
        hdr[1] = ctxSz;

#ifdef WOLFSSL_SLHDSA_SHA2
        if (SLHDSA_IS_SHA2(key->params->param)) {
            /* SHA2: Build oid||hash as message for H_msg. */
            byte phMsg[SLHDSA_PHMSG_MAX_LEN];
            word32 phMsgLen = (word32)oidLen + hashSz;

            XMEMCPY(phMsg, oid, oidLen);
            XMEMCPY(phMsg + oidLen, hash, hashSz);

            ret = slhdsakey_h_msg_sha2(key, sig, hdr, ctx, ctxSz, phMsg,
                phMsgLen, md, (word32)key->params->dl1 + key->params->dl2 +
                key->params->dl3);
        }
        else
#endif
        {
            /* SHAKE: H_msg streaming. */
            ret = slhdsakey_hash_start(&key->hash.shk.shake, sig, n);
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake,
                    key->sk + 2U * n, 2U * n);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, hdr,
                    sizeof(hdr));
            }
            if ((ret == 0) && (ctxSz > 0)) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, ctx, ctxSz);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, oid, oidLen);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_update(&key->hash.shk.shake, hash, hashSz);
            }
            if (ret == 0) {
                ret = slhdsakey_hash_final(&key->hash.shk.shake, md,
                    (word32)key->params->dl1 + key->params->dl2 +
                    key->params->dl3);
            }
        }
        if (ret == 0) {
            /* Alg 25, Step 21: Verify M'.
             * Alg 20, Steps 4,6-18: Verify digest. */
            ret = slhdsakey_verify(key, md, sig);
        }
    }

    return ret;
}

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
/* Import private key from data.
 *
 * Includes the public key.
 *
 * @param [in] key      SLH-DSA key.
 * @param [in] priv     Private key data.
 * @param [in] privLen  Length of private key data in bytes.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key, key's parameters or priv is NULL.
 * @return  BAD_LENGTH_E when inLen does not match parameters.
 */
int wc_SlhDsaKey_ImportPrivate(SlhDsaKey* key, const byte* priv, word32 privLen)
{
    int ret = 0;

    /* Validate parameters. */
    if ((key == NULL) || (key->params == NULL) || (priv == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    /* Check private key data length matches parameters. */
    else if ((privLen != 4 * key->params->n)) {
        ret = BAD_LENGTH_E;
    }
    else {
        /* Copy private and public key data into SLH-DSA key object. */
        XMEMCPY(key->sk, priv, 4U * key->params->n);
        key->flags = WC_SLHDSA_FLAG_BOTH_KEYS;
#ifdef WOLFSSL_SLHDSA_SHA2
        if (SLHDSA_IS_SHA2(key->params->param)) {
            ret = slhdsakey_precompute_sha2_midstates(key);
        }
#endif
    }

    return ret;
}
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

/* Import public key from data.
 *
 * @param [in] key     SLH-DSA key.
 * @param [in] pub     Public key data.
 * @param [in] pubLen  Length of public key data in bytes.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key, key's parameters or in is NULL.
 * @return  BAD_LENGTH_E when inLen does not match parameters.
 */
int wc_SlhDsaKey_ImportPublic(SlhDsaKey* key, const byte* pub, word32 pubLen)
{
    int ret = 0;

    /* Validate parameters. */
    if ((key == NULL) || (key->params == NULL) || (pub == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    /* Check public key data length matches parameters. */
    else if ((pubLen != 2 * key->params->n)) {
        ret = BAD_LENGTH_E;
    }
    else {
        /* Copy public key data into SLH-DSA key object. */
        XMEMCPY(key->sk + 2U * key->params->n, pub, 2U * key->params->n);
        key->flags |= WC_SLHDSA_FLAG_PUBLIC;
#ifdef WOLFSSL_SLHDSA_SHA2
        if (SLHDSA_IS_SHA2(key->params->param)) {
            ret = slhdsakey_precompute_sha2_midstates(key);
        }
#endif
    }

    return ret;
}

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
/* Check that the private key is valid.
 *
 * @param [in] key  SLH-DSA key.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key or key's parameters is NULL.
 * @return  MISSING_KEY when private key not set.
 * @return  WC_KEY_MISMATCH_E when private key and public seed don't compute
 *          public key root.
 * @return  MEMORY_E on dynamic memory allocation failure.
 * @return  SHAKE-256 error return code on digest failure.
 */
int wc_SlhDsaKey_CheckKey(SlhDsaKey* key)
{
    int ret = 0;

    /* Validate parameter. */
    if ((key == NULL) || (key->params == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    /* Check we have a private key to validate. */
    else if ((key->flags & WC_SLHDSA_FLAG_PRIVATE) == 0) {
        ret = MISSING_KEY;
    }
    if (ret == 0) {
        byte root[SLHDSA_MAX_N];
        byte n = key->params->n;

        /* Cache the public key root as making the key overwrites. */
        XMEMCPY(root, key->sk + 3 * n, n);
        ret = wc_SlhDsaKey_MakeKeyWithRandom(key, key->sk, n, key->sk + n, n,
                key->sk + 2 * n, n);
        /* Compare computed root with what was cached. */
        if ((ret == 0) && (XMEMCMP(root, key->sk + 3 * n, n) != 0)) {
            ret = WC_KEY_MISMATCH_E;
        }
    }

    return ret;
}

/* Export the private key.
 *
 * Includes the public key.
 *
 * @param [in]       key      SLH-DSA key.
 * @param [out]      priv     Buffer for private key data.
 * @param [in, out]  privLen  On in, length of buffer.
 *                            On out, length of private key.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key, key's parameters, priv or privLen is NULL.
 * @return  BAD_LENGTH_E when privLen is too small for private key.
 */
int wc_SlhDsaKey_ExportPrivate(SlhDsaKey* key, byte* priv, word32* privLen)
{
    int ret = 0;

    /* Validate parameters. */
    if ((key == NULL) || (key->params == NULL) || (priv == NULL) ||
            (privLen == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    /* Check private key buffer length. */
    else if (*privLen < key->params->n * 4) {
        ret = BAD_LENGTH_E;
    }
    else {
        word32 n = (word32)key->params->n;

        /* Copy data out and return length. */
        XMEMCPY(priv, key->sk, n * 4U);
        *privLen = n * 4U;
    }

    return ret;
}
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

/* Export the public key.
 *
 * @param [in]       key     SLH-DSA key.
 * @param [out]      pub     Buffer for public key data.
 * @param [in, out]  pubLen  On in, length of buffer.
 *                           On out, length of public key.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when key, key's parameters, pub or pubLen is NULL.
 * @return  BAD_LENGTH_E when pubLen is too small for public key.
 */
int wc_SlhDsaKey_ExportPublic(SlhDsaKey* key, byte* pub, word32* pubLen)
{
    int ret = 0;

    /* Validate parameters. */
    if ((key == NULL) || (key->params == NULL) || (pub == NULL) ||
            (pubLen == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    /* Check public key buffer length. */
    else if (*pubLen < key->params->n * 2) {
        ret = BAD_LENGTH_E;
    }
    else {
        word32 n = (word32)key->params->n;

        /* Copy data out and return length. */
        XMEMCPY(pub, key->sk + n * 2U, n * 2U);
        *pubLen = n * 2U;
    }

    return ret;
}

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
/* Return the size of the private key for the parameters.
 *
 * @param [in] key  SLH-DSA key.
 * @return  Private key data length in bytes on success.
 * @return  BAD_FUNC_ARG when key or key's parameters is NULL.
 */
int wc_SlhDsaKey_PrivateSize(SlhDsaKey* key)
{
    int ret;

    /* Validate parameters. */
    if ((key == NULL) || (key->params == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    else {
        /* Length is of 3 seeds and a hash, all n bytes long.  */
        ret = key->params->n * 4;
    }

    return ret;
}
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

/* Return the size of the public key for the parameters.
 *
 * @param [in] key  SLH-DSA key.
 * @return  Public key data length in bytes on success.
 * @return  BAD_FUNC_ARG when key or key's parameters is NULL.
 */
int wc_SlhDsaKey_PublicSize(SlhDsaKey* key)
{
    int ret;

    /* Validate parameters. */
    if ((key == NULL) || (key->params == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    else {
        /* Length is of a seed and a hash, both n bytes long.  */
        ret = key->params->n * 2;
    }

    return ret;
}

/* Return the size of a signature for the parameters.
 *
 * @param [in] key  SLH-DSA key.
 * @return  Signature length in bytes on success.
 * @return  BAD_FUNC_ARG when key or key's parameters is NULL.
 */
int wc_SlhDsaKey_SigSize(SlhDsaKey* key)
{
    int ret;

    /* Validate parameters. */
    if ((key == NULL) || (key->params == NULL)) {
        ret = BAD_FUNC_ARG;
    }
    else {
        /* Length from the parameters. */
        ret = (int)key->params->sigLen;
    }

    return ret;
}

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
/* Return the size of the private key for the parameters.
 *
 * @param [in] param  SLH-DSA parameters.
 * @return  Private key data length in bytes on success.
 * @return  NOT_COMPILED_IN when parameters not supported.
 */
int wc_SlhDsaKey_PrivateSizeFromParam(enum SlhDsaParam param)
{
    int ret;

    switch (param) {
        case SLHDSA_SHAKE128S:
            ret = WC_SLHDSA_SHAKE128S_PRIV_LEN;
            break;
        case SLHDSA_SHAKE128F:
            ret = WC_SLHDSA_SHAKE128F_PRIV_LEN;
            break;
        case SLHDSA_SHAKE192S:
            ret = WC_SLHDSA_SHAKE192S_PRIV_LEN;
            break;
        case SLHDSA_SHAKE192F:
            ret = WC_SLHDSA_SHAKE192F_PRIV_LEN;
            break;
        case SLHDSA_SHAKE256S:
            ret = WC_SLHDSA_SHAKE256S_PRIV_LEN;
            break;
        case SLHDSA_SHAKE256F:
            ret = WC_SLHDSA_SHAKE256F_PRIV_LEN;
            break;
#ifdef WOLFSSL_SLHDSA_SHA2
        case SLHDSA_SHA2_128S:
            ret = WC_SLHDSA_SHA2_128S_PRIV_LEN;
            break;
        case SLHDSA_SHA2_128F:
            ret = WC_SLHDSA_SHA2_128F_PRIV_LEN;
            break;
        case SLHDSA_SHA2_192S:
            ret = WC_SLHDSA_SHA2_192S_PRIV_LEN;
            break;
        case SLHDSA_SHA2_192F:
            ret = WC_SLHDSA_SHA2_192F_PRIV_LEN;
            break;
        case SLHDSA_SHA2_256S:
            ret = WC_SLHDSA_SHA2_256S_PRIV_LEN;
            break;
        case SLHDSA_SHA2_256F:
            ret = WC_SLHDSA_SHA2_256F_PRIV_LEN;
            break;
#endif
        default:
            ret = NOT_COMPILED_IN;
            break;
    }

    return ret;
}
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

/* Return the size of the public key for the parameters.
 *
 * @param [in] param  SLH-DSA parameters.
 * @return  Public key data length in bytes on success.
 * @return  NOT_COMPILED_IN when parameters not supported.
 */
int wc_SlhDsaKey_PublicSizeFromParam(enum SlhDsaParam param)
{
    int ret;

    switch (param) {
        case SLHDSA_SHAKE128S:
            ret = WC_SLHDSA_SHAKE128S_PUB_LEN;
            break;
        case SLHDSA_SHAKE128F:
            ret = WC_SLHDSA_SHAKE128F_PUB_LEN;
            break;
        case SLHDSA_SHAKE192S:
            ret = WC_SLHDSA_SHAKE192S_PUB_LEN;
            break;
        case SLHDSA_SHAKE192F:
            ret = WC_SLHDSA_SHAKE192F_PUB_LEN;
            break;
        case SLHDSA_SHAKE256S:
            ret = WC_SLHDSA_SHAKE256S_PUB_LEN;
            break;
        case SLHDSA_SHAKE256F:
            ret = WC_SLHDSA_SHAKE256F_PUB_LEN;
            break;
#ifdef WOLFSSL_SLHDSA_SHA2
        case SLHDSA_SHA2_128S:
            ret = WC_SLHDSA_SHA2_128S_PUB_LEN;
            break;
        case SLHDSA_SHA2_128F:
            ret = WC_SLHDSA_SHA2_128F_PUB_LEN;
            break;
        case SLHDSA_SHA2_192S:
            ret = WC_SLHDSA_SHA2_192S_PUB_LEN;
            break;
        case SLHDSA_SHA2_192F:
            ret = WC_SLHDSA_SHA2_192F_PUB_LEN;
            break;
        case SLHDSA_SHA2_256S:
            ret = WC_SLHDSA_SHA2_256S_PUB_LEN;
            break;
        case SLHDSA_SHA2_256F:
            ret = WC_SLHDSA_SHA2_256F_PUB_LEN;
            break;
#endif
        default:
            ret = NOT_COMPILED_IN;
            break;
    }

    return ret;
}

/* Return the size of a signature for the parameters.
 *
 * @param [in] param  SLH-DSA parameters.
 * @return  Signature length in bytes on success.
 * @return  NOT_COMPILED_IN when parameters not supported.
 */
int wc_SlhDsaKey_SigSizeFromParam(enum SlhDsaParam param)
{
    int ret;

    switch (param) {
        case SLHDSA_SHAKE128S:
            ret = WC_SLHDSA_SHAKE128S_SIG_LEN;
            break;
        case SLHDSA_SHAKE128F:
            ret = WC_SLHDSA_SHAKE128F_SIG_LEN;
            break;
        case SLHDSA_SHAKE192S:
            ret = WC_SLHDSA_SHAKE192S_SIG_LEN;
            break;
        case SLHDSA_SHAKE192F:
            ret = WC_SLHDSA_SHAKE192F_SIG_LEN;
            break;
        case SLHDSA_SHAKE256S:
            ret = WC_SLHDSA_SHAKE256S_SIG_LEN;
            break;
        case SLHDSA_SHAKE256F:
            ret = WC_SLHDSA_SHAKE256F_SIG_LEN;
            break;
#ifdef WOLFSSL_SLHDSA_SHA2
        case SLHDSA_SHA2_128S:
            ret = WC_SLHDSA_SHA2_128S_SIG_LEN;
            break;
        case SLHDSA_SHA2_128F:
            ret = WC_SLHDSA_SHA2_128F_SIG_LEN;
            break;
        case SLHDSA_SHA2_192S:
            ret = WC_SLHDSA_SHA2_192S_SIG_LEN;
            break;
        case SLHDSA_SHA2_192F:
            ret = WC_SLHDSA_SHA2_192F_SIG_LEN;
            break;
        case SLHDSA_SHA2_256S:
            ret = WC_SLHDSA_SHA2_256S_SIG_LEN;
            break;
        case SLHDSA_SHA2_256F:
            ret = WC_SLHDSA_SHA2_256F_SIG_LEN;
            break;
#endif
        default:
            ret = NOT_COMPILED_IN;
            break;
    }

    return ret;
}

/* Find SlhDsaParameters entry for a given param enum. */
static const SlhDsaParameters* slhdsa_find_params(enum SlhDsaParam param)
{
    int i;
    for (i = 0; i < SLHDSA_PARAM_LEN; i++) {
        if (SlhDsaParams[i].param == param) {
            return &SlhDsaParams[i];
        }
    }
    return NULL;
}

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
/* Decode a DER-encoded SLH-DSA private key (PKCS#8 / OneAsymmetricKey).
 *
 * RFC 9909 Section 6: The privateKey OCTET STRING contains the raw
 * concatenation SK.seed || SK.prf || PK.seed || PK.root (4*n bytes)
 * directly, without a nested OCTET STRING wrapper. This differs from
 * Ed25519/Ed448 which wrap the key in an additional OCTET STRING.
 *
 * The parameter set is detected from the AlgorithmIdentifier OID.
 * On success, key->params is updated to match the detected parameter set.
 *
 * @param [in]      input     DER-encoded key data.
 * @param [in, out] inOutIdx  Index into input, updated on return.
 * @param [in, out] key       SLH-DSA key. Parameter set is auto-detected.
 * @param [in]      inSz      Size of input in bytes.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when input, inOutIdx, or key is NULL.
 * @return  ASN_PARSE_E when the DER cannot be parsed as an SLH-DSA key.
 */
int wc_SlhDsaKey_PrivateKeyDecode(const byte* input, word32* inOutIdx,
    SlhDsaKey* key, word32 inSz)
{
    int ret = 0;
    int length;
    int version;
    word32 oid = 0;
    word32 seqEnd;
    word32 savedIdx;
    int privSz;
    int paramId;
    const SlhDsaParameters* params;

    if ((input == NULL) || (inOutIdx == NULL) || (key == NULL) || (inSz == 0)) {
        return BAD_FUNC_ARG;
    }

    /* Snapshot the caller's index so failures restore it -- mirrors
     * wc_SlhDsaKey_PublicKeyDecode and lets callers chain parsers or
     * retry on the same buffer without recomputing the offset. */
    savedIdx = *inOutIdx;

    /* Parse PKCS#8 OneAsymmetricKey wrapper:
     * SEQUENCE { version, AlgorithmIdentifier { OID }, OCTET STRING { key },
     *            [0] attributes OPTIONAL, [1] publicKey OPTIONAL }
     */
    if (GetSequence(input, inOutIdx, &length, inSz) < 0) {
        *inOutIdx = savedIdx;
        return ASN_PARSE_E;
    }
    seqEnd = *inOutIdx + (word32)length;

    if (GetMyVersion(input, inOutIdx, &version, inSz) < 0) {
        *inOutIdx = savedIdx;
        return ASN_PARSE_E;
    }
    if (version != 0 && version != 1) {
        *inOutIdx = savedIdx;
        return ASN_PARSE_E;
    }

    if (GetAlgoId(input, inOutIdx, &oid, oidKeyType, inSz) < 0) {
        *inOutIdx = savedIdx;
        return ASN_PARSE_E;
    }

    /* Map the OID to an SLH-DSA parameter set.  Pass through NOT_COMPILED_IN
     * so callers can distinguish "variant present but not built in" from
     * "malformed DER". */
    paramId = wc_SlhDsaOidToParam((int)oid);
    if (paramId == WC_NO_ERR_TRACE(NOT_COMPILED_IN)) {
        *inOutIdx = savedIdx;
        return NOT_COMPILED_IN;
    }
    if (paramId < 0) {
        *inOutIdx = savedIdx;
        return ASN_PARSE_E;
    }
    params = slhdsa_find_params((enum SlhDsaParam)paramId);
    if (params == NULL) {
        *inOutIdx = savedIdx;
        return ASN_PARSE_E;
    }

    /* RFC 9909: privateKey is a single OCTET STRING containing the raw key
     * (4*n bytes). Unlike Ed25519/Ed448, there is no nested inner OCTET
     * STRING wrapping. */
    if (GetOctetString(input, inOutIdx, &privSz, inSz) < 0) {
        *inOutIdx = savedIdx;
        return ASN_PARSE_E;
    }

    if (privSz != params->n * 4) {
        *inOutIdx = savedIdx;
        return ASN_PARSE_E;
    }

    {
        const SlhDsaParameters* oldParams = key->params;
        int oldFlags = (int)key->flags;

        /* Update the key's parameter set to the detected one. */
        key->params = params;

        /* Import the raw private key: SK.seed || SK.prf || PK.seed || PK.root */
        ret = wc_SlhDsaKey_ImportPrivate(key, input + *inOutIdx,
                                         (word32)privSz);
        if (ret == 0) {
            /* Validate trailing fields per RFC 5958 OneAsymmetricKey:
             *   [0] IMPLICIT Attributes  OPTIONAL  -- at most once
             *   [1] IMPLICIT PublicKey   OPTIONAL  -- at most once,
             *                                        must follow [0]
             * Reject duplicates, out-of-order tags, and any other tag.
             * The previous code accepted any number of either tag in any
             * order. */
            const byte tagAttrs = ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 0;
            const byte tagPub   = ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 1;
            int seenAttrs = 0;
            int seenPub   = 0;
            *inOutIdx += (word32)privSz;
            while (ret == 0 && *inOutIdx < seqEnd) {
                byte tlvTag;
                int tlvLen;
                if (GetASNTag(input, inOutIdx, &tlvTag, inSz) < 0) {
                    ret = ASN_PARSE_E;
                    break;
                }
                if (tlvTag == tagAttrs) {
                    /* attributes must precede publicKey and appear once */
                    if (seenAttrs || seenPub) {
                        ret = ASN_PARSE_E;
                        break;
                    }
                    seenAttrs = 1;
                }
                else if (tlvTag == tagPub) {
                    /* publicKey may appear at most once */
                    if (seenPub) {
                        ret = ASN_PARSE_E;
                        break;
                    }
                    seenPub = 1;
                }
                else {
                    ret = ASN_PARSE_E;
                    break;
                }
                if (GetLength(input, inOutIdx, &tlvLen, inSz) < 0) {
                    ret = ASN_PARSE_E;
                    break;
                }
                /* Length must stay within the outer SEQUENCE. */
                if (*inOutIdx + (word32)tlvLen > seqEnd) {
                    ret = ASN_PARSE_E;
                    break;
                }
                *inOutIdx += (word32)tlvLen;
            }
            if (ret == 0 && *inOutIdx != seqEnd) {
                ret = ASN_PARSE_E;
            }
            if (ret != 0) {
                /* Trailing-field validation failed after ImportPrivate
                 * already populated key->sk. Scrub the imported material
                 * and roll back state so the caller sees the failure as
                 * if the import never happened. Clear FLAG_BOTH_KEYS from
                 * the restored flags since we just zeroed the bytes those
                 * flags would claim. */
                ForceZero(key->sk, (word32)(4 * params->n));
                key->params = oldParams;
                key->flags = oldFlags & ~((int)WC_SLHDSA_FLAG_BOTH_KEYS);
                *inOutIdx = savedIdx;
            }
        }
        else {
            /* On failure, restore params/flags. ImportPrivate writes the
             * full sk[0..4*n] (private + public material) before any
             * SHA-2 precompute step, so a precompute failure can leave
             * the entire sk dirty -- clear it and clear the matching
             * flags so flags can never claim valid bytes that we zeroed.
             * BAD_LENGTH_E is detected before any write, so no zeroing
             * (or flag scrubbing) is needed in that case. */
            if (ret != WC_NO_ERR_TRACE(BAD_LENGTH_E)) {
                ForceZero(key->sk, (word32)(4 * params->n));
                key->flags = oldFlags & ~((int)WC_SLHDSA_FLAG_BOTH_KEYS);
            }
            else {
                key->flags = oldFlags;
            }
            key->params = oldParams;
            *inOutIdx = savedIdx;
        }
    }

    return ret;
}
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */

/* Decode a DER-encoded SLH-DSA public key (SubjectPublicKeyInfo).
 *
 * The parameter set is detected from the AlgorithmIdentifier OID.
 * On success, key->params is updated to match the detected parameter set.
 *
 * @param [in]      input     DER-encoded key data.
 * @param [in, out] inOutIdx  Index into input, updated on return.
 * @param [in, out] key       SLH-DSA key. Parameter set is auto-detected.
 * @param [in]      inSz      Size of input in bytes.
 * @return  0 on success.
 * @return  BAD_FUNC_ARG when input, inOutIdx, or key is NULL.
 * @return  ASN_PARSE_E when the DER cannot be parsed as an SLH-DSA key.
 */
int wc_SlhDsaKey_PublicKeyDecode(const byte* input, word32* inOutIdx,
    SlhDsaKey* key, word32 inSz)
{
    int ret;
    int keytype = ANONk;
    int paramId;
    const SlhDsaParameters* params;
    const SlhDsaParameters* oldParams;
    const byte* pubKeyPtr = NULL;
    word32 pubKeyLen = 0;
    word32 savedIdx;
    int oldFlags;

    if ((input == NULL) || (inOutIdx == NULL) || (key == NULL) || (inSz == 0)) {
        return BAD_FUNC_ARG;
    }

    savedIdx = *inOutIdx;

    /* Fast path: if the caller initialised the key with a parameter set,
     * treat the entire window from *inOutIdx to inSz as a candidate raw
     * public key and let wc_SlhDsaKey_ImportPublic decide via its length
     * check. The window must contain exactly 2*n bytes for the configured
     * parameter set -- callers chaining decoders must pass inSz scoped to
     * just the public-key buffer or the import will reject the length and
     * fall through to SPKI parsing. Mirrors the raw-first fallback in
     * wc_MlDsaKey_PublicKeyDecode and wc_Falcon_PublicKeyDecode so all PQ
     * public-key decoders accept either raw bytes or SPKI.
     *
     * The length check in ImportPublic is the disambiguator: a real SPKI
     * for any SLH-DSA variant carries ~19 bytes of AlgorithmIdentifier and
     * BIT STRING overhead on top of the 2*n public bytes, so SPKI input
     * never collides with the 2*n raw length and falls through cleanly. */
    if (key->params != NULL && savedIdx < inSz) {
        word32 windowSz = inSz - savedIdx;
        int n = key->params->n;
        oldFlags = key->flags;
        ret = wc_SlhDsaKey_ImportPublic(key, input + savedIdx, windowSz);
        if (ret == 0) {
            *inOutIdx += windowSz;
            return 0;
        }
        /* Fall through to SPKI parsing. BAD_LENGTH_E is detected before
         * any write (typical SPKI input), so there is nothing to scrub.
         * On SHA-2 precompute failure ImportPublic has written only the
         * public half at sk[2*n .. 4*n] - leave the private half
         * sk[0 .. 2*n] untouched in case the caller imported it earlier.
         * When we do scrub the public half, also clear FLAG_PUBLIC from
         * the restored flags so flags cannot claim a public key over the
         * zeroed bytes (the caller may have had FLAG_PUBLIC set from a
         * prior import). */
        if (ret != WC_NO_ERR_TRACE(BAD_LENGTH_E)) {
            ForceZero(key->sk + 2 * n, (word32)(2 * n));
            key->flags = oldFlags & ~((int)WC_SLHDSA_FLAG_PUBLIC);
        }
        else {
            key->flags = oldFlags;
        }
    }

    /* Use ANONk to auto-detect the OID from the SPKI AlgorithmIdentifier
     * in a single parse. (PrivateKeyDecode parses each DER element
     * manually because the PKCS#8 OneAsymmetricKey layout differs from
     * SPKI and has no matching helper.) */
    ret = DecodeAsymKeyPublic_Assign(input, inOutIdx, inSz, &pubKeyPtr,
                                     &pubKeyLen, &keytype);
    if (ret != 0) {
        return ret;
    }

    /* Map the detected OID key type to an SLH-DSA parameter set.  Pass
     * through NOT_COMPILED_IN so callers see the specific reason
     * (unsupported variant) rather than a generic parse error. */
    paramId = wc_SlhDsaOidToParam(keytype);
    if (paramId == WC_NO_ERR_TRACE(NOT_COMPILED_IN)) {
        *inOutIdx = savedIdx;
        return NOT_COMPILED_IN;
    }
    if (paramId < 0) {
        *inOutIdx = savedIdx;
        return ASN_PARSE_E;
    }
    params = slhdsa_find_params((enum SlhDsaParam)paramId);
    if (params == NULL) {
        *inOutIdx = savedIdx;
        return ASN_PARSE_E;
    }

    oldFlags = key->flags;
    oldParams = key->params;
    key->params = params;
    ret = wc_SlhDsaKey_ImportPublic(key, pubKeyPtr, pubKeyLen);
    if (ret != 0) {
        /* Restore params/flags/inOutIdx. ImportPublic writes only the
         * public half (sk[2*n .. 4*n]) and only after the length check
         * passes; preserve any prior private bytes the caller may have
         * imported into sk[0 .. 2*n]. When we scrub the public half on
         * a post-write failure, also clear FLAG_PUBLIC from the restored
         * flags so flags cannot claim a public key over the zeroed bytes
         * (the caller may have had FLAG_PUBLIC set from a prior import). */
        if (ret != WC_NO_ERR_TRACE(BAD_LENGTH_E)) {
            ForceZero(key->sk + 2 * params->n, (word32)(2 * params->n));
            key->flags = oldFlags & ~((int)WC_SLHDSA_FLAG_PUBLIC);
        }
        else {
            key->flags = oldFlags;
        }
        key->params = oldParams;
        *inOutIdx = savedIdx;
    }

    return ret;
}

#ifdef WC_ENABLE_ASYM_KEY_EXPORT
/* Encode an SLH-DSA public key to DER.
 *
 * Pass NULL for output to get the size of the encoding.
 *
 * @param [in]  key       SLH-DSA key object.
 * @param [out] output    Buffer to put encoded data in.
 * @param [in]  inLen     Size of buffer in bytes.
 * @param [in]  withAlg   Whether to use SubjectPublicKeyInfo format.
 * @return  Size of encoded data in bytes on success.
 * @return  BAD_FUNC_ARG when key/key->params is NULL or param is unknown.
 * @return  NOT_COMPILED_IN when key->params names a known SLH-DSA variant
 *          whose parameter set isn't compiled in. In practice unreachable
 *          because SlhDsaParams[] is itself gated on the build, but the
 *          contract matches wc_SlhDsaOidToParam for forward compatibility.
 */
int wc_SlhDsaKey_PublicKeyToDer(SlhDsaKey* key, byte* output, word32 inLen,
    int withAlg)
{
    int ret;
    byte pubKey[WC_SLHDSA_MAX_PUB_LEN];
    word32 pubKeyLen = (word32)sizeof(pubKey);
    int keytype;

    if ((key == NULL) || (key->params == NULL)) {
        return BAD_FUNC_ARG;
    }

    keytype = wc_SlhDsaParamToOid(key->params->param);
    if (keytype < 0) {
        return keytype;
    }

    ret = wc_SlhDsaKey_ExportPublic(key, pubKey, &pubKeyLen);
    if (ret == 0) {
        ret = SetAsymKeyDerPublic(pubKey, pubKeyLen, output, inLen, keytype,
                                  withAlg);
    }

    return ret;
}

#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
/* Encode an SLH-DSA private key to DER (PKCS#8 / OneAsymmetricKey).
 *
 * RFC 9909: The privateKey OCTET STRING contains the raw 4*n bytes
 * (SK.seed || SK.prf || PK.seed || PK.root) directly, without a nested
 * OCTET STRING wrapper. This differs from Ed25519/Ed448 which use a
 * double OCTET STRING wrapping.
 *
 * Pass NULL for output to get the required buffer size.
 *
 * @param [in]  key       SLH-DSA key object.
 * @param [out] output    Buffer to put encoded data in (or NULL for size).
 * @param [in]  inLen     Size of buffer in bytes.
 * @return  Size of encoded data in bytes on success.
 * @return  BAD_FUNC_ARG when key/key->params is NULL or param is unknown.
 * @return  NOT_COMPILED_IN when key->params names a known SLH-DSA variant
 *          whose parameter set isn't compiled in (in practice unreachable;
 *          SlhDsaParams[] is itself gated on the build).
 * @return  MISSING_KEY when private key not set.
 * @return  BUFFER_E when output buffer is too small.
 * @return  ASN_PARSE_E when SetMyVersion returns an unexpected size
 *          (internal encoder consistency check).
 */
int wc_SlhDsaKey_KeyToDer(SlhDsaKey* key, byte* output, word32 inLen)
{
    int keytype;
    int n;
    word32 privSz, algoSz, verSz, seqSz, sz;

    if ((key == NULL) || (key->params == NULL)) {
        return BAD_FUNC_ARG;
    }
    if ((key->flags & WC_SLHDSA_FLAG_PRIVATE) == 0) {
        return MISSING_KEY;
    }

    keytype = wc_SlhDsaParamToOid(key->params->param);
    if (keytype < 0) {
        return keytype;
    }

    n = key->params->n;
    /* RFC 9909: bare OCTET STRING containing 4*n raw key bytes */
    privSz = SetOctetString((word32)(n * 4), NULL) + (word32)(n * 4);
    algoSz = SetAlgoID(keytype, NULL, oidKeyType, 0);
    verSz  = 3; /* ASN_INTEGER(1) + length(1) + version_byte(1) */
    seqSz  = SetSequence(verSz + algoSz + privSz, NULL);
    sz     = seqSz + verSz + algoSz + privSz;

    if (output == NULL) {
        return (int)sz;
    }
    if (sz > inLen) {
        return BUFFER_E;
    }

    {
        word32 idx = 0;
        int actualVerSz;
        idx += SetSequence(verSz + algoSz + privSz, output + idx);
        actualVerSz = SetMyVersion(0, output + idx, FALSE);
        if (actualVerSz != (int)verSz) {
            /* Internal consistency: if SetMyVersion ever returns a size
             * different from the verSz we used to compute the total,
             * something in the encoder changed -- this is not a caller
             * buffer-size issue, so report it as an ASN encoding error. */
            return ASN_PARSE_E;
        }
        idx += (word32)actualVerSz;
        idx += SetAlgoID(keytype, output + idx, oidKeyType, 0);
        idx += SetOctetString((word32)(n * 4), output + idx);
        XMEMCPY(output + idx, key->sk, (word32)(n * 4));
        idx += (word32)(n * 4);
        return (int)idx;
    }
}

/* Encode an SLH-DSA private key to DER (PKCS#8 / OneAsymmetricKey).
 *
 * For SLH-DSA, RFC 9909 packs SK.seed || SK.prf || PK.seed || PK.root into
 * a single OCTET STRING, so there is no separate "private-only" encoding.
 * This function is intentionally an alias of wc_SlhDsaKey_KeyToDer, kept
 * for API parity with Ed25519/Ed448 which do have a distinct private form.
 *
 * @param [in]  key       SLH-DSA key object.
 * @param [out] output    Buffer to put encoded data in (or NULL for size).
 * @param [in]  inLen     Size of buffer in bytes.
 * @return  Size of encoded data in bytes on success.
 * @return  BAD_FUNC_ARG when key is NULL.
 * @return  MISSING_KEY when private key not set.
 * @return  BUFFER_E when output buffer is too small.
 */
int wc_SlhDsaKey_PrivateKeyToDer(SlhDsaKey* key, byte* output, word32 inLen)
{
    return wc_SlhDsaKey_KeyToDer(key, output, inLen);
}
#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */
#endif /* WC_ENABLE_ASYM_KEY_EXPORT */

#endif /* WOLFSSL_HAVE_SLHDSA */
