From 527c938e79a0d40e2c55da0368291a10fef09e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 18 Aug 2026 09:02:58 +0000 Subject: [PATCH 1/4] Report availability of wc_Sha256HashBlock with a macro The prototype in sha256.h was guarded by WOLFSSL_HAVE_LMS together with WOLFSSL_LMS_FULL_HASH, and nothing in the tree ever defines the latter. The definition, meanwhile, is only compiled in the arm that selects a software transform. On a port that supplies its own Update and Final, LMS therefore declared and called a function that was never built. Add WOLFSSL_HAVE_SHA256_HASH_BLOCK to sha256.h, listing in one place the ports where the helper is absent, and use it for both the prototype and the definition. The list is negative so that naming a port that does have the transform only costs the fast path, while a build combining two ports falls back instead of guessing. A compile time check in sha256.c catches the list drifting away from the arms that select XTRANSFORM. The list tests the macros that gate each port's arm in sha256.c, not the ones a port defines inside its own .c file (MAX3266X_SHA, the ESP32 and Renesas hash switches), since those read as undefined in the header and would exclude nothing. A FIPS v1 build skips the whole region, which leaves the macro undefined and the callers on the streaming path. ESP32 hardware hashing does build the software transform, but in hardware mode Update feeds the accelerator and leaves sha256->digest stale, so a block compressed onto it starts from the wrong state. It is listed as well, which moves LMS on those builds to the full hash API. Move every in-tree caller onto the new macro in the same commit: the wolfCrypt and API test suites and the MC/DC hash fault injector all gated on WOLFSSL_LMS_FULL_HASH too, so a build without raw hash access kept calling a function that was not compiled. WOLFSSL_LMS_FULL_HASH is now referenced nowhere and leaves the known macro list. The wolfCrypt test for the helper is renamed, as it is no longer LMS specific. LMS now falls back to the full hash API when the helper is missing. A new CI config builds LMS, XMSS and SLH-DSA with WOLFSSL_NO_HASH_RAW, so a caller of the unbuilt helper fails to link. --- .github/configs/pq-all.json | 5 +++++ .wolfssl_known_macro_extras | 1 - tests/api/test_sha256.c | 4 +--- tests/unit-mcdc/mcdc_fault_hash.h | 4 ++-- wolfcrypt/src/sha256.c | 14 +++++++++++-- wolfcrypt/test/test.c | 12 +++++------ wolfssl/wolfcrypt/sha256.h | 35 ++++++++++++++++++++++++++++++- wolfssl/wolfcrypt/wc_lms.h | 4 ++++ 8 files changed, 63 insertions(+), 16 deletions(-) diff --git a/.github/configs/pq-all.json b/.github/configs/pq-all.json index 32a3c50f9fc..ee8d7ea6c0b 100644 --- a/.github/configs/pq-all.json +++ b/.github/configs/pq-all.json @@ -154,6 +154,11 @@ "--enable-dtls-mtu", "--enable-dtls-frag-ch", "--enable-dtlscid", "--enable-mlkem=make,enc,dec,768", "--disable-qt", "CPPFLAGS=-pedantic -Wdeclaration-after-statement -Wnull-dereference -DWOLFCRYPT_TEST_LINT -DNO_WOLFSSL_CIPHER_SUITE_TEST -DTEST_LIBWOLFSSL_SOURCES_INCLUSION_SEQUENCE"]}, +{"name": "lms-slhdsa-no-hash-raw", "minutes": 3, + "comment": "No raw hash access, as on hardware SHA-256 ports: catches callers of the unbuilt wc_Sha256HashBlock()", + "configure": ["--enable-lms", "--enable-xmss", + "--enable-slhdsa=yes,sha2", + "CPPFLAGS=-DWOLFSSL_NO_HASH_RAW"]}, {"name": "mldsa-no-asn1-opensslextra", "minutes": 2.8, "configure": ["--enable-intelasm", "--enable-sp-asm", "--enable-dilithium=yes", "--enable-opensslextra", diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index fb302c4c8e1..d998125f4ec 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -1045,7 +1045,6 @@ WOLFSSL_LINUXKM_USE_GET_RANDOM_KPROBES WOLFSSL_LINUXKM_USE_GET_RANDOM_USER_KRETPROBE WOLFSSL_LINUXKM_USE_MUTEXES WOLFSSL_LMS_CACHE_BITS -WOLFSSL_LMS_FULL_HASH WOLFSSL_LMS_MAX_HEIGHT WOLFSSL_LMS_MAX_LEVELS WOLFSSL_LMS_ROOT_LEVELS diff --git a/tests/api/test_sha256.c b/tests/api/test_sha256.c index d6bdec5a722..bba029721d6 100644 --- a/tests/api/test_sha256.c +++ b/tests/api/test_sha256.c @@ -220,9 +220,7 @@ int test_wc_Sha256Transform(void) int test_wc_Sha256HashBlock_unaligned(void) { EXPECT_DECLS; -#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_FULL_HASH) && \ - !defined(NO_SHA256) && !defined(WOLFSSL_KCAPI_HASH) && \ - !defined(WOLF_CRYPTO_CB_ONLY_SHA256) +#ifdef WOLFSSL_HAVE_SHA256_HASH_BLOCK wc_Sha256 sha256; byte buf[WC_SHA256_BLOCK_SIZE * 2]; byte aligned[WC_SHA256_DIGEST_SIZE]; diff --git a/tests/unit-mcdc/mcdc_fault_hash.h b/tests/unit-mcdc/mcdc_fault_hash.h index 5ef7708c56c..ccb66e747e8 100644 --- a/tests/unit-mcdc/mcdc_fault_hash.h +++ b/tests/unit-mcdc/mcdc_fault_hash.h @@ -207,7 +207,7 @@ MCDC_FH_MAYBE_UNUSED static int mcdc_fh_Sha256Final(wc_Sha256* sha, byte* hash) return MCDC_FH_ERR; return wc_Sha256Final(sha, hash); } -#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_FULL_HASH) +#ifdef WOLFSSL_HAVE_SHA256_HASH_BLOCK MCDC_FH_MAYBE_UNUSED static int mcdc_fh_Sha256HashBlock(wc_Sha256* sha, const unsigned char* data, unsigned char* hash) { @@ -385,7 +385,7 @@ MCDC_FH_MAYBE_UNUSED static int mcdc_fh_AesSetKeyDirect(Aes* aes, const byte* ke #define wc_Sha256Update(a, b, c) mcdc_fh_Sha256Update((a), (b), (c)) #undef wc_Sha256Final #define wc_Sha256Final(a, b) mcdc_fh_Sha256Final((a), (b)) - #if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_FULL_HASH) + #ifdef WOLFSSL_HAVE_SHA256_HASH_BLOCK #undef wc_Sha256HashBlock #define wc_Sha256HashBlock(a, b, c) \ mcdc_fh_Sha256HashBlock((a), (b), (c)) diff --git a/wolfcrypt/src/sha256.c b/wolfcrypt/src/sha256.c index 07ddeb60a5f..97dcc0d4e6a 100644 --- a/wolfcrypt/src/sha256.c +++ b/wolfcrypt/src/sha256.c @@ -145,6 +145,12 @@ on the specific device platform. #endif #endif +#if defined(WOLFSSL_HAVE_SHA256_HASH_BLOCK) && \ + (defined(WOLFSSL_TI_HASH) || defined(WOLFSSL_CRYPTOCELL) || \ + defined(MAX3266X_SHA) || defined(WOLFSSL_KCAPI_HASH)) + #error "WOLFSSL_HAVE_SHA256_HASH_BLOCK set without a SHA-256 transform" +#endif + #if defined(WOLFSSL_TI_HASH) /* #include included by wc_port.c */ #elif defined(MAX3266X_SHA) @@ -2052,6 +2058,10 @@ static WC_INLINE int Transform_Sha256_Len(wc_Sha256* sha256, const byte* data, #endif /* End wc_ software implementation */ +#if defined(WOLFSSL_HAVE_SHA256_HASH_BLOCK) && !defined(XTRANSFORM) + #error "WOLFSSL_HAVE_SHA256_HASH_BLOCK set without a SHA-256 transform" +#endif + #ifdef XTRANSFORM static WC_INLINE void AddLength(wc_Sha256* sha256, word32 len) @@ -2608,7 +2618,7 @@ static WC_INLINE int Transform_Sha256_Len(wc_Sha256* sha256, const byte* data, } #endif /* OPENSSL_EXTRA || HAVE_CURL */ -#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_FULL_HASH) +#ifdef WOLFSSL_HAVE_SHA256_HASH_BLOCK /* One block will be used from data. * hash must be big enough to hold all of digest output. */ @@ -2717,7 +2727,7 @@ static WC_INLINE int Transform_Sha256_Len(wc_Sha256* sha256, const byte* data, return ret; } -#endif /* WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_FULL_HASH */ +#endif /* WOLFSSL_HAVE_SHA256_HASH_BLOCK */ #endif /* !WOLFSSL_KCAPI_HASH */ #endif /* XTRANSFORM */ diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index ff02ebaa7a2..d45926c7a34 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -7670,9 +7670,8 @@ static wc_test_ret_t sha256_large_hash_test(wc_Sha256* sha) #undef LARGE_HASH_TEST_INPUT_SZ #endif /* NO_LARGE_HASH_TEST */ -#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_FULL_HASH) && \ - !defined(WOLF_CRYPTO_CB_ONLY_SHA256) -static wc_test_ret_t sha256_lms_test(wc_Sha256* sha) +#ifdef WOLFSSL_HAVE_SHA256_HASH_BLOCK +static wc_test_ret_t sha256_hash_block_test(wc_Sha256* sha) { byte hash[WC_SHA256_DIGEST_SIZE]; wc_test_ret_t ret = 0; @@ -7708,7 +7707,7 @@ static wc_test_ret_t sha256_lms_test(wc_Sha256* sha) wc_Sha256Free(sha); return ret; } -#endif /* WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_FULL_HASH */ +#endif /* WOLFSSL_HAVE_SHA256_HASH_BLOCK */ #if !defined(HAVE_SELFTEST) && (!defined(HAVE_FIPS) || FIPS_VERSION_GE(7, 0)) static wc_test_ret_t sha256_copy_test(wc_Sha256* sha, wc_Sha256* shaCopy) @@ -7883,9 +7882,8 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t sha256_test(void) if ((ret = sha256_large_hash_test(&sha)) != 0) return ret; #endif -#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_FULL_HASH) && \ - !defined(WOLF_CRYPTO_CB_ONLY_SHA256) - if ((ret = sha256_lms_test(&sha)) != 0) +#ifdef WOLFSSL_HAVE_SHA256_HASH_BLOCK + if ((ret = sha256_hash_block_test(&sha)) != 0) return ret; #endif #if !defined(HAVE_SELFTEST) && (!defined(HAVE_FIPS) || FIPS_VERSION_GE(7, 0)) diff --git a/wolfssl/wolfcrypt/sha256.h b/wolfssl/wolfcrypt/sha256.h index 4bd22ff2df0..c3190d32d07 100644 --- a/wolfssl/wolfcrypt/sha256.h +++ b/wolfssl/wolfcrypt/sha256.h @@ -109,6 +109,39 @@ #define WOLFSSL_NO_HASH_RAW #endif +/* Ports with no software transform or with state outside sha256->digest. */ +#if (defined(WOLFSSL_HAVE_LMS) || \ + (defined(WOLFSSL_HAVE_SLHDSA) && defined(WOLFSSL_SLHDSA_SHA2))) && \ + !defined(WOLFSSL_NO_HASH_RAW) && \ + !defined(WOLFSSL_TI_HASH) && \ + !defined(WOLFSSL_CRYPTOCELL) && \ + !defined(WOLFSSL_MAX3266X) && \ + !defined(WOLFSSL_MAX3266X_OLD) && \ + !defined(FREESCALE_LTC_SHA) && \ + !defined(WOLFSSL_PIC32MZ_HASH) && \ + !defined(STM32_HASH_SHA2) && \ + !(defined(WOLFSSL_IMX6_CAAM) && !defined(NO_IMX6_CAAM_HASH) && \ + !defined(WOLFSSL_QNX_CAAM)) && \ + !(defined(WOLFSSL_SE050) && defined(WOLFSSL_SE050_HASH)) && \ + !defined(WOLFSSL_AFALG_HASH) && \ + !defined(WOLFSSL_DEVCRYPTO_HASH) && \ + !(defined(WOLFSSL_ESP32_CRYPT) && \ + !defined(NO_WOLFSSL_ESP32_CRYPT_HASH)) && \ + !((defined(WOLFSSL_RENESAS_TSIP_TLS) || \ + defined(WOLFSSL_RENESAS_TSIP_CRYPTONLY)) && \ + !defined(NO_WOLFSSL_RENESAS_TSIP_CRYPT_HASH)) && \ + !((defined(WOLFSSL_RENESAS_SCEPROTECT) || \ + defined(WOLFSSL_RENESAS_RSIP)) && \ + !defined(NO_WOLFSSL_RENESAS_FSPSM_HASH)) && \ + !defined(WOLFSSL_RENESAS_RX64_HASH) && \ + !defined(PSOC6_HASH_SHA2) && \ + !defined(WOLFSSL_IMXRT_DCP) && \ + !defined(WOLFSSL_NXP_HASHCRYPT_SHA) && \ + !defined(WOLFSSL_SILABS_SE_ACCEL) && \ + !defined(WOLFSSL_KCAPI_HASH) + #define WOLFSSL_HAVE_SHA256_HASH_BLOCK +#endif + #define SHA256_NOINLINE WC_NO_INLINE #if !defined(NO_OLD_SHA_NAMES) @@ -297,7 +330,7 @@ WOLFSSL_API int wc_Sha256Reset(wc_Sha256* sha256); !defined(WOLF_CRYPTO_CB_ONLY_SHA256) WOLFSSL_API int wc_Sha256Transform(wc_Sha256* sha, const unsigned char* data); #endif -#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_FULL_HASH) +#ifdef WOLFSSL_HAVE_SHA256_HASH_BLOCK WOLFSSL_API int wc_Sha256HashBlock(wc_Sha256* sha, const unsigned char* data, unsigned char* hash); #endif diff --git a/wolfssl/wolfcrypt/wc_lms.h b/wolfssl/wolfcrypt/wc_lms.h index 76446453e2c..4b979651eee 100644 --- a/wolfssl/wolfcrypt/wc_lms.h +++ b/wolfssl/wolfcrypt/wc_lms.h @@ -107,6 +107,10 @@ #if defined(WOLFSSL_NO_HASH_RAW) && !defined(WC_LMS_FULL_HASH) #define WC_LMS_FULL_HASH #endif +/* The SHA-256 parameter sets need the one block compression helper. */ +#if !defined(WOLFSSL_HAVE_SHA256_HASH_BLOCK) && !defined(WC_LMS_FULL_HASH) + #define WC_LMS_FULL_HASH +#endif /* Length of the Key ID. */ #define WC_LMS_I_LEN 16 From f5143d4a826c63d2524e07af760c61bafeeb4b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 30 Sep 2026 09:07:54 +0200 Subject: [PATCH 2/4] Speed up SLH-DSA SHA2 hashing The SHA2 parameter sets ran every single F, H and PRF through the streaming hash API: a free, a context copy, two updates and a final, all to compute one 64 byte compression. Replace that by building the block that follows the pre-computed PK.seed midstate and compressing it once. The four helpers now share one dispatcher, which takes wc_Sha256HashBlock where it is available and the streaming API otherwise. On current master, where the batched hashes already take the n-way SHA-256 path, the gain is modest and depends on the parameter set: measured on a Xeon at 2.1 GHz without assembly, SHA2-192s and SHA2-256s signing get about 1.08x faster (SHA2-256s 1135 ms to 1037 ms) while the 128 sets are unchanged, and with --enable-intelasm signing moves by at most 1.1x (SHA2-256f 36.7 ms to 33.2 ms). The streaming path keeps the free ahead of each copy. Only the generic wc_Sha256Copy() frees its destination: the KCAPI and AF_ALG ports overwrite it and allocate a fresh handle, and the generic one returns before its free when a crypto callback takes the copy, so dropping the free would strand a handle per hash on those builds. The direct path is taken only when neither the working object nor the midstate has been claimed by a crypto callback, since a callback holds the state itself and expects to see every update. Both are initialised with the default device id, so a registered callback keeps the streaming path. WOLF_CRYPTO_CB_FIND consults a callback for every hash object and always takes it. The block helper rejects a message that cannot share one block with its padding, and compile time checks pin the wc_Sha256 buffer and digest sizes it writes. Public keys and signatures are unchanged. Checked byte for byte against the previous code for all SHA2 parameter sets, and with WOLFSSL_SLHDSA_FULL_HASH forcing the streaming API. The PRF output that now takes the wc_Sha256HashBlock path is secret, and on x86-64 CPUs without MOVBE that function byte-reverses the digest through a stack buffer it never cleared. Wipe it. The helpers build the block in the working SHA-256 object's buffer and write the digest to the key's sha2Digest, so the once per operation wipe covers both: it zeroes sha2Digest and frees the object. That wipe also drops the transform scratch a WOLFSSL_SMALL_STACK_CACHE build allocates at init, so the dispatcher initialises the object again before the next compression. The streaming SHA-256 and SHA-512 paths share one helper. Its SHA-512 branch is compiled only when a category 3 or 5 SHA2 parameter set is. --- wolfcrypt/src/sha256.c | 1 + wolfcrypt/src/wc_slhdsa.c | 369 +++++++++++++++++++--------------- wolfssl/wolfcrypt/wc_slhdsa.h | 2 +- 3 files changed, 214 insertions(+), 158 deletions(-) diff --git a/wolfcrypt/src/sha256.c b/wolfcrypt/src/sha256.c index 97dcc0d4e6a..fe5cbc2a833 100644 --- a/wolfcrypt/src/sha256.c +++ b/wolfcrypt/src/sha256.c @@ -2661,6 +2661,7 @@ static WC_INLINE int Transform_Sha256_Len(wc_Sha256* sha256, const byte* data, word32 buf[WC_SHA256_DIGEST_SIZE / sizeof(word32)]; ByteReverseWords(buf, sha256->digest, WC_SHA256_DIGEST_SIZE); XMEMCPY(hash, buf, WC_SHA256_DIGEST_SIZE); + ForceZero(buf, sizeof(buf)); } #endif else { diff --git a/wolfcrypt/src/wc_slhdsa.c b/wolfcrypt/src/wc_slhdsa.c index a634a3c7157..9e49458e8b5 100644 --- a/wolfcrypt/src/wc_slhdsa.c +++ b/wolfcrypt/src/wc_slhdsa.c @@ -740,6 +740,29 @@ static int slhdsakey_hash_shake_4(wc_Shake* shake, const byte* data1, /* Size of compressed HashAddress (ADRS^c) per FIPS 205 Section 11.2. */ #define SLHDSA_HAC_SZ 22 +/* Compress the block after the PK.seed midstate directly. */ +#if !defined(WOLFSSL_SLHDSA_FULL_HASH) && \ + defined(WOLFSSL_HAVE_SHA256_HASH_BLOCK) && \ + !(defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_FIND)) + #define SLHDSA_SHA2_BLOCK_HASH +#endif + +#ifdef SLHDSA_SHA2_BLOCK_HASH +/* The direct path writes wc_Sha256's digest and block buffer in place. */ +wc_static_assert(sizeof(((wc_Sha256*)0)->buffer) == WC_SHA256_BLOCK_SIZE); +wc_static_assert(sizeof(((wc_Sha256*)0)->digest) == WC_SHA256_DIGEST_SIZE); + +/* A registered callback expects to see every update, and holds the state + * itself, so a claimed midstate has no digest to restore. */ +#ifdef WOLF_CRYPTO_CB + #define SLHDSA_SHA256_RAW_OK(key) \ + (((key)->hash.sha2.sha256.devId == INVALID_DEVID) && \ + ((key)->hash.sha2.sha256_mid.devId == INVALID_DEVID)) +#else + #define SLHDSA_SHA256_RAW_OK(key) 1 +#endif +#endif /* SLHDSA_SHA2_BLOCK_HASH */ + /* Encode a compressed HashAddress (ADRS^c). * * FIPS 205. Section 11.2. @@ -824,6 +847,183 @@ static int slhdsakey_precompute_sha2_midstates(SlhDsaKey* key) return ret; } +/* Largest message is n bytes for F and PRF, 2n for H at category 1; both + * leave room for the padding and length in the block after the midstate. */ +wc_static_assert(SLHDSA_HAC_SZ + 32 + 1 + 8 <= WC_SHA256_BLOCK_SIZE); + +#ifdef SLHDSA_SHA2_BLOCK_HASH +/* Hash the block following the pre-computed SHA-256 midstate. + * + * F, H and PRF are each one block past the midstate, so the compressed + * address, message and padding are built directly and compressed once. + * + * @param [in] key SLH-DSA key. + * @param [in] address Encoded compressed HashAddress. + * @param [in] m1 First message part. + * @param [in] m1_len Length of first message part. + * @param [in] m2 Second message part, may be NULL. + * @param [in] m2_len Length of second message part. + * @param [out] hash Buffer to hold hash output. + * @param [in] hash_len Number of bytes of hash to output. + * @return 0 on success. + * @return BUFFER_E when the message does not fit the block with its padding. + */ +static int slhdsakey_sha256_block_hash(SlhDsaKey* key, const byte* address, + const byte* m1, byte m1_len, const byte* m2, byte m2_len, byte* hash, + byte hash_len) +{ + int ret; + byte* block = (byte*)key->hash.sha2.sha256.buffer; + word32 len = (word32)SLHDSA_HAC_SZ + m1_len + m2_len; + /* Length covers the midstate block as well as this one. */ + word32 bits = (WC_SHA256_BLOCK_SIZE + len) * 8; + + /* Message, padding byte and 8 length bytes have to share one block. */ + if (len + 1 + 8 > WC_SHA256_BLOCK_SIZE) { + return BUFFER_E; + } + + XMEMCPY(block, address, SLHDSA_HAC_SZ); + XMEMCPY(block + SLHDSA_HAC_SZ, m1, m1_len); + if (m2_len > 0) { + XMEMCPY(block + SLHDSA_HAC_SZ + m1_len, m2, m2_len); + } + /* SHA-256 padding. */ + block[len] = 0x80; + XMEMSET(block + len + 1, 0, WC_SHA256_BLOCK_SIZE - 8 - (len + 1)); + c32toa(0, block + WC_SHA256_BLOCK_SIZE - 8); + c32toa(bits, block + WC_SHA256_BLOCK_SIZE - 4); + + /* Restore the midstate and compress. */ + XMEMCPY(key->hash.sha2.sha256.digest, key->hash.sha2.sha256_mid.digest, + WC_SHA256_DIGEST_SIZE); + ret = wc_Sha256HashBlock(&key->hash.sha2.sha256, block, key->sha2Digest); + if (ret == 0) { + XMEMCPY(hash, key->sha2Digest, hash_len); + } + + return ret; +} +#endif /* SLHDSA_SHA2_BLOCK_HASH */ + +/* Hash the compressed address and message from the SHA-256 or SHA-512 + * midstate with the streaming API. + * + * @param [in] key SLH-DSA key. + * @param [in] sha512 Use SHA-512 when non-zero, otherwise SHA-256. + * @param [in] address Encoded compressed HashAddress. + * @param [in] m1 First message part. + * @param [in] m1_len Length of first message part. + * @param [in] m2 Second message part, may be NULL. + * @param [in] m2_len Length of second message part. + * @param [out] hash Buffer to hold hash output. + * @param [in] hash_len Number of bytes of hash to output. + * @return 0 on success. + */ +static int slhdsakey_sha2_api_hash(SlhDsaKey* key, int sha512, + const byte* address, const byte* m1, byte m1_len, const byte* m2, + byte m2_len, byte* hash, byte hash_len) +{ + int ret; + int i; + const byte* data[3]; + word32 len[3]; + byte* digest = key->sha2Digest; +#ifdef WC_SLHDSA_NEED_SHA512 + byte digest512[WC_SHA512_DIGEST_SIZE]; +#endif + + data[0] = address; + len[0] = SLHDSA_HAC_SZ; + data[1] = m1; + len[1] = m1_len; + data[2] = m2; + len[2] = m2_len; + +#ifdef WC_SLHDSA_NEED_SHA512 + if (sha512) { + digest = digest512; + 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; + } + for (i = 0; (ret == 0) && (i < 3); i++) { + if (len[i] > 0) { + ret = wc_Sha512Update(&key->hash.sha2.sha512, data[i], len[i]); + } + } + if (ret == 0) { + ret = wc_Sha512Final(&key->hash.sha2.sha512, digest); + } + } + else +#endif + { + (void)sha512; + /* Not every wc_Sha256Copy() frees its destination. */ + 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; + } + for (i = 0; (ret == 0) && (i < 3); i++) { + if (len[i] > 0) { + ret = wc_Sha256Update(&key->hash.sha2.sha256, data[i], len[i]); + } + } + if (ret == 0) { + ret = wc_Sha256Final(&key->hash.sha2.sha256, digest); + } + } + if (ret == 0) { + XMEMCPY(hash, digest, hash_len); + } + + return ret; +} + +/* Hash the compressed address and message with SHA-256. + * + * @param [in] key SLH-DSA key. + * @param [in] address Encoded compressed HashAddress. + * @param [in] m1 First message part. + * @param [in] m1_len Length of first message part. + * @param [in] m2 Second message part, may be NULL. + * @param [in] m2_len Length of second message part. + * @param [out] hash Buffer to hold hash output. + * @param [in] hash_len Number of bytes of hash to output. + * @return 0 on success. + */ +static int slhdsakey_sha256_hash(SlhDsaKey* key, const byte* address, + const byte* m1, byte m1_len, const byte* m2, byte m2_len, byte* hash, + byte hash_len) +{ +#ifdef SLHDSA_SHA2_BLOCK_HASH + if (!key->hash.sha2.sha256_inited) { + int ret = wc_InitSha256(&key->hash.sha2.sha256); + if (ret != 0) { + return ret; + } + key->hash.sha2.sha256_inited = 1; + } + if (SLHDSA_SHA256_RAW_OK(key)) { + return slhdsakey_sha256_block_hash(key, address, m1, m1_len, m2, + m2_len, hash, hash_len); + } +#endif + return slhdsakey_sha2_api_hash(key, 0, address, m1, m1_len, m2, m2_len, + hash, hash_len); +} + /* SHA2 F function. * * FIPS 205. Section 11.2. @@ -842,39 +1042,14 @@ static int slhdsakey_precompute_sha2_midstates(SlhDsaKey* key) 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 = key->sha2Digest; (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; + return slhdsakey_sha256_hash(key, address, m, n, NULL, 0, hash, n); } #ifndef WOLFSSL_SLHDSA_VERIFY_ONLY @@ -905,54 +1080,14 @@ static int slhdsakey_hash_h_sha2(SlhDsaKey* key, const byte* pk_seed, if (SLHDSA_SHA2_USE_SHA256(n)) { /* 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); - } + ret = slhdsakey_sha256_hash(key, address, node, (byte)(2 * n), NULL, 0, + hash, n); } #ifdef WC_SLHDSA_NEED_SHA512 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); - } + ret = slhdsakey_sha2_api_hash(key, 1, address, node, (byte)(2 * n), + NULL, 0, hash, n); } #endif @@ -960,15 +1095,15 @@ static int slhdsakey_hash_h_sha2(SlhDsaKey* key, const byte* pk_seed, } #endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */ -/* SHA2 H function with two separate n-byte halves. +/* SHA2 H function over two separate n-byte messages. * - * Same as slhdsakey_hash_h_sha2 but M2 = m1 || m2. + * FIPS 205. Section 11.2. * * @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] m1 First message of n bytes. + * @param [in] m2 Second 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. @@ -986,60 +1121,12 @@ static int slhdsakey_hash_h_2_sha2(SlhDsaKey* key, const byte* pk_seed, if (SLHDSA_SHA2_USE_SHA256(n)) { /* 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); - } + ret = slhdsakey_sha256_hash(key, address, m1, n, m2, n, hash, n); } #ifdef WC_SLHDSA_NEED_SHA512 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); - } + ret = slhdsakey_sha2_api_hash(key, 1, address, m1, n, m2, n, hash, n); } #endif @@ -1064,46 +1151,14 @@ static int slhdsakey_hash_h_2_sha2(SlhDsaKey* key, const byte* pk_seed, 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); - /* digest now holds the secret PRF output (WOTS+/FORS key); register it - * before it is copied out so any later exit is covered. */ -#ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Add("slhdsa prf digest", digest, sizeof(digest)); -#endif - } - if (ret == 0) { - XMEMCPY(hash, digest, n); - } - - /* digest holds the secret PRF output (WOTS+/FORS key). */ - ForceZero(digest, sizeof(digest)); -#ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Check(digest, sizeof(digest)); -#endif - return ret; + return slhdsakey_sha256_hash(key, address, sk_seed, n, NULL, 0, hash, n); } #endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */ diff --git a/wolfssl/wolfcrypt/wc_slhdsa.h b/wolfssl/wolfcrypt/wc_slhdsa.h index 70d48f3f4a2..3b4a7ca65d8 100644 --- a/wolfssl/wolfcrypt/wc_slhdsa.h +++ b/wolfssl/wolfcrypt/wc_slhdsa.h @@ -851,7 +851,7 @@ struct SlhDsaKey { #endif } hash; #ifdef WOLFSSL_SLHDSA_SHA2 - /* Reusable F-function output, cleared after private operations and Free. */ + /* SHA-256 output, cleared after private operations and Free. */ byte sha2Digest[WC_SHA256_DIGEST_SIZE]; #endif }; From c2eb602740880c1028871f19d8f19ace9d9a8c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 30 Sep 2026 09:29:58 +0200 Subject: [PATCH 3/4] Reuse the SLH-DSA WOTS+ chain buffer across a signature Every WOTS+ public key allocated its own chain value buffer, and a signature builds thousands of them: 511 per XMSS subtree for the 128s parameter sets, over seven layers. Hand one buffer down instead. slhdsakey_ht_sign owns it for a whole signature, and the new slhdsakey_root_from_seed owns it for key generation, the key pair test and wc_SlhDsaKey_CheckKey, which all computed the top root the same way and now share that helper. The four-way, eight-way and SHA2 sixteen-way helpers take the same buffer rather than each allocating their own, so it is sized for sixteen lanes. Under WOLFSSL_SMALL_STACK without assembly, SHAKE-128s key generation goes from 513 allocations to 2 and signing from 3795 to 219. The SHA2 sets save the same few thousand, a fraction of a percent of what their hashing allocates on that build. Without WOLFSSL_SMALL_STACK the buffer moves from the leaf function's frame to the owner's. A small-memory build without the batched helpers keeps its single chain value on the stack and carries no buffer. Public keys and signatures are unchanged, checked byte for byte against the previous code for all twelve parameter sets. Verification is the one path where a defect means a false accept rather than a visible failure, so the check for that goes in the test suite. test_wc_slhdsa_sign_vfy now hands each signature it makes to a helper that requires SIG_VERIFY_E from nine single bit flips spread evenly over the signature, a changed message, a changed context and an absent context, then re-checks the restored signature so a rejection cannot be an artefact of leftover damage. Reusing the keys and signatures sign_vfy already makes keeps the cost to a few verifies per parameter set. Confirmed to bite by making the root comparison always accept. --- tests/api/test_slhdsa.c | 65 +++++++ wolfcrypt/src/wc_slhdsa.c | 400 ++++++++++++++++++++------------------ 2 files changed, 276 insertions(+), 189 deletions(-) diff --git a/tests/api/test_slhdsa.c b/tests/api/test_slhdsa.c index 45d2e0a40ad..3a2c3ccf577 100644 --- a/tests/api/test_slhdsa.c +++ b/tests/api/test_slhdsa.c @@ -941,6 +941,47 @@ int test_wc_slhdsa_verify(void) return EXPECT_RESULT(); } +#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) +/* Number of single bit flips applied across one signature. */ +#define TEST_SLHDSA_NEG_FLIPS 9 + +/* Verify has to reject a flipped signature bit, a changed message or context, + * and an absent context. The inputs are restored before returning. */ +static int slhdsa_verify_reject(SlhDsaKey* key, byte* ctx, byte ctxSz, + byte* msg, word32 msgSz, byte* sig, word32 sigLen) +{ + EXPECT_DECLS; + word32 off; + int i; + + for (i = 0; (i < TEST_SLHDSA_NEG_FLIPS) && EXPECT_SUCCESS(); i++) { + /* Evenly spaced, first and last byte included. */ + off = (word32)i * (sigLen - 1) / (TEST_SLHDSA_NEG_FLIPS - 1); + sig[off] ^= 0x01; + ExpectIntEQ(wc_SlhDsaKey_Verify(key, ctx, ctxSz, msg, msgSz, sig, + sigLen), WC_NO_ERR_TRACE(SIG_VERIFY_E)); + sig[off] ^= 0x01; + } + + msg[0] ^= 0x01; + ExpectIntEQ(wc_SlhDsaKey_Verify(key, ctx, ctxSz, msg, msgSz, sig, sigLen), + WC_NO_ERR_TRACE(SIG_VERIFY_E)); + msg[0] ^= 0x01; + + ctx[0] ^= 0x01; + ExpectIntEQ(wc_SlhDsaKey_Verify(key, ctx, ctxSz, msg, msgSz, sig, sigLen), + WC_NO_ERR_TRACE(SIG_VERIFY_E)); + ctx[0] ^= 0x01; + + ExpectIntEQ(wc_SlhDsaKey_Verify(key, NULL, 0, msg, msgSz, sig, sigLen), + WC_NO_ERR_TRACE(SIG_VERIFY_E)); + ExpectIntEQ(wc_SlhDsaKey_Verify(key, ctx, ctxSz, msg, msgSz, sig, sigLen), + 0); + + return EXPECT_RESULT(); +} +#endif /* WOLFSSL_HAVE_SLHDSA && !WOLFSSL_SLHDSA_VERIFY_ONLY */ + /* * Test combined sign and verify for all parameter sets. */ @@ -978,6 +1019,8 @@ int test_wc_slhdsa_sign_vfy(void) ExpectIntEQ(sigLen, WC_SLHDSA_SHAKE128S_SIG_LEN); ExpectIntEQ(wc_SlhDsaKey_Verify(&key, ctx, sizeof(ctx), msg, sizeof(msg), sig, sigLen), 0); + ExpectIntEQ(slhdsa_verify_reject(&key, ctx, (byte)sizeof(ctx), msg, + (word32)sizeof(msg), sig, sigLen), TEST_SUCCESS); wc_SlhDsaKey_Free(&key); #endif @@ -993,6 +1036,8 @@ int test_wc_slhdsa_sign_vfy(void) ExpectIntEQ(sigLen, WC_SLHDSA_SHAKE128F_SIG_LEN); ExpectIntEQ(wc_SlhDsaKey_Verify(&key, ctx, sizeof(ctx), msg, sizeof(msg), sig, sigLen), 0); + ExpectIntEQ(slhdsa_verify_reject(&key, ctx, (byte)sizeof(ctx), msg, + (word32)sizeof(msg), sig, sigLen), TEST_SUCCESS); wc_SlhDsaKey_Free(&key); #endif @@ -1008,6 +1053,8 @@ int test_wc_slhdsa_sign_vfy(void) ExpectIntEQ(sigLen, (word32)wc_SlhDsaKey_SigSize(&key)); ExpectIntEQ(wc_SlhDsaKey_Verify(&key, ctx, sizeof(ctx), msg, sizeof(msg), sig, sigLen), 0); + ExpectIntEQ(slhdsa_verify_reject(&key, ctx, (byte)sizeof(ctx), msg, + (word32)sizeof(msg), sig, sigLen), TEST_SUCCESS); wc_SlhDsaKey_Free(&key); #endif @@ -1023,6 +1070,8 @@ int test_wc_slhdsa_sign_vfy(void) ExpectIntEQ(sigLen, WC_SLHDSA_SHAKE192F_SIG_LEN); ExpectIntEQ(wc_SlhDsaKey_Verify(&key, ctx, sizeof(ctx), msg, sizeof(msg), sig, sigLen), 0); + ExpectIntEQ(slhdsa_verify_reject(&key, ctx, (byte)sizeof(ctx), msg, + (word32)sizeof(msg), sig, sigLen), TEST_SUCCESS); wc_SlhDsaKey_Free(&key); #endif @@ -1038,6 +1087,8 @@ int test_wc_slhdsa_sign_vfy(void) ExpectIntEQ(sigLen, WC_SLHDSA_SHAKE256S_SIG_LEN); ExpectIntEQ(wc_SlhDsaKey_Verify(&key, ctx, sizeof(ctx), msg, sizeof(msg), sig, sigLen), 0); + ExpectIntEQ(slhdsa_verify_reject(&key, ctx, (byte)sizeof(ctx), msg, + (word32)sizeof(msg), sig, sigLen), TEST_SUCCESS); wc_SlhDsaKey_Free(&key); #endif @@ -1053,6 +1104,8 @@ int test_wc_slhdsa_sign_vfy(void) ExpectIntEQ(sigLen, WC_SLHDSA_SHAKE256F_SIG_LEN); ExpectIntEQ(wc_SlhDsaKey_Verify(&key, ctx, sizeof(ctx), msg, sizeof(msg), sig, sigLen), 0); + ExpectIntEQ(slhdsa_verify_reject(&key, ctx, (byte)sizeof(ctx), msg, + (word32)sizeof(msg), sig, sigLen), TEST_SUCCESS); wc_SlhDsaKey_Free(&key); #endif @@ -1068,6 +1121,8 @@ int test_wc_slhdsa_sign_vfy(void) ExpectIntEQ(sigLen, WC_SLHDSA_SHA2_128S_SIG_LEN); ExpectIntEQ(wc_SlhDsaKey_Verify(&key, ctx, sizeof(ctx), msg, sizeof(msg), sig, sigLen), 0); + ExpectIntEQ(slhdsa_verify_reject(&key, ctx, (byte)sizeof(ctx), msg, + (word32)sizeof(msg), sig, sigLen), TEST_SUCCESS); wc_SlhDsaKey_Free(&key); #endif #ifdef WOLFSSL_SLHDSA_PARAM_SHA2_128F @@ -1082,6 +1137,8 @@ int test_wc_slhdsa_sign_vfy(void) ExpectIntEQ(sigLen, WC_SLHDSA_SHA2_128F_SIG_LEN); ExpectIntEQ(wc_SlhDsaKey_Verify(&key, ctx, sizeof(ctx), msg, sizeof(msg), sig, sigLen), 0); + ExpectIntEQ(slhdsa_verify_reject(&key, ctx, (byte)sizeof(ctx), msg, + (word32)sizeof(msg), sig, sigLen), TEST_SUCCESS); wc_SlhDsaKey_Free(&key); ExpectBufEQ(key.sha2Digest, zeroDigest, sizeof(zeroDigest)); #endif @@ -1095,6 +1152,8 @@ int test_wc_slhdsa_sign_vfy(void) ExpectIntEQ(sigLen, WC_SLHDSA_SHA2_192S_SIG_LEN); ExpectIntEQ(wc_SlhDsaKey_Verify(&key, ctx, sizeof(ctx), msg, sizeof(msg), sig, sigLen), 0); + ExpectIntEQ(slhdsa_verify_reject(&key, ctx, (byte)sizeof(ctx), msg, + (word32)sizeof(msg), sig, sigLen), TEST_SUCCESS); wc_SlhDsaKey_Free(&key); #endif #ifdef WOLFSSL_SLHDSA_PARAM_SHA2_192F @@ -1107,6 +1166,8 @@ int test_wc_slhdsa_sign_vfy(void) ExpectIntEQ(sigLen, WC_SLHDSA_SHA2_192F_SIG_LEN); ExpectIntEQ(wc_SlhDsaKey_Verify(&key, ctx, sizeof(ctx), msg, sizeof(msg), sig, sigLen), 0); + ExpectIntEQ(slhdsa_verify_reject(&key, ctx, (byte)sizeof(ctx), msg, + (word32)sizeof(msg), sig, sigLen), TEST_SUCCESS); wc_SlhDsaKey_Free(&key); #endif #ifdef WOLFSSL_SLHDSA_PARAM_SHA2_256S @@ -1119,6 +1180,8 @@ int test_wc_slhdsa_sign_vfy(void) ExpectIntEQ(sigLen, WC_SLHDSA_SHA2_256S_SIG_LEN); ExpectIntEQ(wc_SlhDsaKey_Verify(&key, ctx, sizeof(ctx), msg, sizeof(msg), sig, sigLen), 0); + ExpectIntEQ(slhdsa_verify_reject(&key, ctx, (byte)sizeof(ctx), msg, + (word32)sizeof(msg), sig, sigLen), TEST_SUCCESS); wc_SlhDsaKey_Free(&key); #endif #ifdef WOLFSSL_SLHDSA_PARAM_SHA2_256F @@ -1131,6 +1194,8 @@ int test_wc_slhdsa_sign_vfy(void) ExpectIntEQ(sigLen, WC_SLHDSA_SHA2_256F_SIG_LEN); ExpectIntEQ(wc_SlhDsaKey_Verify(&key, ctx, sizeof(ctx), msg, sizeof(msg), sig, sigLen), 0); + ExpectIntEQ(slhdsa_verify_reject(&key, ctx, (byte)sizeof(ctx), msg, + (word32)sizeof(msg), sig, sigLen), TEST_SUCCESS); wc_SlhDsaKey_Free(&key); #endif #endif /* WOLFSSL_SLHDSA_SHA2 */ diff --git a/wolfcrypt/src/wc_slhdsa.c b/wolfcrypt/src/wc_slhdsa.c index 9e49458e8b5..d222cdaf347 100644 --- a/wolfcrypt/src/wc_slhdsa.c +++ b/wolfcrypt/src/wc_slhdsa.c @@ -189,6 +189,18 @@ wc_static_assert(SLHDSA_MAX_MSG_SZ <= 255); * declarations and the ForceZero() sizes so they cannot drift. */ #define SLHDSA_SHAKE_X4_STATE_W (25 * 4) +/* WOTS+ chain values, padded for the widest batch of sixteen lanes. */ +#define SLHDSA_WOTS_SK_SZ ((SLHDSA_MAX_MSG_SZ + 15) * SLHDSA_MAX_N) + +#if !defined(WOLFSSL_WC_SLHDSA_SMALL_MEM) || \ + (defined(USE_INTEL_SPEEDUP) && !defined(WOLFSSL_WC_SLHDSA_SMALL) && \ + (!defined(WOLFSSL_SLHDSA_NO_SHAKE) || \ + (defined(WOLFSSL_SLHDSA_SHA2) && \ + defined(WOLFSSL_SLHDSA_HAVE_INTEL_AVX512) && \ + defined(WC_SHA256_N_WAY)))) + #define SLHDSA_NEED_WOTS_SK_BUF +#endif + #ifndef WC_SLHDSA_ALL_NO_256F /* Maximum number of bytes to produce from digest of message. */ #define SLHDSA_MAX_MD 49 @@ -3081,36 +3093,31 @@ static int slhdsakey_chain_x8(byte* sk, const byte* pk_seed, byte* addr, * @param [in] pk_seed Public key seed. * @param [in, out] addr HashAddress buffer. * @param [in, out] sk_addr WOTS+ PRF HashAddress buffer. + * @param [in, out] sk Buffer for the chain values. * @return 0 on success. * @return SHAKE-256 error return code on digest failure. * @return MEMORY_E on dynamic memory allocation failure. */ static int slhdsakey_wots_pkgen_chain_x8(SlhDsaKey* key, const byte* sk_seed, - const byte* pk_seed, byte* addr, byte* sk_addr) + const byte* pk_seed, byte* addr, byte* sk_addr, byte* sk) { int ret = 0; int i; byte n = key->params->n; byte len = key->params->len; - WC_DECLARE_VAR(sk, byte, (SLHDSA_MAX_MSG_SZ + 7) * SLHDSA_MAX_N, - key->heap); - WC_ALLOC_VAR_EX(sk, byte, (SLHDSA_MAX_MSG_SZ + 7) * SLHDSA_MAX_N, - key->heap, DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E); - if (ret == 0) { - /* The last group runs past len - the PRF and the chains cover a whole - * eight - and the extra results are never read. */ - for (i = 0; i < len; i += SLHDSA_X8) { - ret = slhdsakey_hash_prf_x8(pk_seed, sk_seed, sk_addr, n, (byte)i, - sk + i * n, key->heap); - if (ret != 0) { - break; - } - ret = slhdsakey_chain_x8(sk + i * n, pk_seed, addr, (byte)i, n, - key->heap); - if (ret != 0) { - break; - } + /* The last group runs past len - the PRF and the chains cover a whole + * eight - and the extra results are never read. */ + for (i = 0; i < len; i += SLHDSA_X8) { + ret = slhdsakey_hash_prf_x8(pk_seed, sk_seed, sk_addr, n, (byte)i, + sk + i * n, key->heap); + if (ret != 0) { + break; + } + ret = slhdsakey_chain_x8(sk + i * n, pk_seed, addr, (byte)i, n, + key->heap); + if (ret != 0) { + break; } } if (ret == 0) { @@ -3120,10 +3127,9 @@ static int slhdsakey_wots_pkgen_chain_x8(SlhDsaKey* key, const byte* sk_seed, /* On error sk still holds secret WOTS+ leaves; on success it is * overwritten with public chain values. The batch fills up to an * eight-lane multiple beyond len, so wipe the whole buffer. */ - if ((ret != 0) && WC_VAR_OK(sk)) { + if (ret != 0) { ForceZero(sk, (SLHDSA_MAX_MSG_SZ + 7) * SLHDSA_MAX_N); } - WC_FREE_VAR_EX(sk, key->heap, DYNAMIC_TYPE_SLHDSA); return ret; } @@ -4191,36 +4197,32 @@ static int slhdsakey_chain_idx_32(SlhDsaKey* key, byte* sk, * 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. + * @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. + * @param [in, out] sk Buffer for the chain values. * @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) + const byte* pk_seed, byte* addr, byte* sk_addr, byte* sk) { 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; - } + 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) { @@ -4238,10 +4240,9 @@ static int slhdsakey_wots_pkgen_chain_x4_16(SlhDsaKey* key, const byte* sk_seed, /* On error sk still holds secret WOTS+ leaves; on success it is overwritten * with public chain values. The x4 PRF fills up to a 4-lane multiple * (beyond len), so wipe the whole buffer. */ - if ((ret != 0) && WC_VAR_OK(sk)) { + if (ret != 0) { ForceZero(sk, (SLHDSA_MAX_MSG_SZ + 3) * 16); } - WC_FREE_VAR_EX(sk, key->heap, DYNAMIC_TYPE_SLHDSA); return ret; } #endif @@ -4265,36 +4266,32 @@ static int slhdsakey_wots_pkgen_chain_x4_16(SlhDsaKey* key, const byte* sk_seed, * 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. + * @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. + * @param [in, out] sk Buffer for the chain values. * @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) + const byte* pk_seed, byte* addr, byte* sk_addr, byte* sk) { 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; - } + 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) { @@ -4312,10 +4309,9 @@ static int slhdsakey_wots_pkgen_chain_x4_24(SlhDsaKey* key, const byte* sk_seed, /* On error sk still holds secret WOTS+ leaves; on success it is overwritten * with public chain values. The x4 PRF fills up to a 4-lane multiple * (beyond len), so wipe the whole buffer. */ - if ((ret != 0) && WC_VAR_OK(sk)) { + if (ret != 0) { ForceZero(sk, (SLHDSA_MAX_MSG_SZ + 3) * 24); } - WC_FREE_VAR_EX(sk, key->heap, DYNAMIC_TYPE_SLHDSA); return ret; } #endif @@ -4339,36 +4335,32 @@ static int slhdsakey_wots_pkgen_chain_x4_24(SlhDsaKey* key, const byte* sk_seed, * 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. + * @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. + * @param [in, out] sk Buffer for the chain values. * @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) + const byte* pk_seed, byte* addr, byte* sk_addr, byte* sk) { 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; - } + 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) { @@ -4386,10 +4378,9 @@ static int slhdsakey_wots_pkgen_chain_x4_32(SlhDsaKey* key, const byte* sk_seed, /* On error sk still holds secret WOTS+ leaves; on success it is overwritten * with public chain values. The x4 PRF fills up to a 4-lane multiple * (beyond len), so wipe the whole buffer. */ - if ((ret != 0) && WC_VAR_OK(sk)) { + if (ret != 0) { ForceZero(sk, (SLHDSA_MAX_MSG_SZ + 3) * 32); } - WC_FREE_VAR_EX(sk, key->heap, DYNAMIC_TYPE_SLHDSA); return ret; } #endif @@ -4412,16 +4403,17 @@ static int slhdsakey_wots_pkgen_chain_x4_32(SlhDsaKey* key, const byte* sk_seed, * 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. + * @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. + * @param [in, out] sk Buffer for the chain values. * @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) + const byte* pk_seed, word32* adrs, word32* sk_adrs, byte* sk) { int ret = 0; byte sk_addr[SLHDSA_HA_SZ]; @@ -4436,27 +4428,27 @@ static int slhdsakey_wots_pkgen_chain_x4(SlhDsaKey* key, const byte* sk_seed, /* Eight lanes when the whole Keccak state fits in registers. */ if (IS_INTEL_AVX512(cpuid_flags)) { return slhdsakey_wots_pkgen_chain_x8(key, sk_seed, pk_seed, addr, - sk_addr); + sk_addr, sk); } #endif #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); + sk_addr, sk); } 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); + sk_addr, sk); } 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); + sk_addr, sk); } else #endif @@ -4482,11 +4474,13 @@ static int slhdsakey_wots_pkgen_chain_x4(SlhDsaKey* key, const byte* sk_seed, * @param [in] pk_seed Public key seed. * @param [in] adrs HashAddress set. * @param [in, out] sk_adrs WOTS+ PRF HashAddress set. + * @param [in, out] sk Buffer for the chain values. * @return 0 on success. * @return MEMORY_E on dynamic memory allocation failure. */ static int slhdsakey_wots_pkgen_chain_sha2_x16(SlhDsaKey* key, - const byte* sk_seed, const byte* pk_seed, word32* adrs, word32* sk_adrs) + const byte* sk_seed, const byte* pk_seed, word32* adrs, word32* sk_adrs, + byte* sk) { int ret = 0; int i; @@ -4495,18 +4489,12 @@ static int slhdsakey_wots_pkgen_chain_sha2_x16(SlhDsaKey* key, byte len = key->params->len; byte ac[SLHDSA_HAC_SZ]; word32 cav[SLHDSA_SHA2_X16]; - WC_DECLARE_VAR(sk, byte, (SLHDSA_MAX_MSG_SZ + 15) * SLHDSA_MAX_N, - key->heap); WC_DECLARE_VAR(buf, byte, SLHDSA_SHA2_X16 * WC_SHA256_BLOCK_SIZE, key->heap); WC_DECLARE_VAR(st, word32, SLHDSA_SHA2_X16 * 8, key->heap); - WC_ALLOC_VAR_EX(sk, byte, (SLHDSA_MAX_MSG_SZ + 15) * SLHDSA_MAX_N, + WC_ALLOC_VAR_EX(buf, byte, SLHDSA_SHA2_X16 * WC_SHA256_BLOCK_SIZE, key->heap, DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E); - if (ret == 0) { - WC_ALLOC_VAR_EX(buf, byte, SLHDSA_SHA2_X16 * WC_SHA256_BLOCK_SIZE, - key->heap, DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E); - } if (ret == 0) { WC_ALLOC_VAR_EX(st, word32, SLHDSA_SHA2_X16 * 8, key->heap, DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E); @@ -4551,8 +4539,8 @@ static int slhdsakey_wots_pkgen_chain_sha2_x16(SlhDsaKey* key, /* On error sk still holds secret WOTS+ leaves; on success it is * overwritten with public chain values. The batch runs past len, so wipe * the whole buffer. */ - if ((ret != 0) && WC_VAR_OK(sk)) { - ForceZero(sk, (SLHDSA_MAX_MSG_SZ + 15) * SLHDSA_MAX_N); + if (ret != 0) { + ForceZero(sk, SLHDSA_WOTS_SK_SZ); } /* buf and st held secret chain values. */ if (WC_VAR_OK(buf)) { @@ -4563,7 +4551,6 @@ static int slhdsakey_wots_pkgen_chain_sha2_x16(SlhDsaKey* key, } WC_FREE_VAR_EX(st, key->heap, DYNAMIC_TYPE_SLHDSA); WC_FREE_VAR_EX(buf, key->heap, DYNAMIC_TYPE_SLHDSA); - WC_FREE_VAR_EX(sk, key->heap, DYNAMIC_TYPE_SLHDSA); return ret; } @@ -4722,17 +4709,17 @@ static int slhdsakey_wots_sign_chain_sha2_x16(SlhDsaKey* key, const byte* msg, * 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. + * @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. + * @param [in, out] sk_buf Buffer for the chain values. * @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) + const byte* pk_seed, word32* adrs, word32* sk_adrs, byte* sk_buf) { int ret = 0; int i; @@ -4740,31 +4727,26 @@ static int slhdsakey_wots_pkgen_chain_c(SlhDsaKey* key, const byte* sk_seed, 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); + byte* sk = sk_buf; - 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) - XMEMSET(sk, 0, (SLHDSA_MAX_MSG_SZ + 3) * SLHDSA_MAX_N); - 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; - } + XMEMSET(sk, 0, (SLHDSA_MAX_MSG_SZ + 3) * SLHDSA_MAX_N); + /* 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) { @@ -4773,13 +4755,14 @@ static int slhdsakey_wots_pkgen_chain_c(SlhDsaKey* key, const byte* sk_seed, } /* On error sk still holds secret WOTS+ leaves; on success it is overwritten * with public chain values (generic path fills exactly len entries). */ - if ((ret != 0) && WC_VAR_OK(sk)) { + if (ret != 0) { ForceZero(sk, (word32)len * n); } - WC_FREE_VAR_EX(sk, key->heap, DYNAMIC_TYPE_SLHDSA); #else byte sk[SLHDSA_MAX_N]; + (void)sk_buf; + /* Step 4. len consecutive addresses. */ for (i = 0; i < len; i++) { /* Step 5. Set chain address for WOTS PRF. */ @@ -4833,17 +4816,18 @@ static int slhdsakey_wots_pkgen_chain_c(SlhDsaKey* key, const byte* sk_seed, * 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. + * @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. + * @param [in, out] sk_buf Buffer for the chain values. * @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) + const byte* pk_seed, word32* adrs, byte* node, byte* sk_buf) { int ret; byte n = key->params->n; @@ -4875,7 +4859,7 @@ static int slhdsakey_wots_pkgen(SlhDsaKey* key, const byte* sk_seed, ret = SAVE_VECTOR_REGISTERS2(); if (ret == 0) { ret = slhdsakey_wots_pkgen_chain_x4(key, sk_seed, pk_seed, - adrs, sk_adrs); + adrs, sk_adrs, sk_buf); RESTORE_VECTOR_REGISTERS(); } } @@ -4892,7 +4876,7 @@ static int slhdsakey_wots_pkgen(SlhDsaKey* key, const byte* sk_seed, ret = SAVE_VECTOR_REGISTERS2(); if (ret == 0) { ret = slhdsakey_wots_pkgen_chain_sha2_x16(key, sk_seed, - pk_seed, adrs, sk_adrs); + pk_seed, adrs, sk_adrs, sk_buf); RESTORE_VECTOR_REGISTERS(); } } @@ -4900,7 +4884,7 @@ static int slhdsakey_wots_pkgen(SlhDsaKey* key, const byte* sk_seed, #endif { ret = slhdsakey_wots_pkgen_chain_c(key, sk_seed, pk_seed, adrs, - sk_adrs); + sk_adrs, sk_buf); } } if (ret == 0) { @@ -6075,12 +6059,13 @@ static int slhdsakey_wots_pk_from_sig(SlhDsaKey* key, const byte* sig, * @param [in] pk_seed Public key seed. * @param [in, out] adrs HashAddress - WOTS HASH. * @param [out] node Root node. + * @param [in, out] sk_buf Buffer for the WOTS+ chain values. * @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 z, const byte* pk_seed, word32* adrs, byte* node, byte* sk_buf) { int ret = 0; @@ -6091,7 +6076,7 @@ static int slhdsakey_xmss_node(SlhDsaKey* key, const byte* sk_seed, int i, /* 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); + ret = slhdsakey_wots_pkgen(key, sk_seed, pk_seed, adrs, node, sk_buf); } else { WC_DECLARE_VAR(nodes, byte, (SLHDSA_MAX_H_M + 2) * SLHDSA_MAX_N, @@ -6112,7 +6097,7 @@ static int slhdsakey_xmss_node(SlhDsaKey* key, const byte* sk_seed, int i, 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); + nodes + ((word32)z - 1U + (j & 1U)) * n, sk_buf); if (ret != 0) { break; } @@ -6184,12 +6169,13 @@ static int slhdsakey_xmss_node(SlhDsaKey* key, const byte* sk_seed, int i, * @param [in] pk_seed Public key seed. * @param [in, out] adrs HashAddress - WOTS HASH. * @param [out] node Root node. + * @param [in, out] sk_buf Buffer for the WOTS+ chain values. * @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 z, const byte* pk_seed, word32* adrs, byte* node, byte* sk_buf) { int ret; byte nodes[2 * SLHDSA_MAX_N]; @@ -6201,18 +6187,18 @@ static int slhdsakey_xmss_node(SlhDsaKey* key, const byte* sk_seed, int i, /* 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); + ret = slhdsakey_wots_pkgen(key, sk_seed, pk_seed, adrs, node, sk_buf); } 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); + nodes, sk_buf); 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); + adrs, nodes + n, sk_buf); } if (ret == 0) { /* Steps 8-10: Step type, height and index for TREE. */ @@ -6250,13 +6236,14 @@ static int slhdsakey_xmss_node(SlhDsaKey* key, const byte* sk_seed, int i, * @param [in] adrs HashAddress. * @param [out] sig_xmss XMSS signature. * len n-byte nodes and h' authentication nodes. + * @param [in, out] sk_buf Buffer for the WOTS+ chain values. * @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) + byte* sig_xmss, byte* sk_buf) { int ret = WC_NO_ERR_TRACE(BAD_FUNC_ARG); byte n = key->params->n; @@ -6273,7 +6260,7 @@ static int slhdsakey_xmss_sign(SlhDsaKey* key, const byte* m, word32 k = i ^ 1; /* Step 3: Calculate authentication node. */ ret = slhdsakey_xmss_node(key, sk_seed, (int)k, j, pk_seed, adrs, - auth); + auth, sk_buf); if (ret != 0) { break; } @@ -6431,7 +6418,7 @@ 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; + int ret = 0; HashAddress adrs; byte root[SLHDSA_MAX_N]; byte n = key->params->n; @@ -6440,14 +6427,24 @@ static int slhdsakey_ht_sign(SlhDsaKey* key, const byte* pk_fors, byte d = key->params->d; int j; word32 mask = ((word32)1U << h_m) - 1U; +#ifdef SLHDSA_NEED_WOTS_SK_BUF + WC_DECLARE_VAR(sk_buf, byte, SLHDSA_WOTS_SK_SZ, key->heap); - /* 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); + WC_ALLOC_VAR_EX(sk_buf, byte, SLHDSA_WOTS_SK_SZ, key->heap, + DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E); +#else + byte* sk_buf = NULL; +#endif + + if (ret == 0) { + /* 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, sk_buf); + } if (ret == 0) { /* Step 5: Compute root/public key from signature. */ ret = slhdsakey_xmss_pk_from_sig(key, idx_leaf, sig_ht, pk_fors, @@ -6468,7 +6465,7 @@ static int slhdsakey_ht_sign(SlhDsaKey* key, const byte* pk_fors, 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); + adrs, sig_ht, sk_buf); if (ret != 0) { break; } @@ -6486,6 +6483,9 @@ static int slhdsakey_ht_sign(SlhDsaKey* key, const byte* pk_fors, } } +#ifdef SLHDSA_NEED_WOTS_SK_BUF + WC_FREE_VAR_EX(sk_buf, key->heap, DYNAMIC_TYPE_SLHDSA); +#endif return ret; } #endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */ @@ -9265,6 +9265,42 @@ static void slhdsakey_memzero_check_hash(SlhDsaKey* key) } #endif +/* Compute the root of the top layer XMSS tree from the seeds in the key. + * + * FIPS 205, Section 9.1, Algorithm 18, steps 1 to 3. + * + * @param [in] key SLH-DSA key with SK.seed and PK.seed set. + * @param [out] root Root node. + * @return 0 on success. + * @return MEMORY_E on dynamic memory allocation failure. + * @return Digest error return code on failure. + */ +static int slhdsakey_root_from_seed(SlhDsaKey* key, byte* root) +{ + int ret = 0; + HashAddress adrs; +#ifdef SLHDSA_NEED_WOTS_SK_BUF + WC_DECLARE_VAR(sk_buf, byte, SLHDSA_WOTS_SK_SZ, key->heap); + + WC_ALLOC_VAR_EX(sk_buf, byte, SLHDSA_WOTS_SK_SZ, key->heap, + DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E); +#else + byte* sk_buf = NULL; +#endif + + if (ret == 0) { + HA_Init(adrs); + HA_SetLayerAddress(adrs, key->params->d - 1); + ret = slhdsakey_xmss_node(key, key->sk, 0, key->params->h_m, + key->sk + 2 * key->params->n, adrs, root, sk_buf); + } + +#ifdef SLHDSA_NEED_WOTS_SK_BUF + WC_FREE_VAR_EX(sk_buf, key->heap, DYNAMIC_TYPE_SLHDSA); +#endif + return ret; +} + /* Compute the public key root from the seeds already staged in the key. * * FIPS 205, Section 9.1, Algorithm 18, steps 1 to 3. @@ -9283,7 +9319,6 @@ static int slhdsakey_compute_root(SlhDsaKey* key) { int ret = 0; byte n = key->params->n; - HashAddress adrs; #ifdef WOLFSSL_SLHDSA_SHA2 /* Pre-compute SHA2 midstates now that PK.seed is set. */ @@ -9298,13 +9333,8 @@ static int slhdsakey_compute_root(SlhDsaKey* key) #ifdef WOLFSSL_CHECK_MEM_ZERO slhdsakey_memzero_add_hash(key); #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, key->sk, 0, key->params->h_m, - key->sk + 2 * n, adrs, &key->sk[3 * n]); + /* Steps 1-3: Compute the root node of the top subtree. */ + ret = slhdsakey_root_from_seed(key, &key->sk[3 * n]); if (ret == 0) { key->flags = WC_SLHDSA_FLAG_BOTH_KEYS; } @@ -9442,7 +9472,6 @@ int wc_SlhDsaKey_MakeKeyWithRandom(SlhDsaKey* key, const byte* sk_seed, byte pct_root[SLHDSA_MAX_N]; byte pct_pub[2 * SLHDSA_MAX_N]; word32 pct_pubLen = (word32)(n * 2); - HashAddress pct_adrs; #ifdef WOLFSSL_CHECK_MEM_ZERO slhdsakey_memzero_add_hash(key); @@ -9455,10 +9484,7 @@ int wc_SlhDsaKey_MakeKeyWithRandom(SlhDsaKey* key, const byte* sk_seed, /* The public root, recomputed from the private seed. */ if (ret == 0) { - HA_Init(pct_adrs); - HA_SetLayerAddress(pct_adrs, key->params->d - 1); - ret = slhdsakey_xmss_node(key, key->sk, 0, key->params->h_m, - key->sk + 2 * n, pct_adrs, pct_root); + ret = slhdsakey_root_from_seed(key, pct_root); if ((ret == 0) && (XMEMCMP(pct_root, key->sk + 3 * n, n) != 0)) { ret = SLH_DSA_PCT_E; } @@ -11573,7 +11599,6 @@ int wc_SlhDsaKey_CheckKey(SlhDsaKey* key) if (ret == 0) { byte n = key->params->n; byte root[SLHDSA_MAX_N]; - HashAddress adrs; #ifdef WOLFSSL_CHECK_MEM_ZERO slhdsakey_memzero_add_hash(key); @@ -11582,10 +11607,7 @@ int wc_SlhDsaKey_CheckKey(SlhDsaKey* key) * Done directly rather than by regenerating the key: regeneration * overwrites the key being checked, and its key-pair test frees the * key on failure, which a validation call must never do. */ - HA_Init(adrs); - HA_SetLayerAddress(adrs, key->params->d - 1); - ret = slhdsakey_xmss_node(key, key->sk, 0, key->params->h_m, - key->sk + 2 * n, adrs, root); + ret = slhdsakey_root_from_seed(key, root); if ((ret == 0) && (XMEMCMP(root, key->sk + 3 * n, n) != 0)) { ret = WC_KEY_MISMATCH_E; } From a6c68c27319ba7bd35c4bf0290b5ef09213ad137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 30 Sep 2026 10:08:35 +0200 Subject: [PATCH 4/4] Allocate the SLH-DSA tree node buffers once per signature The iterative slhdsakey_fors_node_c and slhdsakey_xmss_node each allocated their node buffer under WOLFSSL_SMALL_STACK on every call above the leaves, and a signature makes k*(a-1) FORS and d*(h'-1) XMSS such calls, 210 for the 128s parameter sets. slhdsakey_sign now allocates one buffer and hands it to slhdsakey_fors_sign and then to slhdsakey_ht_sign, since the two never run at the same time. The FORS buffer is the larger of the two for every parameter set, and a compile time check keeps it that way. slhdsakey_root_from_seed allocates the XMSS sized one for its single call. The recursive build keeps its per-level stack pairs and gets no buffer. A FORS leaf is private until it is hashed in place, so slhdsakey_sign wipes the buffer before releasing it. Under WOLFSSL_SMALL_STACK without assembly, SHAKE signing goes from 219-352 allocations to 10-25, and with WOLFSSL_WC_SLHDSA_SMALL_MEM from 209-342 to 1, with the heap peak there unchanged. Without SMALL_MEM the buffer is held next to the WOTS+ chain buffer for the whole signature, which raises that peak by the buffer size, 480 bytes with every parameter set compiled in. Without WOLFSSL_SMALL_STACK the buffer moves from the node function's frame to slhdsakey_sign's. Public keys and signatures are unchanged, checked byte for byte against the previous code for all twelve parameter sets, with and without WOLFSSL_WC_SLHDSA_SMALL_MEM. --- wolfcrypt/src/wc_slhdsa.c | 345 +++++++++++++++++++++----------------- 1 file changed, 188 insertions(+), 157 deletions(-) diff --git a/wolfcrypt/src/wc_slhdsa.c b/wolfcrypt/src/wc_slhdsa.c index d222cdaf347..600bd63ae77 100644 --- a/wolfcrypt/src/wc_slhdsa.c +++ b/wolfcrypt/src/wc_slhdsa.c @@ -201,6 +201,12 @@ wc_static_assert(SLHDSA_MAX_MSG_SZ <= 255); #define SLHDSA_NEED_WOTS_SK_BUF #endif +/* Tree nodes of an iteratively computed FORS or XMSS subtree root. */ +#define SLHDSA_FORS_NODES_SZ ((SLHDSA_MAX_A + 1) * SLHDSA_MAX_N) +#define SLHDSA_XMSS_NODES_SZ ((SLHDSA_MAX_H_M + 2) * SLHDSA_MAX_N) +/* Signing uses one buffer for both FORS and XMSS nodes. */ +wc_static_assert(SLHDSA_FORS_NODES_SZ >= SLHDSA_XMSS_NODES_SZ); + #ifndef WC_SLHDSA_ALL_NO_256F /* Maximum number of bytes to produce from digest of message. */ #define SLHDSA_MAX_MD 49 @@ -6060,12 +6066,14 @@ static int slhdsakey_wots_pk_from_sig(SlhDsaKey* key, const byte* sig, * @param [in, out] adrs HashAddress - WOTS HASH. * @param [out] node Root node. * @param [in, out] sk_buf Buffer for the WOTS+ chain values. + * @param [in, out] nodes_buf Buffer for the tree 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_node(SlhDsaKey* key, const byte* sk_seed, int i, - int z, const byte* pk_seed, word32* adrs, byte* node, byte* sk_buf) + int z, const byte* pk_seed, word32* adrs, byte* node, byte* sk_buf, + byte* nodes_buf) { int ret = 0; @@ -6079,66 +6087,59 @@ static int slhdsakey_xmss_node(SlhDsaKey* key, const byte* sk_seed, int i, ret = slhdsakey_wots_pkgen(key, sk_seed, pk_seed, adrs, node, sk_buf); } else { - WC_DECLARE_VAR(nodes, byte, (SLHDSA_MAX_H_M + 2) * SLHDSA_MAX_N, - key->heap); + byte* nodes = nodes_buf; 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, sk_buf); - if (ret != 0) { - break; - } + /* 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, sk_buf); + 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 { + /* 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; } } - if (ret != 0) { + else { 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); + if (ret != 0) { + break; } } - - WC_FREE_VAR_EX(nodes, key->heap, DYNAMIC_TYPE_SLHDSA); + 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); + } } return ret; @@ -6170,12 +6171,14 @@ static int slhdsakey_xmss_node(SlhDsaKey* key, const byte* sk_seed, int i, * @param [in, out] adrs HashAddress - WOTS HASH. * @param [out] node Root node. * @param [in, out] sk_buf Buffer for the WOTS+ chain values. + * @param [in, out] nodes_buf Buffer for the tree 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_node(SlhDsaKey* key, const byte* sk_seed, int i, - int z, const byte* pk_seed, word32* adrs, byte* node, byte* sk_buf) + int z, const byte* pk_seed, word32* adrs, byte* node, byte* sk_buf, + byte* nodes_buf) { int ret; byte nodes[2 * SLHDSA_MAX_N]; @@ -6194,11 +6197,11 @@ static int slhdsakey_xmss_node(SlhDsaKey* key, const byte* sk_seed, int i, /* Step 6: Calculate left node recursively. */ ret = slhdsakey_xmss_node(key, sk_seed, 2 * i, z - 1, pk_seed, adrs, - nodes, sk_buf); + nodes, sk_buf, nodes_buf); 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, sk_buf); + adrs, nodes + n, sk_buf, nodes_buf); } if (ret == 0) { /* Steps 8-10: Step type, height and index for TREE. */ @@ -6237,13 +6240,14 @@ static int slhdsakey_xmss_node(SlhDsaKey* key, const byte* sk_seed, int i, * @param [out] sig_xmss XMSS signature. * len n-byte nodes and h' authentication nodes. * @param [in, out] sk_buf Buffer for the WOTS+ chain values. + * @param [in, out] nodes_buf Buffer for the tree 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, byte* sk_buf) + byte* sig_xmss, byte* sk_buf, byte* nodes_buf) { int ret = WC_NO_ERR_TRACE(BAD_FUNC_ARG); byte n = key->params->n; @@ -6260,7 +6264,7 @@ static int slhdsakey_xmss_sign(SlhDsaKey* key, const byte* m, word32 k = i ^ 1; /* Step 3: Calculate authentication node. */ ret = slhdsakey_xmss_node(key, sk_seed, (int)k, j, pk_seed, adrs, - auth, sk_buf); + auth, sk_buf, nodes_buf); if (ret != 0) { break; } @@ -6403,20 +6407,21 @@ static int slhdsakey_xmss_pk_from_sig(SlhDsaKey* key, word32 idx, * 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. + * @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. + * @param [in, out] nodes_buf Buffer for the tree 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) + byte* sig_ht, byte* nodes_buf) { int ret = 0; HashAddress adrs; @@ -6443,7 +6448,7 @@ static int slhdsakey_ht_sign(SlhDsaKey* key, const byte* pk_fors, 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, sk_buf); + adrs, sig_ht, sk_buf, nodes_buf); } if (ret == 0) { /* Step 5: Compute root/public key from signature. */ @@ -6465,7 +6470,7 @@ static int slhdsakey_ht_sign(SlhDsaKey* key, const byte* pk_fors, 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, sk_buf); + adrs, sig_ht, sk_buf, nodes_buf); if (ret != 0) { break; } @@ -7545,19 +7550,20 @@ static int slhdsakey_fors_node_x4(SlhDsaKey* key, const byte* sk_seed, word32 i, * 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. + * @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. + * @param [in, out] nodes_buf Buffer for the tree 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_fors_node_c(SlhDsaKey* key, const byte* sk_seed, word32 i, - word32 z, const byte* pk_seed, word32* adrs, byte* node) + word32 z, const byte* pk_seed, word32* adrs, byte* node, byte* nodes_buf) { int ret = 0; byte n = key->params->n; @@ -7581,85 +7587,66 @@ static int slhdsakey_fors_node_c(SlhDsaKey* key, const byte* sk_seed, word32 i, } /* Step 6: Non leaf node. */ else { - WC_DECLARE_VAR(nodes, byte, (SLHDSA_MAX_A + 1) * SLHDSA_MAX_N, - key->heap); + byte* nodes = nodes_buf; 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) { -#ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Add("slhdsa fors_node_c nodes", nodes, - (SLHDSA_MAX_A + 1) * SLHDSA_MAX_N); -#endif - /* 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 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 { + /* 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; } } - } - 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); + /* Left node - can go no higher. */ + else { + break; + } } } - - /* nodes may still hold FORS secret leaves - * (ISO/IEC 19790:2012 7.9.7). */ - if (WC_VAR_OK(nodes)) { - ForceZero(nodes, (SLHDSA_MAX_A + 1) * SLHDSA_MAX_N); -#if defined(WOLFSSL_CHECK_MEM_ZERO) && !defined(WOLFSSL_SMALL_STACK) - wc_MemZero_Check(nodes, (SLHDSA_MAX_A + 1) * SLHDSA_MAX_N); -#endif + 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; @@ -7685,19 +7672,20 @@ static int slhdsakey_fors_node_c(SlhDsaKey* key, const byte* sk_seed, word32 i, * 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. + * @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. + * @param [in, out] nodes_buf Buffer for the tree 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_fors_node_c(SlhDsaKey* key, const byte* sk_seed, word32 i, - word32 z, const byte* pk_seed, word32* adrs, byte* node) + word32 z, const byte* pk_seed, word32* adrs, byte* node, byte* nodes_buf) { int ret; byte n = key->params->n; @@ -7727,11 +7715,11 @@ static int slhdsakey_fors_node_c(SlhDsaKey* key, const byte* sk_seed, word32 i, #endif /* Step 7: Compute left node. */ ret = slhdsakey_fors_node_c(key, sk_seed, 2 * i + 0, z - 1, pk_seed, - adrs, nodes); + adrs, nodes, nodes_buf); 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); + adrs, nodes + n, nodes_buf); } if (ret == 0) { /* Step 9: Set tree height. */ @@ -7775,12 +7763,14 @@ static int slhdsakey_fors_node_c(SlhDsaKey* key, const byte* sk_seed, word32 i, * @param [in] pk_seed Public key seed. * @param [inm out] adrs FORS tree HashAddress. * @param [out] sig_fors FORS signature. + * @param [in, out] nodes_buf Buffer for the tree 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_fors_sign(SlhDsaKey* key, const byte* md, - const byte* sk_seed, const byte* pk_seed, word32* adrs, byte* sig_fors) + const byte* sk_seed, const byte* pk_seed, word32* adrs, byte* sig_fors, + byte* nodes_buf) { int ret = WC_NO_ERR_TRACE(BAD_FUNC_ARG); byte* sig_start = sig_fors; @@ -7849,7 +7839,7 @@ static int slhdsakey_fors_sign(SlhDsaKey* key, const byte* md, /* 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); + sig_fors, nodes_buf); if (ret != 0) { /* At j == 0 this slot holds a private key value. */ ForceZero(sig_fors, n); @@ -9281,20 +9271,34 @@ static int slhdsakey_root_from_seed(SlhDsaKey* key, byte* root) HashAddress adrs; #ifdef SLHDSA_NEED_WOTS_SK_BUF WC_DECLARE_VAR(sk_buf, byte, SLHDSA_WOTS_SK_SZ, key->heap); +#else + byte* sk_buf = NULL; +#endif +#ifndef WOLFSSL_WC_SLHDSA_RECURSIVE + WC_DECLARE_VAR(nodes, byte, SLHDSA_XMSS_NODES_SZ, key->heap); +#else + byte* nodes = NULL; +#endif +#ifdef SLHDSA_NEED_WOTS_SK_BUF WC_ALLOC_VAR_EX(sk_buf, byte, SLHDSA_WOTS_SK_SZ, key->heap, DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E); -#else - byte* sk_buf = NULL; +#endif +#ifndef WOLFSSL_WC_SLHDSA_RECURSIVE + WC_ALLOC_VAR_EX(nodes, byte, SLHDSA_XMSS_NODES_SZ, key->heap, + DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E); #endif if (ret == 0) { HA_Init(adrs); HA_SetLayerAddress(adrs, key->params->d - 1); ret = slhdsakey_xmss_node(key, key->sk, 0, key->params->h_m, - key->sk + 2 * key->params->n, adrs, root, sk_buf); + key->sk + 2 * key->params->n, adrs, root, sk_buf, nodes); } +#ifndef WOLFSSL_WC_SLHDSA_RECURSIVE + WC_FREE_VAR_EX(nodes, key->heap, DYNAMIC_TYPE_SLHDSA); +#endif #ifdef SLHDSA_NEED_WOTS_SK_BUF WC_FREE_VAR_EX(sk_buf, key->heap, DYNAMIC_TYPE_SLHDSA); #endif @@ -9541,13 +9545,18 @@ int wc_SlhDsaKey_MakeKeyWithRandom(SlhDsaKey* key, const byte* sk_seed, #ifndef WOLF_CRYPTO_CB_ONLY_SLHDSA static int slhdsakey_sign(SlhDsaKey* key, byte* md, byte* sig) { - int ret; + int ret = 0; HashAddress adrs; word32 t[3]; word32 l; byte pk_fors[SLHDSA_MAX_N]; byte n = key->params->n; byte* sigFors = sig; +#ifndef WOLFSSL_WC_SLHDSA_RECURSIVE + WC_DECLARE_VAR(nodes, byte, SLHDSA_FORS_NODES_SZ, key->heap); +#else + byte* nodes = NULL; +#endif #ifdef WOLFSSL_CHECK_MEM_ZERO slhdsakey_memzero_add_hash(key); @@ -9555,8 +9564,20 @@ static int slhdsakey_sign(SlhDsaKey* key, byte* md, byte* sig) /* 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); +#ifndef WOLFSSL_WC_SLHDSA_RECURSIVE + WC_ALLOC_VAR_EX(nodes, byte, SLHDSA_FORS_NODES_SZ, key->heap, + DYNAMIC_TYPE_SLHDSA, ret = MEMORY_E); +#ifdef WOLFSSL_CHECK_MEM_ZERO + if (ret == 0) { + wc_MemZero_Add("slhdsa sign nodes", nodes, SLHDSA_FORS_NODES_SZ); + } +#endif +#endif + if (ret == 0) { + /* Step 14: FORS sign message. */ + ret = slhdsakey_fors_sign(key, md, key->sk, key->sk + 2 * n, adrs, sig, + nodes); + } if (ret == 0) { /* Step 16: FORS public key from signature. */ ret = slhdsakey_fors_pk_from_sig(key, sig, md, key->sk + 2 * n, adrs, @@ -9567,7 +9588,7 @@ static int slhdsakey_sign(SlhDsaKey* key, byte* md, byte* sig) 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); + sig, nodes); } if (ret != 0) { /* Unreleased FORS secrets may be in sig @@ -9580,6 +9601,16 @@ static int slhdsakey_sign(SlhDsaKey* key, byte* md, byte* sig) slhdsakey_memzero_check_hash(key); #endif +#ifndef WOLFSSL_WC_SLHDSA_RECURSIVE + /* nodes may still hold FORS secret leaves (ISO/IEC 19790:2012 7.9.7). */ + if (WC_VAR_OK(nodes)) { + ForceZero(nodes, SLHDSA_FORS_NODES_SZ); +#if defined(WOLFSSL_CHECK_MEM_ZERO) && !defined(WOLFSSL_SMALL_STACK) + wc_MemZero_Check(nodes, SLHDSA_FORS_NODES_SZ); +#endif + } + WC_FREE_VAR_EX(nodes, key->heap, DYNAMIC_TYPE_SLHDSA); +#endif return ret; }