From db1f17079b03f9dc40c042f0b0d94a6e8c97ef35 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 13 Nov 2026 15:38:45 -0800 Subject: [PATCH] Add SLH-DSA support --- .github/workflows/build-and-run-examples.yml | 20 + .github/workflows/build-and-test-refactor.yml | 4 + docs/src/2-FAQs.md | 2 +- docs/src/5-Features.md | 2 +- examples/demo/client/wh_demo_client_all.c | 7 + examples/demo/client/wh_demo_client_crypto.c | 92 + examples/demo/client/wh_demo_client_crypto.h | 2 + examples/posix/wh_posix_client/Makefile | 4 + .../posix/wh_posix_client/user_settings.h | 13 + .../posix/wh_posix_server/user_settings.h | 8 +- src/wh_client_crypto.c | 1431 +++++++- src/wh_client_cryptocb.c | 168 +- src/wh_crypto.c | 70 +- src/wh_message_crypto.c | 282 +- src/wh_server_crypto.c | 2803 ++++++++++----- src/wh_server_keystore.c | 42 +- .../client-server/wh_test_crypto_slhdsa.c | 3038 +++++++++++++++++ .../misc/wh_test_check_struct_padding.c | 14 + test-refactor/posix/Makefile | 4 + test-refactor/wh_test_list.c | 2 + test/config/user_settings.h | 8 +- test/wh_test_check_struct_padding.c | 16 +- wolfhsm/wh_client_crypto.h | 375 +- wolfhsm/wh_common.h | 3 +- wolfhsm/wh_crypto.h | 16 +- wolfhsm/wh_message_crypto.h | 227 +- wolfhsm/wh_server_crypto.h | 13 +- wolfhsm/wh_settings.h | 11 +- 28 files changed, 7841 insertions(+), 836 deletions(-) create mode 100644 test-refactor/client-server/wh_test_crypto_slhdsa.c diff --git a/.github/workflows/build-and-run-examples.yml b/.github/workflows/build-and-run-examples.yml index 970f06924..df44fa58c 100644 --- a/.github/workflows/build-and-run-examples.yml +++ b/.github/workflows/build-and-run-examples.yml @@ -88,3 +88,23 @@ jobs: done done done + + - name: Build and run the callback-only SLH-DSA client + if: ${{ matrix.transport == 'tcp' }} + run: | + WS="$(pwd)" + SERVER_DIR="$WS/examples/posix/wh_posix_server" + CLIENT_DIR="$WS/examples/posix/wh_posix_client" + make -C "$SERVER_DIR" clean + make -C "$CLIENT_DIR" clean + DEMO_KEK=1 make -C "$SERVER_DIR" -j WOLFSSL_DIR=../../../wolfssl + SLHDSA_CB_ONLY=1 make -C "$CLIENT_DIR" -j WOLFSSL_DIR=../../../wolfssl + rm -f "$SERVER_DIR"/*.bin + cd "$SERVER_DIR" + ./Build/wh_posix_server.elf --type tcp & + SERVER_PID=$! + sleep 1 + cd "$CLIENT_DIR" + ./Build/wh_posix_client.elf --type tcp --test + kill $SERVER_PID 2>/dev/null || true + wait $SERVER_PID 2>/dev/null || true diff --git a/.github/workflows/build-and-test-refactor.yml b/.github/workflows/build-and-test-refactor.yml index ff3fbccd6..880552d33 100644 --- a/.github/workflows/build-and-test-refactor.yml +++ b/.github/workflows/build-and-test-refactor.yml @@ -113,6 +113,10 @@ jobs: if: matrix.group == 'pq-dma' run: cd test-refactor/posix && make clean && make -j DMA=1 ASAN=1 NOKEYGEN=1 LMS_VERIFY_ONLY=1 XMSS_VERIFY_ONLY=1 WOLFSSL_DIR=../../wolfssl && make run + - name: Build and test refactor DMA ASAN SLHDSA verify-only + if: matrix.group == 'pq-dma' + run: cd test-refactor/posix && make clean && make -j DMA=1 ASAN=1 SLHDSA_VERIFY_ONLY=1 WOLFSSL_DIR=../../wolfssl && make run + # Build and test ASAN build, with wolfCrypt tests enabled. - name: Build and test refactor ASAN TESTWOLFCRYPT if: matrix.group == 'wolfcrypt' diff --git a/docs/src/2-FAQs.md b/docs/src/2-FAQs.md index a196fadd9..5deae1f8e 100644 --- a/docs/src/2-FAQs.md +++ b/docs/src/2-FAQs.md @@ -117,7 +117,7 @@ Clients reach these algorithms through the wolfHSM client library, which offload ## Does wolfHSM support post-quantum cryptography? -Yes! wolfHSM supports ML-KEM (FIPS 203) for key exchange and ML-DSA (FIPS 204), LMS, and XMSS for digital signatures, with more algorithms being added every day. Clients use them through the standard wolfCrypt API like any other algorithm, and private keys can be generated on the server so they never leave the HSM. +Yes! wolfHSM supports ML-KEM (FIPS 203) for key exchange and ML-DSA (FIPS 204), SLH-DSA (FIPS 205), LMS, and XMSS for digital signatures, with more algorithms being added every day. Clients use them through the standard wolfCrypt API like any other algorithm, and private keys can be generated on the server so they never leave the HSM. Like any algorithm without hardware support, post-quantum algorithms run in wolfCrypt software on the HSM core unless the port provides a hardware driver for them. diff --git a/docs/src/5-Features.md b/docs/src/5-Features.md index 9cd600467..3d58d510c 100644 --- a/docs/src/5-Features.md +++ b/docs/src/5-Features.md @@ -110,7 +110,7 @@ The wolfHSM server exposes the full set of wolfCrypt software algorithms, and th - **Message authentication**: HMAC (over the supported hash functions) and CMAC - **Asymmetric**: RSA (encryption, signing, key generation), ECC (ECDSA, ECDH, public key derivation, key validation), Ed25519, Curve25519 - **Random number generation**: DRBG/RNG backed by the server's entropy source -- **Post-quantum cryptography**: ML-DSA (FIPS 204) and ML-KEM (FIPS 203) +- **Post-quantum cryptography**: ML-DSA (FIPS 204), SLH-DSA (FIPS 205) and ML-KEM (FIPS 203) For the authoritative list of algorithms, parameter ranges, and options, see the [wolfCrypt API reference](https://www.wolfssl.com/documentation/manuals/wolfssl/index.html). An algorithm not yet wired through the crypto callback can still be used locally against the client's own wolfCrypt instance — only operations dispatched to the client's `devId` are offloaded. diff --git a/examples/demo/client/wh_demo_client_all.c b/examples/demo/client/wh_demo_client_all.c index 2b1169dde..225e203c9 100644 --- a/examples/demo/client/wh_demo_client_all.c +++ b/examples/demo/client/wh_demo_client_all.c @@ -201,5 +201,12 @@ int wh_DemoClient_All(whClientContext* clientContext) #endif /* WOLFSSL_CMAC */ +#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) + rc = wh_DemoClient_CryptoSlhDsa(clientContext); + if (rc != 0) { + return rc; + } +#endif /* WOLFSSL_HAVE_SLHDSA && !WOLFSSL_SLHDSA_VERIFY_ONLY */ + return rc; } diff --git a/examples/demo/client/wh_demo_client_crypto.c b/examples/demo/client/wh_demo_client_crypto.c index a7c3dc91d..a7553bb30 100644 --- a/examples/demo/client/wh_demo_client_crypto.c +++ b/examples/demo/client/wh_demo_client_crypto.c @@ -44,6 +44,10 @@ #include "wolfssl/wolfcrypt/kdf.h" #endif +#ifdef WOLFSSL_HAVE_SLHDSA +#include "wolfssl/wolfcrypt/wc_slhdsa.h" +#endif /* WOLFSSL_HAVE_SLHDSA */ + #include "wh_demo_client_crypto.h" #if !defined(NO_RSA) @@ -1692,4 +1696,92 @@ int wh_DemoClient_CryptoCmacKdfCacheInputs(whClientContext* clientContext) #endif /* HAVE_CMAC_KDF && WOLFSSL_CMAC */ +#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) + +/* Generate an SLH-DSA key that stays on the server and reference it by ID. */ +int wh_DemoClient_CryptoSlhDsa(whClientContext* clientContext) +{ + int ret; + int devId = WH_CLIENT_DEVID(clientContext); + whKeyId keyId = WH_KEYID_ERASED; + SlhDsaKey pub[1]; + SlhDsaKey handle[1]; + uint8_t label[] = "slhdsa-demo"; + byte message[] = "wolfHSM SLH-DSA demo message"; + byte signature[WC_SLHDSA_SHAKE128S_SIG_LEN]; + word32 sigLen = sizeof(signature); + byte addRnd[WC_SLHDSA_MAX_SEED]; + + /* Placeholder for user-supplied random input */ + memset(addRnd, 0x42, sizeof(addRnd)); + + ret = wc_SlhDsaKey_Init(pub, SLHDSA_SHAKE128S, NULL, devId); + if (ret != 0) { + WOLFHSM_CFG_PRINTF("Failed to wc_SlhDsaKey_Init %d\n", ret); + return ret; + } + + ret = wc_SlhDsaKey_Init(handle, SLHDSA_SHAKE128S, NULL, devId); + if (ret != 0) { + WOLFHSM_CFG_PRINTF("Failed to wc_SlhDsaKey_Init %d\n", ret); + wc_SlhDsaKey_Free(pub); + return ret; + } + + /* Private key stays on the HSM; only the public key is returned */ + ret = wh_Client_SlhDsaMakeCacheKeyAndExportPublic( + clientContext, SLHDSA_SHAKE128S, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY, sizeof(label), + label, pub); + if (ret != 0) { + WOLFHSM_CFG_PRINTF("Failed to generate SLH-DSA key %d\n", ret); + goto exit; + } + + /* handle contains no key material, just the server key id */ + ret = wh_Client_SlhDsaSetKeyId(handle, keyId); + if (ret != 0) { + WOLFHSM_CFG_PRINTF("Failed to wh_Client_SlhDsaSetKeyId %d\n", ret); + goto exit; + } + + /* handle has no PK.seed, so supply the randomizer */ + ret = wc_SlhDsaKey_SignWithRandom(handle, NULL, 0, message, sizeof(message), + signature, &sigLen, addRnd); + if (ret != 0) { + WOLFHSM_CFG_PRINTF("Failed to wc_SlhDsaKey_SignWithRandom %d\n", ret); + goto exit; + } + + ret = wc_SlhDsaKey_Verify(pub, NULL, 0, message, sizeof(message), signature, + sigLen); + if (ret != 0) { + WOLFHSM_CFG_PRINTF("Failed to wc_SlhDsaKey_Verify %d\n", ret); + goto exit; + } + + /* A tampered signature must not verify */ + signature[0] ^= 0xFF; + if (wc_SlhDsaKey_Verify(pub, NULL, 0, message, sizeof(message), signature, + sigLen) == 0) { + WOLFHSM_CFG_PRINTF("SLH-DSA verified a tampered signature\n"); + ret = -1; + goto exit; + } + + WOLFHSM_CFG_PRINTF("SLH-DSA sign/verify with a server-resident key: " + "SUCCESS\n"); + ret = 0; + +exit: + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(clientContext, keyId); + } + wc_SlhDsaKey_Free(handle); + wc_SlhDsaKey_Free(pub); + return ret; +} + +#endif /* WOLFSSL_HAVE_SLHDSA && !WOLFSSL_SLHDSA_VERIFY_ONLY */ + #endif /* WOLFHSM_CFG_NO_CRYPTO */ diff --git a/examples/demo/client/wh_demo_client_crypto.h b/examples/demo/client/wh_demo_client_crypto.h index 72fbb187d..f7c14417c 100644 --- a/examples/demo/client/wh_demo_client_crypto.h +++ b/examples/demo/client/wh_demo_client_crypto.h @@ -30,4 +30,6 @@ int wh_DemoClient_CryptoCmacKdfExport(whClientContext* clientContext); int wh_DemoClient_CryptoCmacKdfCache(whClientContext* clientContext); int wh_DemoClient_CryptoCmacKdfCacheInputs(whClientContext* clientContext); +int wh_DemoClient_CryptoSlhDsa(whClientContext* clientContext); + #endif /* !DEMO_CLIENT_CRYPTO_H_ */ diff --git a/examples/posix/wh_posix_client/Makefile b/examples/posix/wh_posix_client/Makefile index f193e361d..8826579c6 100644 --- a/examples/posix/wh_posix_client/Makefile +++ b/examples/posix/wh_posix_client/Makefile @@ -143,6 +143,10 @@ ifeq ($(AUTH),1) DEF += -DWOLFHSM_CFG_ENABLE_AUTHENTICATION endif +ifeq ($(SLHDSA_CB_ONLY),1) +DEF += -DWOLFHSM_CFG_SLHDSA_CB_ONLY +endif + else DEF += -DWOLFHSM_CFG_NO_CRYPTO endif diff --git a/examples/posix/wh_posix_client/user_settings.h b/examples/posix/wh_posix_client/user_settings.h index d25542bc9..690e5a1a5 100644 --- a/examples/posix/wh_posix_client/user_settings.h +++ b/examples/posix/wh_posix_client/user_settings.h @@ -44,6 +44,19 @@ #define WOLFSSL_CMAC #define HAVE_HKDF +/* SLH-DSA: only 128s fits in the default WOLFHSM_CFG_COMM_DATA_LEN */ +#define WOLFSSL_HAVE_SLHDSA +#define WOLFSSL_SHA3 +#define WOLFSSL_SHAKE128 +#define WOLFSSL_SHAKE256 +#define WOLFSSL_SLHDSA_PARAM_NO_128F +#define WOLFSSL_SLHDSA_PARAM_NO_192 +#define WOLFSSL_SLHDSA_PARAM_NO_256 + +#ifdef WOLFHSM_CFG_SLHDSA_CB_ONLY +#define WOLF_CRYPTO_CB_ONLY_SLHDSA +#endif /* WOLFHSM_CFG_SLHDSA_CB_ONLY */ + /* wolfCrypt benchmark settings */ #define NO_MAIN_DRIVER #define BENCH_EMBEDDED diff --git a/examples/posix/wh_posix_server/user_settings.h b/examples/posix/wh_posix_server/user_settings.h index efb06ce4b..3ae577a14 100644 --- a/examples/posix/wh_posix_server/user_settings.h +++ b/examples/posix/wh_posix_server/user_settings.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * @@ -150,6 +150,12 @@ extern "C" { #define WOLFSSL_MLDSA_NO_MAKE_KEY #endif +/* SLH-DSA: only 128s fits in the default WOLFHSM_CFG_COMM_DATA_LEN */ +#define WOLFSSL_HAVE_SLHDSA +#define WOLFSSL_SLHDSA_PARAM_NO_128F +#define WOLFSSL_SLHDSA_PARAM_NO_192 +#define WOLFSSL_SLHDSA_PARAM_NO_256 + /* ML-KEM Options */ #define WOLFSSL_HAVE_MLKEM diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index 3c18a6a6b..488898e01 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * @@ -11454,6 +11454,1435 @@ int wh_Client_MlDsaCheckPrivKeyDma(whClientContext* ctx, wc_MlDsaKey* key, #endif /* WOLFHSM_CFG_DMA */ #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + +static int _SlhDsaKeyParam(const SlhDsaKey* key) +{ + if ((key == NULL) || (key->params == NULL)) { + return WC_SLHDSA_DEFAULT_PARAM; + } + return (int)key->params->param; +} + +int wh_Client_SlhDsaSetKeyId(SlhDsaKey* key, whKeyId keyId) +{ + if (key == NULL) { + return WH_ERROR_BADARGS; + } + + key->devCtx = WH_KEYID_TO_DEVCTX(keyId); + + return WH_ERROR_OK; +} + +int wh_Client_SlhDsaGetKeyId(SlhDsaKey* key, whKeyId* outId) +{ + if ((key == NULL) || (outId == NULL)) { + return WH_ERROR_BADARGS; + } + + *outId = WH_DEVCTX_TO_KEYID(key->devCtx); + + return WH_ERROR_OK; +} + +int wh_Client_SlhDsaImportKey(whClientContext* ctx, SlhDsaKey* key, + whKeyId* inout_keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label) +{ + int ret = WH_ERROR_OK; + whKeyId key_id = WH_KEYID_ERASED; + byte buffer[WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE]; + uint16_t buffer_len = 0; + uint8_t* dataPtr = NULL; + + if ((ctx == NULL) || (key == NULL) || + ((label_len != 0) && (label == NULL))) { + return WH_ERROR_BADARGS; + } + + if (inout_keyId != NULL) { + key_id = *inout_keyId; + } + + ret = wh_Crypto_SlhDsaSerializeKeyDer(key, sizeof(buffer), buffer, + &buffer_len); + if (ret == WH_ERROR_OK) { + /* Cache the key and get the keyID */ + ret = wh_Client_KeyCache(ctx, flags, label, label_len, buffer, + buffer_len, &key_id); + if ((ret == WH_ERROR_OK) && (inout_keyId != NULL)) { + *inout_keyId = key_id; + } + /* The request copied the DER into the shared packet buffer */ + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr != NULL) { + wc_ForceZero(dataPtr, WOLFHSM_CFG_COMM_DATA_LEN); + } + } + + WH_DEBUG_CLIENT_VERBOSE("label:%.*s ret:%d keyid:%u\n", label_len, label, + ret, key_id); + /* The DER above contains private key material. */ + wc_ForceZero(buffer, sizeof(buffer)); + return ret; +} + +int wh_Client_SlhDsaExportKey(whClientContext* ctx, whKeyId keyId, + SlhDsaKey* key, uint16_t label_len, + uint8_t* label) +{ + int ret = WH_ERROR_OK; + byte buffer[WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE]; + uint16_t buffer_len = sizeof(buffer); + + if ((ctx == NULL) || WH_KEYID_ISERASED(keyId) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = + wh_Client_KeyExport(ctx, keyId, label, label_len, buffer, &buffer_len); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_SlhDsaDeserializeKeyDer(buffer, buffer_len, key); + } + + WH_DEBUG_CLIENT_VERBOSE("keyid:%x key:%p ret:%d label:%.*s\n", keyId, key, + ret, (int)label_len, label); + /* The DER above contains private key material. */ + wc_ForceZero(buffer, sizeof(buffer)); + return ret; +} + +int wh_Client_SlhDsaExportPublicKey(whClientContext* ctx, whKeyId keyId, + SlhDsaKey* key, uint16_t label_len, + uint8_t* label) +{ + int ret; + byte buffer[WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE] = {0}; + uint16_t buffer_len = sizeof(buffer); + + if ((ctx == NULL) || WH_KEYID_ISERASED(keyId) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_KeyExportPublic(ctx, keyId, WH_KEY_ALGO_SLHDSA, label, + label_len, buffer, &buffer_len); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_SlhDsaDeserializeKeyDer(buffer, buffer_len, key); + } + return ret; +} + +static int _SlhDsaMakeKey(whClientContext* ctx, int param, const byte* seed, + word32 seedSz, whKeyId* inout_key_id, + whNvmFlags flags, uint16_t label_len, + const uint8_t* label, SlhDsaKey* key, + int* out_committed) +{ + int ret = WH_ERROR_OK; + whKeyId key_id = WH_KEYID_ERASED; + uint8_t* dataPtr = NULL; + whMessageCrypto_SlhDsaKeyGenRequest* req = NULL; + whMessageCrypto_SlhDsaKeyGenResponse* res = NULL; + uint16_t pkType = WC_PK_TYPE_PQC_SIG_KEYGEN; + + if (out_committed != NULL) { + *out_committed = 0; + } + + if ((ctx == NULL) || ((seed == NULL) && (seedSz > 0))) { + return WH_ERROR_BADARGS; + } + + /* Get data pointer from the context to use as request/response storage */ + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + /* Setup generic header and get pointer to request data */ + req = (whMessageCrypto_SlhDsaKeyGenRequest*)_createCryptoRequestWithSubtype( + dataPtr, pkType, WC_PQC_SIG_TYPE_SLHDSA, ctx->cryptoAffinity); + + /* Use the supplied key id if provided */ + if (inout_key_id != NULL) { + key_id = *inout_key_id; + } + + { + /* Request Message */ + uint16_t group = WH_MESSAGE_GROUP_CRYPTO; + uint16_t action = WC_ALGO_TYPE_PK; + + uint64_t total_len = + (uint64_t)sizeof(whMessageCrypto_GenericRequestHeader) + + sizeof(*req) + seedSz; + + if (total_len <= WOLFHSM_CFG_COMM_DATA_LEN) { + uint16_t req_len = (uint16_t)total_len; + + memset(req, 0, sizeof(*req)); + req->param = param; + req->sz = 0; + req->flags = flags; + req->keyId = key_id; + req->seedSz = seedSz; + if (seedSz > 0) { + memcpy((uint8_t*)(req + 1), seed, seedSz); + } + if ((label != NULL) && (label_len > 0)) { + if (label_len > WH_NVM_LABEL_LEN) { + label_len = WH_NVM_LABEL_LEN; + } + memcpy(req->label, label, label_len); + } + + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + if (ret == WH_ERROR_OK) { + uint16_t res_len = 0; + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + WOLFHSM_CFG_COMM_DATA_LEN, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + + if (ret == WH_ERROR_OK) { + /* Get response pointer, validate header */ + ret = _getCryptoResponse(dataPtr, pkType, (uint8_t**)&res); + /* wolfCrypt allows positive error codes on success in some + * scenarios */ + if (ret >= 0) { + const size_t min_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res); + if (res_len < min_sz) { + ret = WH_ERROR_ABORTED; + } + } + if (ret >= 0) { + /* Key is cached on server or is ephemeral */ + key_id = (whKeyId)(res->keyId); + if (out_committed != NULL) { + *out_committed = 1; + } + + /* Update output variable if requested */ + if (inout_key_id != NULL) { + *inout_key_id = key_id; + } + + /* Update the context if provided */ + if (key != NULL) { + uint16_t der_size = (uint16_t)(res->len); + const size_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res); + /* Set the key_id. ERASED for EPHEMERAL, cached id + * otherwise. */ + wh_Client_SlhDsaSetKeyId(key, key_id); + + /* Reject an empty or truncated key DER */ + if (der_size == 0) { + ret = WH_ERROR_ABORTED; + } + else if ((res_len < hdr_sz) || + (res->len > (res_len - hdr_sz))) { + ret = WH_ERROR_ABORTED; + } + else { + uint8_t* key_der = (uint8_t*)(res + 1); + ret = wh_Crypto_SlhDsaDeserializeKeyDer( + key_der, der_size, key); + } + } + } + } + } + } + else { + ret = WH_ERROR_REQUEST_SIZE; + } + } + if (dataPtr != NULL) { + wc_ForceZero(dataPtr, WOLFHSM_CFG_COMM_DATA_LEN); + } + return ret; +} + +int wh_Client_SlhDsaMakeCacheKey(whClientContext* ctx, int param, + whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, uint8_t* label) +{ + if ((inout_key_id == NULL) || (flags & WH_NVM_FLAGS_EPHEMERAL)) { + return WH_ERROR_BADARGS; + } + + return _SlhDsaMakeKey(ctx, param, NULL, 0, inout_key_id, flags, label_len, + label, NULL, NULL); +} + +/* Make pub a handle to the cached key, or evict a key the server cached */ +static int _SlhDsaCacheKeyFinish(whClientContext* ctx, int ret, int committed, + whKeyId* inout_key_id, SlhDsaKey* pub) +{ + if (ret >= 0) { + /* Set again: the public-key deserialize re-inits pub and clears it */ + wh_Client_SlhDsaSetKeyId(pub, *inout_key_id); + pub->devId = WH_CLIENT_DEVID(ctx); + } + else if (committed && !WH_KEYID_ISERASED(*inout_key_id)) { + (void)wh_Client_KeyEvict(ctx, *inout_key_id); + *inout_key_id = WH_KEYID_ERASED; + } + return ret; +} + +int wh_Client_SlhDsaMakeCacheKeyAndExportPublic( + whClientContext* ctx, int param, whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, const uint8_t* label, SlhDsaKey* pub) +{ + int ret; + int committed = 0; + + if ((ctx == NULL) || (inout_key_id == NULL) || (pub == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Ephemeral keygen belongs to the export path, not the cache path. */ + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + return WH_ERROR_BADARGS; + } + + ret = _SlhDsaMakeKey(ctx, param, NULL, 0, inout_key_id, flags, label_len, + label, pub, &committed); + return _SlhDsaCacheKeyFinish(ctx, ret, committed, inout_key_id, pub); +} + +int wh_Client_SlhDsaMakeExportKey(whClientContext* ctx, int param, + SlhDsaKey* key) +{ + if (key == NULL) { + return WH_ERROR_BADARGS; + } + + return _SlhDsaMakeKey(ctx, param, NULL, 0, NULL, WH_NVM_FLAGS_EPHEMERAL, 0, + NULL, key, NULL); +} + +int wh_Client_SlhDsaMakeExportKeyFromSeed(whClientContext* ctx, int param, + const byte* seed, word32 seedSz, + SlhDsaKey* key) +{ + if ((key == NULL) || (seed == NULL) || (seedSz == 0)) { + return WH_ERROR_BADARGS; + } + + return _SlhDsaMakeKey(ctx, param, seed, seedSz, NULL, + WH_NVM_FLAGS_EPHEMERAL, 0, NULL, key, NULL); +} + +int wh_Client_SlhDsaMakeCacheKeyFromSeed(whClientContext* ctx, int param, + const byte* seed, word32 seedSz, + whKeyId* inout_key_id, + whNvmFlags flags, uint16_t label_len, + uint8_t* label) +{ + if ((inout_key_id == NULL) || (seed == NULL) || (seedSz == 0) || + (flags & WH_NVM_FLAGS_EPHEMERAL)) { + return WH_ERROR_BADARGS; + } + + return _SlhDsaMakeKey(ctx, param, seed, seedSz, inout_key_id, flags, + label_len, label, NULL, NULL); +} + +int wh_Client_SlhDsaSign(whClientContext* ctx, const byte* in, word32 in_len, + byte* out, word32* inout_len, SlhDsaKey* key, + const byte* context, byte contextLen, + word32 preHashType, const byte* addRnd, byte addRndSz, + int randomized, int isMPrime) +{ + int ret = WH_ERROR_OK; + whMessageCrypto_SlhDsaSignRequest* req = NULL; + whMessageCrypto_SlhDsaSignResponse* res = NULL; + uint8_t* dataPtr = NULL; + uint16_t pkType; + + /* Transaction state */ + whKeyId key_id; + int evict = 0; + + if ((ctx == NULL) || (key == NULL) || ((in == NULL) && (in_len > 0)) || + (out == NULL) || (inout_len == NULL) || + ((addRnd == NULL) && (addRndSz > 0)) || + ((context == NULL) && (contextLen > 0))) { + return WH_ERROR_BADARGS; + } + + /* M' already contains any context, so drop it */ + if (isMPrime != 0) { + contextLen = 0; + } + + pkType = + (isMPrime != 0) ? WC_PK_TYPE_PQC_SIG_SIGN_MSG : WC_PK_TYPE_PQC_SIG_SIGN; + + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + + /* Import key if necessary */ + if (WH_KEYID_ISERASED(key_id)) { + /* Must import the key to the server and evict it afterwards */ + uint8_t keyLabel[] = "TempSlhDsaSign"; + whNvmFlags flags = WH_NVM_FLAGS_USAGE_SIGN; + + ret = wh_Client_SlhDsaImportKey(ctx, key, &key_id, flags, + sizeof(keyLabel), keyLabel); + if (ret == WH_ERROR_OK) { + evict = 1; + } + } + + if (ret == WH_ERROR_OK) { + /* Request Message */ + uint16_t group = WH_MESSAGE_GROUP_CRYPTO; + uint16_t action = WC_ALGO_TYPE_PK; + + uint64_t total_len = + (uint64_t)sizeof(whMessageCrypto_GenericRequestHeader) + + sizeof(*req) + in_len + contextLen + addRndSz; + uint32_t options = 0; + + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + /* Setup generic header and get pointer to request data */ + req = + (whMessageCrypto_SlhDsaSignRequest*)_createCryptoRequestWithSubtype( + dataPtr, pkType, WC_PQC_SIG_TYPE_SLHDSA, ctx->cryptoAffinity); + + if (total_len <= WOLFHSM_CFG_COMM_DATA_LEN) { + uint16_t req_len = (uint16_t)total_len; + uint8_t* req_data = (uint8_t*)(req + 1); + if (evict != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_EVICT; + } + if (isMPrime != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_MPRIME; + } + if (randomized != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_RANDOMIZED; + } + + memset(req, 0, sizeof(*req)); + req->options = options; + req->param = _SlhDsaKeyParam(key); + req->keyId = key_id; + req->sz = in_len; + req->contextSz = contextLen; + req->preHashType = preHashType; + req->addRndSz = addRndSz; + if ((in != NULL) && (in_len > 0)) { + memcpy(req_data, in, in_len); + } + if ((context != NULL) && (contextLen > 0)) { + memcpy(req_data + in_len, context, contextLen); + } + if (addRndSz > 0) { + memcpy(req_data + in_len + contextLen, addRnd, addRndSz); + } + + /* Send Request */ + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + if (ret == WH_ERROR_OK) { + /* Server will evict at this point. Reset evict */ + uint16_t res_len = 0; + evict = 0; + + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + WOLFHSM_CFG_COMM_DATA_LEN, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + + if (ret == WH_ERROR_OK) { + /* Get response pointer, validate header */ + ret = _getCryptoResponse(dataPtr, pkType, (uint8_t**)&res); + /* wolfCrypt allows positive error codes on success in some + * scenarios */ + if (ret >= 0) { + const uint32_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res); + if ((res_len < hdr_sz) || + (res->sz > (res_len - hdr_sz))) { + ret = WH_ERROR_ABORTED; + } + else { + uint8_t* res_sig = (uint8_t*)(res + 1); + if (res->sz > *inout_len) { + ret = WH_ERROR_BUFFER_SIZE; + } + else { + memcpy(out, res_sig, res->sz); + } + *inout_len = res->sz; + } + } + } + } + } + else { + /* Request length is too long */ + ret = WH_ERROR_REQUEST_SIZE; + } + } + /* Evict key material on error */ + if (evict != 0) { + (void)wh_Client_KeyEvict(ctx, key_id); + } + WH_DEBUG_CLIENT_VERBOSE("ret:%d\n", ret); + return ret; +} + +int wh_Client_SlhDsaVerify(whClientContext* ctx, const byte* sig, + word32 sig_len, const byte* msg, word32 msg_len, + int* out_res, SlhDsaKey* key, const byte* context, + byte contextLen, word32 preHashType, int isMPrime) +{ + int ret = WH_ERROR_OK; + uint8_t* dataPtr = NULL; + whMessageCrypto_SlhDsaVerifyRequest* req = NULL; + whMessageCrypto_SlhDsaVerifyResponse* res = NULL; + uint16_t pkType; + + /* Transaction state */ + whKeyId key_id; + int evict = 0; + + if ((ctx == NULL) || (key == NULL) || ((sig == NULL) && (sig_len > 0)) || + (out_res == NULL) || ((msg == NULL) && (msg_len > 0)) || + ((context == NULL) && (contextLen > 0))) { + return WH_ERROR_BADARGS; + } + + *out_res = 0; + + /* M' already contains any context, so drop it */ + if (isMPrime != 0) { + contextLen = 0; + } + + pkType = (isMPrime != 0) ? WC_PK_TYPE_PQC_SIG_VERIFY_MSG + : WC_PK_TYPE_PQC_SIG_VERIFY; + + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + + /* Import key if necessary */ + if (WH_KEYID_ISERASED(key_id)) { + /* Must import the key to the server and evict it afterwards */ + uint8_t keyLabel[] = "TempSlhDsaVerify"; + whNvmFlags flags = WH_NVM_FLAGS_USAGE_VERIFY; + + ret = wh_Client_SlhDsaImportKey(ctx, key, &key_id, flags, + sizeof(keyLabel), keyLabel); + if (ret == WH_ERROR_OK) { + evict = 1; + } + } + + if (ret == WH_ERROR_OK) { + /* Request Message */ + uint16_t group = WH_MESSAGE_GROUP_CRYPTO; + uint16_t action = WC_ALGO_TYPE_PK; + uint32_t options = 0; + + uint64_t total_len = + (uint64_t)sizeof(whMessageCrypto_GenericRequestHeader) + + sizeof(*req) + sig_len + msg_len + contextLen; + + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + /* Setup generic header and get pointer to request data */ + req = (whMessageCrypto_SlhDsaVerifyRequest*) + _createCryptoRequestWithSubtype( + dataPtr, pkType, WC_PQC_SIG_TYPE_SLHDSA, ctx->cryptoAffinity); + + if (total_len <= WOLFHSM_CFG_COMM_DATA_LEN) { + uint16_t req_len = (uint16_t)total_len; + uint8_t* req_sig = (uint8_t*)(req + 1); + uint8_t* req_hash = req_sig + sig_len; + + /* Set request packet members */ + if (evict != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_EVICT; + } + if (isMPrime != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_MPRIME; + } + + memset(req, 0, sizeof(*req)); + req->options = options; + req->param = _SlhDsaKeyParam(key); + req->keyId = key_id; + req->sigSz = sig_len; + if ((sig != NULL) && (sig_len > 0)) { + memcpy(req_sig, sig, sig_len); + } + req->hashSz = msg_len; + if ((msg != NULL) && (msg_len > 0)) { + memcpy(req_hash, msg, msg_len); + } + req->contextSz = contextLen; + req->preHashType = preHashType; + if ((context != NULL) && (contextLen > 0)) { + memcpy(req_hash + msg_len, context, contextLen); + } + + /* write request */ + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + + if (ret == WH_ERROR_OK) { + /* Server will evict at this point. Reset evict */ + uint16_t res_len = 0; + evict = 0; + + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + WOLFHSM_CFG_COMM_DATA_LEN, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + + if (ret == WH_ERROR_OK) { + /* Get response pointer, validate header */ + ret = _getCryptoResponse(dataPtr, pkType, (uint8_t**)&res); + /* wolfCrypt allows positive error codes on success in some + * scenarios */ + if (ret >= 0) { + const uint32_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res); + /* Note whMessageCrypto_SlhDsaVerifyResponse has no + * size field */ + if (res_len < hdr_sz) { + ret = WH_ERROR_ABORTED; + } + else { + *out_res = res->res; + } + } + } + } + } + else { + /* Request length is too long */ + ret = WH_ERROR_REQUEST_SIZE; + } + } + /* Evict key material on error */ + if (evict != 0) { + (void)wh_Client_KeyEvict(ctx, key_id); + } + WH_DEBUG_CLIENT_VERBOSE("ret:%d\n", ret); + return ret; +} + +int wh_Client_SlhDsaCheckPrivKey(whClientContext* ctx, SlhDsaKey* key, + const byte* pubKey, word32 pubKeySz) +{ + int ret = WH_ERROR_OK; + uint8_t* dataPtr = NULL; + whMessageCrypto_SlhDsaCheckPrivKeyRequest* req = NULL; + whMessageCrypto_SlhDsaCheckPrivKeyResponse* res = NULL; + + /* Transaction state */ + whKeyId key_id; + int evict = 0; + + /* NULL public key asks the server to check its private key. */ + if ((ctx == NULL) || (key == NULL) || + ((pubKey == NULL) != (pubKeySz == 0))) { + return WH_ERROR_BADARGS; + } + + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + + /* Import key if necessary */ + if (WH_KEYID_ISERASED(key_id)) { + uint8_t keyLabel[] = "TempSlhDsaCheck"; + whNvmFlags flags = WH_NVM_FLAGS_USAGE_SIGN; + + ret = wh_Client_SlhDsaImportKey(ctx, key, &key_id, flags, + sizeof(keyLabel), keyLabel); + if (ret == WH_ERROR_OK) { + evict = 1; + } + } + + if (ret == WH_ERROR_OK) { + uint16_t group = WH_MESSAGE_GROUP_CRYPTO; + uint16_t action = WC_ALGO_TYPE_PK; + uint32_t options = 0; + + uint64_t total_len = + (uint64_t)sizeof(whMessageCrypto_GenericRequestHeader) + + sizeof(*req) + pubKeySz; + + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageCrypto_SlhDsaCheckPrivKeyRequest*) + _createCryptoRequestWithSubtype( + dataPtr, WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY, + WC_PQC_SIG_TYPE_SLHDSA, ctx->cryptoAffinity); + + if (total_len <= WOLFHSM_CFG_COMM_DATA_LEN) { + uint16_t req_len = (uint16_t)total_len; + + if (evict != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_CHECKPRIVKEY_OPTIONS_EVICT; + } + + memset(req, 0, sizeof(*req)); + req->options = options; + req->param = _SlhDsaKeyParam(key); + req->keyId = key_id; + req->pubSz = pubKeySz; + if (pubKeySz > 0) { + memcpy((uint8_t*)(req + 1), pubKey, pubKeySz); + } + + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + if (ret == WH_ERROR_OK) { + uint16_t res_len = 0; + evict = 0; + + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + WOLFHSM_CFG_COMM_DATA_LEN, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + + if (ret == WH_ERROR_OK) { + ret = _getCryptoResponse(dataPtr, + WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY, + (uint8_t**)&res); + if (ret >= 0) { + const uint32_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res); + if (res_len < hdr_sz) { + ret = WH_ERROR_ABORTED; + } + else { + ret = res->res; + } + } + } + } + } + else { + ret = WH_ERROR_REQUEST_SIZE; + } + } + if (evict != 0) { + (void)wh_Client_KeyEvict(ctx, key_id); + } + return ret; +} + +#ifdef WOLFHSM_CFG_DMA + +int wh_Client_SlhDsaImportKeyDma(whClientContext* ctx, SlhDsaKey* key, + whKeyId* inout_keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label) +{ + int ret = WH_ERROR_OK; + whKeyId key_id = WH_KEYID_ERASED; + byte buffer[WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE]; + uint16_t buffer_len = 0; + + if ((ctx == NULL) || (key == NULL) || + ((label_len != 0) && (label == NULL))) { + return WH_ERROR_BADARGS; + } + + if (inout_keyId != NULL) { + key_id = *inout_keyId; + } + + /* Serialize the key to a temporary buffer first */ + ret = wh_Crypto_SlhDsaSerializeKeyDer(key, sizeof(buffer), buffer, + &buffer_len); + if (ret == WH_ERROR_OK) { + /* Cache the key using DMA and get the keyID */ + ret = wh_Client_KeyCacheDma(ctx, flags, label, label_len, buffer, + buffer_len, &key_id); + if ((ret == WH_ERROR_OK) && (inout_keyId != NULL)) { + *inout_keyId = key_id; + } + } + + /* The DER above contains private key material. */ + wc_ForceZero(buffer, sizeof(buffer)); + return ret; +} + +int wh_Client_SlhDsaExportKeyDma(whClientContext* ctx, whKeyId keyId, + SlhDsaKey* key, uint16_t label_len, + uint8_t* label) +{ + int ret = WH_ERROR_OK; + WH_DMA_ALIGNED byte + buffer[WH_DMA_PAD_SIZE(WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE)] = {0}; + uint16_t buffer_len = sizeof(buffer); + + if ((ctx == NULL) || WH_KEYID_ISERASED(keyId) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Export the key from server using DMA */ + ret = wh_Client_KeyExportDma(ctx, keyId, buffer, buffer_len, label, + label_len, &buffer_len); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_SlhDsaDeserializeKeyDer(buffer, buffer_len, key); + } + + /* The DER above contains private key material. */ + wc_ForceZero(buffer, sizeof(buffer)); + return ret; +} + +int wh_Client_SlhDsaExportPublicKeyDma(whClientContext* ctx, whKeyId keyId, + SlhDsaKey* key, uint16_t label_len, + uint8_t* label) +{ + int ret; + WH_DMA_ALIGNED byte + buffer[WH_DMA_PAD_SIZE(WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE)] = {0}; + uint16_t buffer_len = sizeof(buffer); + + if ((ctx == NULL) || WH_KEYID_ISERASED(keyId) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = + wh_Client_KeyExportPublicDma(ctx, keyId, WH_KEY_ALGO_SLHDSA, buffer, + buffer_len, label, label_len, &buffer_len); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_SlhDsaDeserializeKeyDer(buffer, buffer_len, key); + } + return ret; +} + +static int _SlhDsaMakeKeyDma(whClientContext* ctx, int param, const byte* seed, + word32 seedSz, whKeyId* inout_key_id, + whNvmFlags flags, uint16_t label_len, + const uint8_t* label, SlhDsaKey* key, + int* out_committed) +{ + int ret = WH_ERROR_OK; + whKeyId key_id = WH_KEYID_ERASED; + WH_DMA_ALIGNED byte + buffer[WH_DMA_PAD_SIZE(WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE)]; + uint8_t* dataPtr = NULL; + whMessageCrypto_SlhDsaKeyGenDmaRequest* req = NULL; + whMessageCrypto_SlhDsaKeyGenDmaResponse* res = NULL; + uintptr_t keyAddr = 0; + uintptr_t seedAddr = 0; + uint64_t keyAddrSz = 0; + int keyPre = 0; + int postErr = WH_ERROR_OK; + int seedPre = 0; + uint16_t pkType = WC_PK_TYPE_PQC_SIG_KEYGEN; + uint16_t req_len; + uint16_t res_len = 0; + uint16_t group; + uint16_t action; + + if (out_committed != NULL) { + *out_committed = 0; + } + + if ((ctx == NULL) || ((seed == NULL) && (seedSz > 0))) { + return WH_ERROR_BADARGS; + } + + /* Get data pointer from the context to use as request/response storage */ + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + /* Setup generic header and get pointer to request data */ + req = (whMessageCrypto_SlhDsaKeyGenDmaRequest*) + _createCryptoRequestWithSubtype(dataPtr, pkType, WC_PQC_SIG_TYPE_SLHDSA, + ctx->cryptoAffinity); + + /* Use the supplied key id if provided */ + if (inout_key_id != NULL) { + key_id = *inout_key_id; + } + + group = WH_MESSAGE_GROUP_CRYPTO_DMA; + action = WC_ALGO_TYPE_PK; + + req_len = sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req); + + if (req_len <= WOLFHSM_CFG_COMM_DATA_LEN) { + memset(req, 0, sizeof(*req)); + req->param = param; + req->flags = flags; + req->keyId = key_id; + req->key.sz = keyAddrSz = sizeof(buffer); + + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)buffer, (void**)&keyAddr, keyAddrSz, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + keyPre = 1; + req->key.addr = (uint64_t)(uintptr_t)keyAddr; + } + + if ((ret == WH_ERROR_OK) && (seedSz > 0)) { + req->seed.sz = seedSz; + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)seed, (void**)&seedAddr, seedSz, + WH_DMA_OPER_CLIENT_READ_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + seedPre = 1; + req->seed.addr = (uint64_t)(uintptr_t)seedAddr; + } + } + + if ((label != NULL) && (label_len > 0)) { + if (label_len > WH_NVM_LABEL_LEN) { + label_len = WH_NVM_LABEL_LEN; + } + memcpy(req->label, label, label_len); + req->labelSize = label_len; + } + + if (ret == WH_ERROR_OK) { + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + } + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + WOLFHSM_CFG_COMM_DATA_LEN, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + } + + /* Defer POST errors until the key id is read, so it can be evicted */ + if (seedPre) { + int rc = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)seed, (void**)&seedAddr, seedSz, + WH_DMA_OPER_CLIENT_READ_POST, (whDmaFlags){0}); + if (postErr == WH_ERROR_OK) { + postErr = rc; + } + } + if (keyPre) { + int rc = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)buffer, (void**)&keyAddr, keyAddrSz, + WH_DMA_OPER_CLIENT_WRITE_POST, (whDmaFlags){0}); + if (postErr == WH_ERROR_OK) { + postErr = rc; + } + } + + if (ret == WH_ERROR_OK) { + /* Get response pointer, validate header */ + ret = _getCryptoResponse(dataPtr, pkType, (uint8_t**)&res); + /* wolfCrypt allows positive error codes on success in some + * scenarios */ + if (ret >= 0) { + const uint32_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res); + /* The response has no trailing payload */ + if (res_len < hdr_sz) { + ret = WH_ERROR_ABORTED; + } + } + if (ret >= 0) { + /* Key is cached on server or is ephemeral */ + key_id = (whKeyId)(res->keyId); + if (out_committed != NULL) { + *out_committed = 1; + } + + /* Update output variable if requested */ + if (inout_key_id != NULL) { + *inout_key_id = key_id; + } + + /* Update the context if provided */ + if (key != NULL) { + /* Set the key_id. ERASED for EPHEMERAL, cached id + * otherwise. */ + wh_Client_SlhDsaSetKeyId(key, key_id); + + if (res->keySize == 0) { + ret = WH_ERROR_ABORTED; + } + /* Bound the server-reported key size to the DMA buffer + * capacity before deserializing */ + else if (res->keySize > sizeof(buffer)) { + ret = WH_ERROR_ABORTED; + } + else { + ret = wh_Crypto_SlhDsaDeserializeKeyDer( + buffer, (uint16_t)res->keySize, key); + } + } + } + } + } + else { + ret = WH_ERROR_REQUEST_SIZE; + } + /* Report a deferred POST error */ + if ((ret >= 0) && (postErr != WH_ERROR_OK)) { + ret = postErr; + } + /* The DER above contains private key material. */ + wc_ForceZero(buffer, sizeof(buffer)); + return ret; +} + +int wh_Client_SlhDsaMakeExportKeyDma(whClientContext* ctx, int param, + SlhDsaKey* key) +{ + if (key == NULL) { + return WH_ERROR_BADARGS; + } + + return _SlhDsaMakeKeyDma(ctx, param, NULL, 0, NULL, WH_NVM_FLAGS_EPHEMERAL, + 0, NULL, key, NULL); +} + +int wh_Client_SlhDsaMakeExportKeyFromSeedDma(whClientContext* ctx, int param, + const byte* seed, word32 seedSz, + SlhDsaKey* key) +{ + if ((key == NULL) || (seed == NULL) || (seedSz == 0)) { + return WH_ERROR_BADARGS; + } + + return _SlhDsaMakeKeyDma(ctx, param, seed, seedSz, NULL, + WH_NVM_FLAGS_EPHEMERAL, 0, NULL, key, NULL); +} + +int wh_Client_SlhDsaMakeCacheKeyDma(whClientContext* ctx, int param, + whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, const uint8_t* label, + SlhDsaKey* pub) +{ + int ret; + int committed = 0; + + if ((ctx == NULL) || (inout_key_id == NULL) || (pub == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Ephemeral means not cached */ + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + return WH_ERROR_BADARGS; + } + + ret = _SlhDsaMakeKeyDma(ctx, param, NULL, 0, inout_key_id, flags, label_len, + label, pub, &committed); + return _SlhDsaCacheKeyFinish(ctx, ret, committed, inout_key_id, pub); +} + +int wh_Client_SlhDsaSignDma(whClientContext* ctx, const byte* in, word32 in_len, + byte* out, word32* out_len, SlhDsaKey* key, + const byte* context, byte contextLen, + word32 preHashType, const byte* addRnd, + byte addRndSz, int randomized, int isMPrime) +{ + int ret = WH_ERROR_OK; + whMessageCrypto_SlhDsaSignDmaRequest* req = NULL; + whMessageCrypto_SlhDsaSignDmaResponse* res = NULL; + uint8_t* dataPtr = NULL; + uintptr_t inAddr = 0; + uintptr_t outAddr = 0; + int inPre = 0; + int outPre = 0; + word32 sigCap = 0; + uint16_t pkType; + + /* Transaction state */ + whKeyId key_id; + int evict = 0; + + if ((ctx == NULL) || (key == NULL) || ((in == NULL) && (in_len > 0)) || + (out == NULL) || (out_len == NULL) || + ((addRnd == NULL) && (addRndSz > 0)) || + ((context == NULL) && (contextLen > 0))) { + return WH_ERROR_BADARGS; + } + + /* M' already contains any context, so drop it */ + if (isMPrime != 0) { + contextLen = 0; + } + + sigCap = *out_len; + pkType = + (isMPrime != 0) ? WC_PK_TYPE_PQC_SIG_SIGN_MSG : WC_PK_TYPE_PQC_SIG_SIGN; + + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + + /* Import key if necessary */ + if (WH_KEYID_ISERASED(key_id)) { + uint8_t keyLabel[] = "TempSlhDsaSign"; + whNvmFlags flags = WH_NVM_FLAGS_USAGE_SIGN; + + ret = wh_Client_SlhDsaImportKeyDma(ctx, key, &key_id, flags, + sizeof(keyLabel), keyLabel); + if (ret == WH_ERROR_OK) { + evict = 1; + } + } + + if (ret == WH_ERROR_OK) { + /* Request Message */ + uint16_t group = WH_MESSAGE_GROUP_CRYPTO_DMA; + uint16_t action = WC_ALGO_TYPE_PK; + + uint16_t req_len = sizeof(whMessageCrypto_GenericRequestHeader) + + sizeof(*req) + contextLen + addRndSz; + uint32_t options = 0; + + /* Get data pointer from the context to use as request/response storage + */ + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + /* Setup generic header and get pointer to request data */ + req = (whMessageCrypto_SlhDsaSignDmaRequest*) + _createCryptoRequestWithSubtype( + dataPtr, pkType, WC_PQC_SIG_TYPE_SLHDSA, ctx->cryptoAffinity); + + if (req_len <= WOLFHSM_CFG_COMM_DATA_LEN) { + uint8_t* req_data = (uint8_t*)(req + 1); + + if (evict != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_EVICT; + } + if (isMPrime != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_MPRIME; + } + if (randomized != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_RANDOMIZED; + } + + memset(req, 0, sizeof(*req)); + req->options = options; + req->param = _SlhDsaKeyParam(key); + req->keyId = key_id; + req->contextSz = contextLen; + req->preHashType = preHashType; + req->addRndSz = addRndSz; + if ((context != NULL) && (contextLen > 0)) { + memcpy(req_data, context, contextLen); + } + if (addRndSz > 0) { + memcpy(req_data + contextLen, addRnd, addRndSz); + } + + /* Set up DMA buffers */ + req->msg.sz = in_len; + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)in, (void**)&inAddr, req->msg.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + inPre = 1; + req->msg.addr = inAddr; + } + + if (ret == WH_ERROR_OK) { + req->sig.sz = sigCap; + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)out, (void**)&outAddr, req->sig.sz, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + outPre = 1; + req->sig.addr = outAddr; + } + } + + /* Send Request */ + if (ret == WH_ERROR_OK) { + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + } + if (ret == WH_ERROR_OK) { + /* Server will evict at this point if requested */ + uint16_t res_len = 0; + evict = 0; + + /* Recv Response */ + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + WOLFHSM_CFG_COMM_DATA_LEN, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + + if (ret == WH_ERROR_OK) { + /* Get response pointer, validate header */ + ret = _getCryptoResponse(dataPtr, pkType, (uint8_t**)&res); + /* wolfCrypt allows positive error codes on success in some + * scenarios */ + if (ret >= 0) { + const uint32_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res); + if (res_len < hdr_sz) { + ret = WH_ERROR_ABORTED; + } + else if (res->sigLen > sigCap) { + ret = WH_ERROR_BADARGS; + } + else { + *out_len = res->sigLen; + } + } + } + } + + if (outPre) { + int postRet = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)out, (void**)&outAddr, sigCap, + WH_DMA_OPER_CLIENT_WRITE_POST, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + ret = postRet; + } + } + if (inPre) { + int postRet = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)in, (void**)&inAddr, in_len, + WH_DMA_OPER_CLIENT_READ_POST, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + ret = postRet; + } + } + } + else { + ret = WH_ERROR_REQUEST_SIZE; + } + } + /* Evict key material on error */ + if (evict != 0) { + (void)wh_Client_KeyEvict(ctx, key_id); + } + + return ret; +} + +int wh_Client_SlhDsaVerifyDma(whClientContext* ctx, const byte* sig, + word32 sig_len, const byte* msg, word32 msg_len, + int* out_res, SlhDsaKey* key, const byte* context, + byte contextLen, word32 preHashType, int isMPrime) +{ + int ret = WH_ERROR_OK; + whMessageCrypto_SlhDsaVerifyDmaRequest* req = NULL; + whMessageCrypto_SlhDsaVerifyDmaResponse* res = NULL; + uint8_t* dataPtr = NULL; + uint16_t pkType; + int sigPre = 0; + int msgPre = 0; + + /* Transaction state */ + whKeyId key_id; + int evict = 0; + + if ((ctx == NULL) || (key == NULL) || ((sig == NULL) && (sig_len > 0)) || + ((msg == NULL) && (msg_len > 0)) || (out_res == NULL) || + ((context == NULL) && (contextLen > 0))) { + return WH_ERROR_BADARGS; + } + + *out_res = 0; + + /* M' already contains any context, so drop it */ + if (isMPrime != 0) { + contextLen = 0; + } + + pkType = (isMPrime != 0) ? WC_PK_TYPE_PQC_SIG_VERIFY_MSG + : WC_PK_TYPE_PQC_SIG_VERIFY; + + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + + /* Import key if necessary */ + if (WH_KEYID_ISERASED(key_id)) { + uint8_t keyLabel[] = "TempSlhDsaVerify"; + whNvmFlags flags = WH_NVM_FLAGS_USAGE_VERIFY; + + ret = wh_Client_SlhDsaImportKeyDma(ctx, key, &key_id, flags, + sizeof(keyLabel), keyLabel); + if (ret == WH_ERROR_OK) { + evict = 1; + } + } + + if (ret == WH_ERROR_OK) { + /* Request Message */ + uint16_t group = WH_MESSAGE_GROUP_CRYPTO_DMA; + uint16_t action = WC_ALGO_TYPE_PK; + uint32_t options = 0; + uintptr_t sigAddr = 0; + uintptr_t msgAddr = 0; + + uint16_t req_len = sizeof(whMessageCrypto_GenericRequestHeader) + + sizeof(*req) + contextLen; + + /* Get data pointer from the context to use as request/response storage + */ + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + /* Setup generic header and get pointer to request data */ + req = (whMessageCrypto_SlhDsaVerifyDmaRequest*) + _createCryptoRequestWithSubtype( + dataPtr, pkType, WC_PQC_SIG_TYPE_SLHDSA, ctx->cryptoAffinity); + + if (req_len <= WOLFHSM_CFG_COMM_DATA_LEN) { + if (evict != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_EVICT; + } + if (isMPrime != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_MPRIME; + } + + memset(req, 0, sizeof(*req)); + req->options = options; + req->param = _SlhDsaKeyParam(key); + req->keyId = key_id; + req->contextSz = contextLen; + req->preHashType = preHashType; + if ((context != NULL) && (contextLen > 0)) { + memcpy((uint8_t*)(req + 1), context, contextLen); + } + + /* Set up DMA buffers */ + req->sig.sz = sig_len; + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)sig, (void**)&sigAddr, sig_len, + WH_DMA_OPER_CLIENT_READ_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + sigPre = 1; + req->sig.addr = sigAddr; + } + if (ret == WH_ERROR_OK) { + req->msg.sz = msg_len; + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)msg, (void**)&msgAddr, msg_len, + WH_DMA_OPER_CLIENT_READ_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + msgPre = 1; + req->msg.addr = msgAddr; + } + } + + /* Send Request */ + if (ret == WH_ERROR_OK) { + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + } + if (ret == WH_ERROR_OK) { + /* Server will evict at this point if requested */ + uint16_t res_len = 0; + evict = 0; + + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + WOLFHSM_CFG_COMM_DATA_LEN, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + + if (ret == WH_ERROR_OK) { + /* Get response pointer, validate header */ + ret = _getCryptoResponse(dataPtr, pkType, (uint8_t**)&res); + /* wolfCrypt allows positive error codes on success in some + * scenarios */ + if (ret >= 0) { + const uint32_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res); + /* Note whMessageCrypto_SlhDsaVerifyDmaResponse has no + * size field */ + if (res_len < hdr_sz) { + ret = WH_ERROR_ABORTED; + } + else { + /* Set verification result */ + *out_res = res->verifyResult; + } + } + } + } + + /* Release only what was acquired. */ + if (msgPre) { + int postRet = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)msg, (void**)&msgAddr, msg_len, + WH_DMA_OPER_CLIENT_READ_POST, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + ret = postRet; + } + } + if (sigPre) { + int postRet = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)sig, (void**)&sigAddr, sig_len, + WH_DMA_OPER_CLIENT_READ_POST, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + ret = postRet; + } + } + } + else { + ret = WH_ERROR_REQUEST_SIZE; + } + } + + /* Evict key material on error */ + if (evict != 0) { + (void)wh_Client_KeyEvict(ctx, key_id); + } + + return ret; +} + +int wh_Client_SlhDsaCheckPrivKeyDma(whClientContext* ctx, SlhDsaKey* key, + const byte* pubKey, word32 pubKeySz) +{ + /* The 2n-byte public key fits the comm buffer */ + return wh_Client_SlhDsaCheckPrivKey(ctx, key, pubKey, pubKeySz); +} + +#endif /* WOLFHSM_CFG_DMA */ +#endif /* WOLFSSL_HAVE_SLHDSA */ + #ifdef WOLFSSL_HAVE_MLKEM int wh_Client_MlKemSetKeyId(MlKemKey* key, whKeyId keyId) diff --git a/src/wh_client_cryptocb.c b/src/wh_client_cryptocb.c index c904bd575..90b63d56e 100644 --- a/src/wh_client_cryptocb.c +++ b/src/wh_client_cryptocb.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * @@ -81,7 +81,8 @@ static int _handlePqcStatefulSigSigsLeft(whClientContext* ctx, #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ -#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) +#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_SLHDSA) static int _handlePqcSigKeyGen(whClientContext* ctx, wc_CryptoInfo* info, int useDma); static int _handlePqcSign(whClientContext* ctx, wc_CryptoInfo* info, @@ -90,7 +91,7 @@ static int _handlePqcVerify(whClientContext* ctx, wc_CryptoInfo* info, int useDma); static int _handlePqcSigCheckPrivKey(whClientContext* ctx, wc_CryptoInfo* info, int useDma); -#endif /* WOLFSSL_HAVE_MLDSA || HAVE_FALCON */ +#endif /* WOLFSSL_HAVE_MLDSA || HAVE_FALCON || WOLFSSL_HAVE_SLHDSA */ int wh_Client_CryptoCb(int devId, wc_CryptoInfo* info, void* inCtx) { @@ -565,10 +566,16 @@ int wh_Client_CryptoCbStd(int devId, wc_CryptoInfo* info, void* inCtx) #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ -#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) +#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_SLHDSA) case WC_PK_TYPE_PQC_SIG_KEYGEN: ret = _handlePqcSigKeyGen(ctx, info, 0); break; +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PK_TYPE_PQC_SIG_KEYGEN_SEED: + ret = _handlePqcSigKeyGen(ctx, info, 0); + break; +#endif /* WOLFSSL_HAVE_SLHDSA */ case WC_PK_TYPE_PQC_SIG_SIGN: ret = _handlePqcSign(ctx, info, 0); @@ -582,7 +589,18 @@ int wh_Client_CryptoCbStd(int devId, wc_CryptoInfo* info, void* inCtx) ret = _handlePqcSigCheckPrivKey(ctx, info, 0); break; -#endif /* WOLFSSL_HAVE_MLDSA || HAVE_FALCON */ +#ifdef WOLFSSL_HAVE_SLHDSA + /* SLH-DSA only: FIPS 205 internal interface, caller builds M' */ + case WC_PK_TYPE_PQC_SIG_SIGN_MSG: + ret = _handlePqcSign(ctx, info, 0); + break; + + case WC_PK_TYPE_PQC_SIG_VERIFY_MSG: + ret = _handlePqcVerify(ctx, info, 0); + break; +#endif /* WOLFSSL_HAVE_SLHDSA */ + +#endif /* WOLFSSL_HAVE_MLDSA || HAVE_FALCON || WOLFSSL_HAVE_SLHDSA */ case WC_PK_TYPE_NONE: default: @@ -1231,7 +1249,8 @@ static int _handlePqcStatefulSigSigsLeft(whClientContext* ctx, } #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ -#if defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) +#if defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \ + defined(WOLFSSL_HAVE_SLHDSA) static int _handlePqcSigKeyGen(whClientContext* ctx, wc_CryptoInfo* info, int useDma) { @@ -1249,6 +1268,14 @@ static int _handlePqcSigKeyGen(whClientContext* ctx, wc_CryptoInfo* info, } #endif +#ifdef WOLFSSL_HAVE_SLHDSA + /* Only SLH-DSA supports keygen from a caller-supplied seed */ + if ((info->pk.type == WC_PK_TYPE_PQC_SIG_KEYGEN_SEED) && + (type != WC_PQC_SIG_TYPE_SLHDSA)) { + return CRYPTOCB_UNAVAILABLE; + } +#endif /* WOLFSSL_HAVE_SLHDSA */ + switch (type) { #ifdef WOLFSSL_HAVE_MLDSA case WC_PQC_SIG_TYPE_MLDSA: { @@ -1265,7 +1292,36 @@ static int _handlePqcSigKeyGen(whClientContext* ctx, wc_CryptoInfo* info, } break; #endif /* WOLFSSL_HAVE_MLDSA */ - /* Support for additional PQC algorithms should be added here */ +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PQC_SIG_TYPE_SLHDSA: { + /* size contains the SlhDsaParam enum, not a byte count */ + const byte* seed = info->pk.pqc_sig_kg.seed; + word32 seedSz = info->pk.pqc_sig_kg.seedSz; +#ifdef WOLFHSM_CFG_DMA + if (useDma) { + if (seed != NULL) { + ret = wh_Client_SlhDsaMakeExportKeyFromSeedDma( + ctx, size, seed, seedSz, key); + } + else { + ret = wh_Client_SlhDsaMakeExportKeyDma(ctx, size, key); + } + } + else +#endif /* WOLFHSM_CFG_DMA */ + { + if (seed != NULL) { + ret = wh_Client_SlhDsaMakeExportKeyFromSeed(ctx, size, seed, + seedSz, key); + } + else { + ret = wh_Client_SlhDsaMakeExportKey(ctx, size, key); + } + } + } break; +#endif /* WOLFSSL_HAVE_SLHDSA */ + + /* Support for additional PQC algorithms should be added here */ default: ret = CRYPTOCB_UNAVAILABLE; @@ -1275,6 +1331,15 @@ static int _handlePqcSigKeyGen(whClientContext* ctx, wc_CryptoInfo* info, return ret; } +#ifdef WOLFSSL_HAVE_SLHDSA +/* Returns 1 if key contains key bytes, 0 if it only references a server key */ +static int _SlhDsaKeyHasMaterial(const SlhDsaKey* key) +{ + return (key != NULL) && ((key->flags & (WC_SLHDSA_FLAG_PRIVATE | + WC_SLHDSA_FLAG_PUBLIC)) != 0); +} +#endif /* WOLFSSL_HAVE_SLHDSA */ + static int _handlePqcSign(whClientContext* ctx, wc_CryptoInfo* info, int useDma) { int ret = CRYPTOCB_UNAVAILABLE; @@ -1315,7 +1380,30 @@ static int _handlePqcSign(whClientContext* ctx, wc_CryptoInfo* info, int useDma) break; #endif /* WOLFSSL_HAVE_MLDSA */ - /* Support for additional PQC algorithms should be added here */ +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PQC_SIG_TYPE_SLHDSA: { + const byte* addRnd = info->pk.pqc_sign.addRnd; + byte addRndSz = info->pk.pqc_sign.addRndSz; + int randomized = (info->pk.pqc_sign.rng != NULL); + int isMPrime = (info->pk.type == WC_PK_TYPE_PQC_SIG_SIGN_MSG); + +#ifdef WOLFHSM_CFG_DMA + if (useDma) { + ret = wh_Client_SlhDsaSignDma( + ctx, in, in_len, out, out_len, key, context, contextLen, + preHashType, addRnd, addRndSz, randomized, isMPrime); + } + else +#endif /* WOLFHSM_CFG_DMA */ + { + ret = wh_Client_SlhDsaSign( + ctx, in, in_len, out, out_len, key, context, contextLen, + preHashType, addRnd, addRndSz, randomized, isMPrime); + } + } break; +#endif /* WOLFSSL_HAVE_SLHDSA */ + + /* Support for additional PQC algorithms should be added here */ default: ret = CRYPTOCB_UNAVAILABLE; @@ -1368,7 +1456,26 @@ static int _handlePqcVerify(whClientContext* ctx, wc_CryptoInfo* info, break; #endif /* WOLFSSL_HAVE_MLDSA */ - /* Support for additional PQC algorithms should be added here */ +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PQC_SIG_TYPE_SLHDSA: { + int isMPrime = (info->pk.type == WC_PK_TYPE_PQC_SIG_VERIFY_MSG); +#ifdef WOLFHSM_CFG_DMA + if (useDma) { + ret = wh_Client_SlhDsaVerifyDma(ctx, sig, sig_len, msg, msg_len, + res, key, context, contextLen, + preHashType, isMPrime); + } + else +#endif /* WOLFHSM_CFG_DMA */ + { + ret = wh_Client_SlhDsaVerify(ctx, sig, sig_len, msg, msg_len, + res, key, context, contextLen, + preHashType, isMPrime); + } + } break; +#endif /* WOLFSSL_HAVE_SLHDSA */ + + /* Support for additional PQC algorithms should be added here */ default: ret = CRYPTOCB_UNAVAILABLE; @@ -1412,6 +1519,29 @@ static int _handlePqcSigCheckPrivKey(whClientContext* ctx, wc_CryptoInfo* info, break; #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PQC_SIG_TYPE_SLHDSA: { + const byte* slhPub = pubKey; + word32 slhPubSz = pubKeySz; + + /* No local key: pass NULL so the server checks its own copy */ + if (!_SlhDsaKeyHasMaterial((const SlhDsaKey*)key)) { + slhPub = NULL; + slhPubSz = 0; + } +#ifdef WOLFHSM_CFG_DMA + if (useDma) { + ret = + wh_Client_SlhDsaCheckPrivKeyDma(ctx, key, slhPub, slhPubSz); + } + else +#endif /* WOLFHSM_CFG_DMA */ + { + ret = wh_Client_SlhDsaCheckPrivKey(ctx, key, slhPub, slhPubSz); + } + } break; +#endif /* WOLFSSL_HAVE_SLHDSA */ + /* Support for additional PQC algorithms should be added here */ default: @@ -1421,7 +1551,7 @@ static int _handlePqcSigCheckPrivKey(whClientContext* ctx, wc_CryptoInfo* info, return ret; } -#endif /* HAVE_FALCON || WOLFSSL_HAVE_MLDSA */ +#endif /* HAVE_FALCON || WOLFSSL_HAVE_MLDSA || WOLFSSL_HAVE_SLHDSA */ #if defined(WOLFHSM_CFG_DMA) && \ @@ -1591,10 +1721,16 @@ int wh_Client_CryptoCbDma(int devId, wc_CryptoInfo* info, void* inCtx) ret = _handlePqcStatefulSigSigsLeft(ctx, info, 1); break; #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ -#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) +#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_SLHDSA) case WC_PK_TYPE_PQC_SIG_KEYGEN: ret = _handlePqcSigKeyGen(ctx, info, 1); break; +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PK_TYPE_PQC_SIG_KEYGEN_SEED: + ret = _handlePqcSigKeyGen(ctx, info, 1); + break; +#endif /* WOLFSSL_HAVE_SLHDSA */ case WC_PK_TYPE_PQC_SIG_SIGN: ret = _handlePqcSign(ctx, info, 1); break; @@ -1604,7 +1740,15 @@ int wh_Client_CryptoCbDma(int devId, wc_CryptoInfo* info, void* inCtx) case WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY: ret = _handlePqcSigCheckPrivKey(ctx, info, 1); break; -#endif /* WOLFSSL_HAVE_MLDSA || HAVE_FALCON */ +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PK_TYPE_PQC_SIG_SIGN_MSG: + ret = _handlePqcSign(ctx, info, 1); + break; + case WC_PK_TYPE_PQC_SIG_VERIFY_MSG: + ret = _handlePqcVerify(ctx, info, 1); + break; +#endif /* WOLFSSL_HAVE_SLHDSA */ +#endif /* WOLFSSL_HAVE_MLDSA || HAVE_FALCON || WOLFSSL_HAVE_SLHDSA */ #ifdef HAVE_ED25519 case WC_PK_TYPE_ED25519_KEYGEN: { ed25519_key* key = info->pk.ed25519kg.key; diff --git a/src/wh_crypto.c b/src/wh_crypto.c index 91d100999..b1a85fe86 100644 --- a/src/wh_crypto.c +++ b/src/wh_crypto.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * @@ -390,6 +390,74 @@ int wh_Crypto_MlDsaDeserializeKeyDer(const uint8_t* buffer, uint16_t size, } #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA +int wh_Crypto_SlhDsaSerializeKeyDer(SlhDsaKey* key, uint16_t max_size, + uint8_t* buffer, uint16_t* out_size) +{ + int ret = 0; + + if ((key == NULL) || (buffer == NULL) || (out_size == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Choose appropriate serialization based on key flags */ + if (key->flags & WC_SLHDSA_FLAG_PRIVATE) { +#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY + /* RFC 9909 always has the public key alongside the private one */ + ret = wc_SlhDsaKey_KeyToDer(key, buffer, max_size); +#else + ret = WH_ERROR_BADARGS; +#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */ + } + else if (key->flags & WC_SLHDSA_FLAG_PUBLIC) { + /* Public key only - use SPKI format */ + ret = wc_SlhDsaKey_PublicKeyToDer(key, buffer, max_size, 1); + } + else { + /* No key data set */ + return WH_ERROR_BADARGS; + } + + /* ASN.1 functions return the size of the DER encoded key on success */ + if (ret > 0) { + *out_size = ret; + ret = WH_ERROR_OK; + } + else { + wc_ForceZero(buffer, max_size); + *out_size = 0; + if (ret == 0) { + ret = WH_ERROR_ABORTED; + } + } + return ret; +} + +int wh_Crypto_SlhDsaDeserializeKeyDer(const uint8_t* buffer, uint16_t size, + SlhDsaKey* key) +{ + word32 idx = 0; + int ret; + + if ((buffer == NULL) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + +#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY + /* Try private key first, if that fails try public key */ + ret = wc_SlhDsaKey_PrivateKeyDecode(buffer, &idx, key, size); + if (ret != 0) { + /* Reset index before trying public key */ + idx = 0; + ret = wc_SlhDsaKey_PublicKeyDecode(buffer, &idx, key, size); + } +#else + ret = wc_SlhDsaKey_PublicKeyDecode(buffer, &idx, key, size); +#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */ + return ret; +} +#endif /* WOLFSSL_HAVE_SLHDSA */ + #ifdef WOLFSSL_HAVE_MLKEM int wh_Crypto_MlKemSerializeKey(MlKemKey* key, uint16_t max_size, uint8_t* buffer, uint16_t* out_size) diff --git a/src/wh_message_crypto.c b/src/wh_message_crypto.c index 45579b7be..3cf53d204 100644 --- a/src/wh_message_crypto.c +++ b/src/wh_message_crypto.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2025 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * @@ -923,6 +923,127 @@ int wh_MessageCrypto_TranslateMlDsaVerifyResponse( return 0; } +/* SLH-DSA Key Generation Request translation */ +int wh_MessageCrypto_TranslateSlhDsaKeyGenRequest( + uint16_t magic, const whMessageCrypto_SlhDsaKeyGenRequest* src, + whMessageCrypto_SlhDsaKeyGenRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, sz); + WH_T32(magic, dest, src, param); + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, flags); + WH_T32(magic, dest, src, access); + WH_T32(magic, dest, src, seedSz); + /* Label is just a byte array, no translation needed */ + if (src != dest) { + memcpy(dest->label, src->label, sizeof(src->label)); + } + return 0; +} + +/* SLH-DSA Key Generation Response translation */ +int wh_MessageCrypto_TranslateSlhDsaKeyGenResponse( + uint16_t magic, const whMessageCrypto_SlhDsaKeyGenResponse* src, + whMessageCrypto_SlhDsaKeyGenResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, len); + return 0; +} + +/* SLH-DSA Sign Request translation */ +int wh_MessageCrypto_TranslateSlhDsaSignRequest( + uint16_t magic, const whMessageCrypto_SlhDsaSignRequest* src, + whMessageCrypto_SlhDsaSignRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, options); + WH_T32(magic, dest, src, param); + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, sz); + WH_T32(magic, dest, src, contextSz); + WH_T32(magic, dest, src, preHashType); + WH_T32(magic, dest, src, addRndSz); + return 0; +} + +/* SLH-DSA Sign Response translation */ +int wh_MessageCrypto_TranslateSlhDsaSignResponse( + uint16_t magic, const whMessageCrypto_SlhDsaSignResponse* src, + whMessageCrypto_SlhDsaSignResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, sz); + return 0; +} + +/* SLH-DSA Verify Request translation */ +int wh_MessageCrypto_TranslateSlhDsaVerifyRequest( + uint16_t magic, const whMessageCrypto_SlhDsaVerifyRequest* src, + whMessageCrypto_SlhDsaVerifyRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, options); + WH_T32(magic, dest, src, param); + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, sigSz); + WH_T32(magic, dest, src, hashSz); + WH_T32(magic, dest, src, contextSz); + WH_T32(magic, dest, src, preHashType); + return 0; +} + +/* SLH-DSA Verify Response translation */ +int wh_MessageCrypto_TranslateSlhDsaVerifyResponse( + uint16_t magic, const whMessageCrypto_SlhDsaVerifyResponse* src, + whMessageCrypto_SlhDsaVerifyResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, res); + return 0; +} + +/* SLH-DSA Check Private Key Request translation */ +int wh_MessageCrypto_TranslateSlhDsaCheckPrivKeyRequest( + uint16_t magic, const whMessageCrypto_SlhDsaCheckPrivKeyRequest* src, + whMessageCrypto_SlhDsaCheckPrivKeyRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, options); + WH_T32(magic, dest, src, param); + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, pubSz); + return 0; +} + +/* SLH-DSA Check Private Key Response translation */ +int wh_MessageCrypto_TranslateSlhDsaCheckPrivKeyResponse( + uint16_t magic, const whMessageCrypto_SlhDsaCheckPrivKeyResponse* src, + whMessageCrypto_SlhDsaCheckPrivKeyResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, res); + return 0; +} + /* ML-KEM Key Generation Request translation */ int wh_MessageCrypto_TranslateMlKemKeyGenRequest( uint16_t magic, const whMessageCrypto_MlKemKeyGenRequest* src, @@ -1363,6 +1484,165 @@ int wh_MessageCrypto_TranslateMlDsaVerifyDmaResponse( return 0; } +/* SLH-DSA DMA Key Generation Request translation */ +int wh_MessageCrypto_TranslateSlhDsaKeyGenDmaRequest( + uint16_t magic, const whMessageCrypto_SlhDsaKeyGenDmaRequest* src, + whMessageCrypto_SlhDsaKeyGenDmaRequest* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->key, &dest->key); + if (ret != 0) { + return ret; + } + + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->seed, &dest->seed); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, param); + WH_T32(magic, dest, src, flags); + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, access); + WH_T32(magic, dest, src, labelSize); + /* Label is just a byte array, no translation needed */ + if (src != dest) { + memcpy(dest->label, src->label, sizeof(src->label)); + } + + return 0; +} + +/* SLH-DSA DMA Key Generation Response translation */ +int wh_MessageCrypto_TranslateSlhDsaKeyGenDmaResponse( + uint16_t magic, const whMessageCrypto_SlhDsaKeyGenDmaResponse* src, + whMessageCrypto_SlhDsaKeyGenDmaResponse* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaAddrStatus(magic, &src->dmaAddrStatus, + &dest->dmaAddrStatus); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, keySize); + return 0; +} + +/* SLH-DSA DMA Sign Request translation */ +int wh_MessageCrypto_TranslateSlhDsaSignDmaRequest( + uint16_t magic, const whMessageCrypto_SlhDsaSignDmaRequest* src, + whMessageCrypto_SlhDsaSignDmaRequest* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->msg, &dest->msg); + if (ret != 0) { + return ret; + } + + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->sig, &dest->sig); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, options); + WH_T32(magic, dest, src, param); + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, contextSz); + WH_T32(magic, dest, src, preHashType); + WH_T32(magic, dest, src, addRndSz); + + return 0; +} + +/* SLH-DSA DMA Sign Response translation */ +int wh_MessageCrypto_TranslateSlhDsaSignDmaResponse( + uint16_t magic, const whMessageCrypto_SlhDsaSignDmaResponse* src, + whMessageCrypto_SlhDsaSignDmaResponse* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaAddrStatus(magic, &src->dmaAddrStatus, + &dest->dmaAddrStatus); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, sigLen); + return 0; +} + +/* SLH-DSA DMA Verify Request translation */ +int wh_MessageCrypto_TranslateSlhDsaVerifyDmaRequest( + uint16_t magic, const whMessageCrypto_SlhDsaVerifyDmaRequest* src, + whMessageCrypto_SlhDsaVerifyDmaRequest* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->sig, &dest->sig); + if (ret != 0) { + return ret; + } + + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->msg, &dest->msg); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, options); + WH_T32(magic, dest, src, param); + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, contextSz); + WH_T32(magic, dest, src, preHashType); + + return 0; +} + +/* SLH-DSA DMA Verify Response translation */ +int wh_MessageCrypto_TranslateSlhDsaVerifyDmaResponse( + uint16_t magic, const whMessageCrypto_SlhDsaVerifyDmaResponse* src, + whMessageCrypto_SlhDsaVerifyDmaResponse* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaAddrStatus(magic, &src->dmaAddrStatus, + &dest->dmaAddrStatus); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, verifyResult); + return 0; +} + /* ML-KEM DMA Key Generation Request translation */ int wh_MessageCrypto_TranslateMlKemKeyGenDmaRequest( uint16_t magic, const whMessageCrypto_MlKemKeyGenDmaRequest* src, diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index ee1dbd2ec..d99011d88 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * @@ -1032,6 +1032,101 @@ static int _MlDsaKeyCacheExportEnforce(whServerContext* ctx, whKeyId keyId, } #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA +#define WH_SERVER_SLHDSA_MAX_CACHE_DER_SIZE WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE + +WH_UTILS_STATIC_ASSERT(WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE >= + WH_SERVER_SLHDSA_MAX_CACHE_DER_SIZE, + "WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE too small for " + "SLH-DSA key DER"); + +int wh_Server_SlhDsaKeyCacheImport(whServerContext* ctx, SlhDsaKey* key, + whKeyId keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label) +{ + int ret = WH_ERROR_OK; + uint8_t* cacheBuf; + whNvmMetadata* cacheMeta; + uint16_t der_size = 0; + + if ((ctx == NULL) || (key == NULL) || (WH_KEYID_ISERASED(keyId)) || + ((label != NULL) && (label_len > sizeof(cacheMeta->label)))) { + return WH_ERROR_BADARGS; + } + + ret = wh_Server_KeystoreGetCacheSlotChecked( + ctx, keyId, WH_SERVER_SLHDSA_MAX_CACHE_DER_SIZE, &cacheBuf, &cacheMeta); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_SlhDsaSerializeKeyDer( + key, WH_SERVER_SLHDSA_MAX_CACHE_DER_SIZE, cacheBuf, &der_size); + WH_DEBUG_SERVER_VERBOSE("keyId:%u, ret:%d\n", keyId, ret); + } + + if (ret == WH_ERROR_OK) { + cacheMeta->id = keyId; + cacheMeta->len = der_size; + /* clients can't set server-only flags (e.g. trusted KEK) */ + cacheMeta->flags = flags & ~WH_NVM_FLAGS_SERVER_ONLY; + cacheMeta->access = WH_NVM_ACCESS_ANY; + + if ((label != NULL) && (label_len > 0)) { + memcpy(cacheMeta->label, label, label_len); + } + } + + return ret; +} + +int wh_Server_SlhDsaKeyCacheExport(whServerContext* ctx, whKeyId keyId, + SlhDsaKey* key) +{ + uint8_t* cacheBuf; + whNvmMetadata* cacheMeta; + int ret = WH_ERROR_OK; + + if ((ctx == NULL) || (key == NULL) || (WH_KEYID_ISERASED(keyId))) { + return WH_ERROR_BADARGS; + } + + ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cacheBuf, &cacheMeta); + + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_SlhDsaDeserializeKeyDer(cacheBuf, cacheMeta->len, key); + WH_DEBUG_SERVER_VERBOSE("keyId:%u, ret:%d\n", keyId, ret); + } + return ret; +} + +static int _SlhDsaKeyCacheExportEnforce(whServerContext* ctx, whKeyId keyId, + whNvmFlags requiredUsage, + SlhDsaKey* key) +{ + uint8_t* cacheBuf; + whNvmMetadata* cacheMeta; + int ret; + + if ((ctx == NULL) || (key == NULL) || (WH_KEYID_ISERASED(keyId))) { + return WH_ERROR_BADARGS; + } + + /* Freshen, check usage and deserialize under one NVM lock */ + ret = WH_SERVER_NVM_LOCK(ctx); + if (ret == WH_ERROR_OK) { + ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cacheBuf, &cacheMeta); + if (ret == WH_ERROR_OK) { + ret = wh_Server_KeystoreEnforceKeyUsage(cacheMeta, requiredUsage); + } + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_SlhDsaDeserializeKeyDer(cacheBuf, cacheMeta->len, + key); + WH_DEBUG_SERVER_VERBOSE("keyId:%u, ret:%d\n", keyId, ret); + } + (void)WH_SERVER_NVM_UNLOCK(ctx); + } /* WH_SERVER_NVM_LOCK() */ + return ret; +} +#endif /* WOLFSSL_HAVE_SLHDSA */ + #ifdef WOLFSSL_HAVE_MLKEM /* The cache import below always requests a max-size slot, so a build whose big * cache buffer cannot hold one has no working ML-KEM cache keygen or import. */ @@ -5561,40 +5656,158 @@ static int _HandleMlDsaCheckPrivKey(whServerContext* ctx, uint16_t magic, } #endif /* WOLFSSL_HAVE_MLDSA */ -#ifdef WOLFSSL_HAVE_MLKEM -static int _IsMlKemLevelSupported(int level) +#ifdef WOLFSSL_HAVE_SLHDSA +static int _IsSlhDsaParamSupported(int param) { - int ret = 0; + /* Unbuilt parameter sets report no signature size */ + return (wc_SlhDsaKey_SigSizeFromParam((enum SlhDsaParam)param) > 0); +} - switch (level) { -#ifndef WOLFSSL_NO_ML_KEM_512 - case WC_ML_KEM_512: - ret = 1; - break; -#endif -#ifndef WOLFSSL_NO_ML_KEM_768 - case WC_ML_KEM_768: - ret = 1; - break; -#endif -#ifndef WOLFSSL_NO_ML_KEM_1024 - case WC_ML_KEM_1024: - ret = 1; - break; -#endif - default: - ret = 0; - break; +/* param is only a hint; the cached DER sets the real parameter set */ +static int _SlhDsaInitForCachedKey(SlhDsaKey* key, uint32_t param, int devId) +{ + enum SlhDsaParam hint = (enum SlhDsaParam)param; + + if (0 == _IsSlhDsaParamSupported((int)param)) { + hint = WC_SLHDSA_DEFAULT_PARAM; + } + return wc_SlhDsaKey_Init(key, hint, NULL, devId); +} + +/* Load the signing or verifying key named by the request. */ +static int _SlhDsaLoadKey(whServerContext* ctx, whKeyId key_id, + whNvmFlags requiredUsage, SlhDsaKey* key) +{ + return _SlhDsaKeyCacheExportEnforce(ctx, key_id, requiredUsage, key); +} + +#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY +/* Largest SLH-DSA n in bytes (FIPS 205: 16, 24 or 32) */ +#define WH_SLHDSA_MAX_N (32U) + +/* Sign with whichever FIPS 205 entry point the request selected. */ +static int _SlhDsaSignDispatch(whServerContext* ctx, SlhDsaKey* key, + uint32_t options, const byte* in, word32 in_len, + const byte* context, uint32_t contextSz, + uint32_t preHashType, const byte* addRnd, + uint32_t addRndSz, byte* sig, word32* sigLen) +{ + int ret; + uint32_t n; + byte rnd[WH_SLHDSA_MAX_N]; + int mprime = !!(options & WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_MPRIME); + int randomized = + !!(options & WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_RANDOMIZED); + + if ((key == NULL) || (key->params == NULL)) { + return WH_ERROR_BADARGS; + } + n = (uint32_t)key->params->n; + if (n > sizeof(rnd)) { + return WH_ERROR_BADARGS; + } + + /* Sign APIs take no randomizer length; they read exactly n bytes */ + if ((addRndSz != 0) && (addRndSz != n)) { + return WH_ERROR_BADARGS; + } + + if (mprime) { + if (addRndSz > 0) { + ret = wc_SlhDsaKey_SignMsgWithRandom(key, in, in_len, sig, sigLen, + addRnd); + } + else if (randomized) { + /* The M' sign API takes no RNG, so generate the randomizer here */ + ret = wc_RNG_GenerateBlock(ctx->crypto->rng, rnd, n); + if (ret == 0) { + ret = wc_SlhDsaKey_SignMsgWithRandom(key, in, in_len, sig, + sigLen, rnd); + } + wc_ForceZero(rnd, sizeof(rnd)); + } + else { + /* Use the server's copy of the key for randomization */ + ret = + wc_SlhDsaKey_SignMsgDeterministic(key, in, in_len, sig, sigLen); + } + } + else if (preHashType != WC_HASH_TYPE_NONE) { + if (addRndSz > 0) { + ret = wc_SlhDsaKey_SignHashWithRandom( + key, context, (byte)contextSz, in, in_len, + (enum wc_HashType)preHashType, sig, sigLen, addRnd); + } + else if (randomized) { + ret = wc_SlhDsaKey_SignHash(key, context, (byte)contextSz, in, + in_len, (enum wc_HashType)preHashType, + sig, sigLen, ctx->crypto->rng); + } + else { + ret = wc_SlhDsaKey_SignHashDeterministic( + key, context, (byte)contextSz, in, in_len, + (enum wc_HashType)preHashType, sig, sigLen); + } + } + else { + if (addRndSz > 0) { + ret = wc_SlhDsaKey_SignWithRandom(key, context, (byte)contextSz, in, + in_len, sig, sigLen, addRnd); + } + else if (randomized) { + ret = wc_SlhDsaKey_Sign(key, context, (byte)contextSz, in, in_len, + sig, sigLen, ctx->crypto->rng); + } + else { + ret = wc_SlhDsaKey_SignDeterministic(key, context, (byte)contextSz, + in, in_len, sig, sigLen); + } } return ret; } +#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */ + +/* Verify using the FIPS 205 entry point the request selected */ +static int _SlhDsaVerifyDispatch(SlhDsaKey* key, uint32_t options, + const byte* sig, word32 sig_len, + const byte* msg, word32 msg_len, + const byte* context, uint32_t contextSz, + uint32_t preHashType, int* out_result) +{ + int ret; + int mprime = !!(options & WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_MPRIME); -static int _HandleMlKemKeyGen(whServerContext* ctx, uint16_t magic, int devId, - const void* cryptoDataIn, uint16_t inSize, - void* cryptoDataOut, uint16_t* outSize) + if (mprime) { + ret = wc_SlhDsaKey_VerifyMsg(key, msg, msg_len, sig, sig_len); + } + else if (preHashType != WC_HASH_TYPE_NONE) { + ret = wc_SlhDsaKey_VerifyHash(key, context, (byte)contextSz, msg, + msg_len, (enum wc_HashType)preHashType, + sig, sig_len); + } + else { + ret = wc_SlhDsaKey_Verify(key, context, (byte)contextSz, msg, msg_len, + sig, sig_len); + } + + if (ret == 0) { + *out_result = 1; + } + else if (ret == WC_NO_ERR_TRACE(SIG_VERIFY_E)) { + /* A bad signature is a result, not an error */ + *out_result = 0; + ret = 0; + } + + return ret; +} + +static int _HandleSlhDsaKeyGen(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) { -#ifdef WOLFSSL_MLKEM_NO_MAKE_KEY +#ifdef WOLFSSL_SLHDSA_VERIFY_ONLY (void)ctx; (void)magic; (void)devId; @@ -5604,102 +5817,141 @@ static int _HandleMlKemKeyGen(whServerContext* ctx, uint16_t magic, int devId, (void)outSize; return WH_ERROR_NOHANDLER; #else - int ret = WH_ERROR_OK; - MlKemKey key[1]; - whMessageCrypto_MlKemKeyGenRequest req; - whMessageCrypto_MlKemKeyGenResponse res; - uint16_t res_size = 0; - uint8_t* res_out; - uint16_t max_size; - whKeyId key_id; - uint16_t label_size = WH_NVM_LABEL_LEN; + int ret = WH_ERROR_OK; + SlhDsaKey key[1]; + whMessageCrypto_SlhDsaKeyGenRequest req; + whMessageCrypto_SlhDsaKeyGenResponse res; + whKeyId key_id; + whNvmFlags flags; + uint8_t* label; + byte* seed; + uint32_t seedSz; + uint8_t* res_out; + uint16_t max_size; + uint16_t res_size = 0; + int param; + uint32_t available; - if (inSize < sizeof(whMessageCrypto_MlKemKeyGenRequest)) { + memset(&res, 0, sizeof(res)); + + if (inSize < sizeof(whMessageCrypto_SlhDsaKeyGenRequest)) { return WH_ERROR_BADARGS; } - ret = wh_MessageCrypto_TranslateMlKemKeyGenRequest( - magic, (whMessageCrypto_MlKemKeyGenRequest*)cryptoDataIn, &req); + /* Translate the request */ + ret = wh_MessageCrypto_TranslateSlhDsaKeyGenRequest( + magic, (whMessageCrypto_SlhDsaKeyGenRequest*)cryptoDataIn, &req); if (ret != 0) { return ret; } - key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, ctx->comm->client_id, - req.keyId); - res_out = (uint8_t*)cryptoDataOut + sizeof(whMessageCrypto_MlKemKeyGenResponse); - max_size = (uint16_t)(WOLFHSM_CFG_COMM_DATA_LEN - - (res_out - (uint8_t*)cryptoDataOut)); + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + param = (int)req.param; + flags = req.flags; + label = req.label; + seedSz = req.seedSz; + seed = (byte*)cryptoDataIn + sizeof(whMessageCrypto_SlhDsaKeyGenRequest); - if (!_IsMlKemLevelSupported((int)req.level)) { + available = + (uint32_t)(inSize - sizeof(whMessageCrypto_SlhDsaKeyGenRequest)); + + if (seedSz > available) { + /* seedSz is the value being rejected, so clear what is present */ + wc_ForceZero(seed, available); return WH_ERROR_BADARGS; } - ret = wc_MlKemKey_Init(key, (int)req.level, NULL, devId); + /* Advance the pointer to the response data */ + res_out = + (uint8_t*)cryptoDataOut + sizeof(whMessageCrypto_SlhDsaKeyGenResponse); + max_size = (uint16_t)(WOLFHSM_CFG_COMM_DATA_LEN - + sizeof(whMessageCrypto_GenericResponseHeader) - + sizeof(whMessageCrypto_SlhDsaKeyGenResponse)); + + if (0 == _IsSlhDsaParamSupported(param)) { + ret = WH_ERROR_BADARGS; + } + else { + ret = wc_SlhDsaKey_Init(key, (enum SlhDsaParam)param, NULL, devId); + } + if (ret == 0) { - ret = wc_MlKemKey_MakeKey(key, ctx->crypto->rng); + if (seedSz > 0) { + /* The seed is the contiguous SK.seed || SK.prf || PK.seed */ + word32 n = seedSz / 3; + if (((seedSz % 3) != 0) || (n != key->params->n)) { + ret = WH_ERROR_BADARGS; + } + else { + ret = wc_SlhDsaKey_MakeKeyWithRandom(key, seed, n, seed + n, n, + seed + 2 * n, n); + } + } + else { + ret = wc_SlhDsaKey_MakeKey(key, ctx->crypto->rng); + } + /* The seed is the private key; clear it before writing the response */ + wc_ForceZero(seed, seedSz); + if (ret == 0) { - if ((req.flags & WH_NVM_FLAGS_EPHEMERAL) != 0) { + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + /* Must serialize the key into the response message. */ key_id = WH_KEYID_ERASED; - ret = wh_Crypto_MlKemSerializeKey(key, max_size, res_out, - &res_size); + ret = wh_Crypto_SlhDsaSerializeKeyDer(key, max_size, res_out, + &res_size); + if (ret != 0) { + wc_ForceZero(res_out, max_size); + res_size = 0; + } } else { - /* Hold the NVM lock so id allocation and cache import are - * atomic with respect to other server contexts under - * THREADSAFE. */ + /* Must import the key into the cache and return keyid */ + res_size = 0; + /* Lock to ensure alloc and import are atomic */ ret = WH_SERVER_NVM_LOCK(ctx); if (ret == WH_ERROR_OK) { if (WH_KEYID_ISERASED(key_id)) { ret = wh_Server_KeystoreGetUniqueId(ctx, &key_id); } if (ret == WH_ERROR_OK) { - ret = wh_Server_MlKemKeyCacheImport( - ctx, key, key_id, req.flags, label_size, req.label); + ret = wh_Server_SlhDsaKeyCacheImport( + ctx, key, key_id, flags, WH_NVM_LABEL_LEN, label); } (void)WH_SERVER_NVM_UNLOCK(ctx); } /* WH_SERVER_NVM_LOCK() */ - if (ret == WH_ERROR_OK) { - /* Best-effort public key export: when the serialized - * public key fits in the response body, return it so the - * client can skip a separate ExportPublicKey call. When it - * does not fit (small comm buffer or a large key), leave the - * body empty and keep the cached key. Plain MakeCacheKey - * callers ignore the body and see no regression; - * MakeCacheKeyAndExportPublic callers detect the empty body - * and evict the key themselves. */ - word32 pubSize = 0; - if ((wc_MlKemKey_PublicKeySize(key, &pubSize) == 0) && - ((uint32_t)pubSize <= (uint32_t)max_size) && - (wc_MlKemKey_EncodePublicKey(key, res_out, pubSize) == - 0)) { - res_size = (uint16_t)pubSize; - } - else { - res_size = 0; - } + if (ret == 0) { + /* Return the public key if it fits, else empty body */ + int pub_ret = + wc_SlhDsaKey_PublicKeyToDer(key, res_out, max_size, 1); + res_size = (pub_ret > 0) ? (uint16_t)pub_ret : 0; } } } - wc_MlKemKey_Free(key); + wc_SlhDsaKey_Free(key); + } + else { + wc_ForceZero(seed, seedSz); } if (ret == WH_ERROR_OK) { res.keyId = wh_KeyId_TranslateToClient(key_id); res.len = res_size; - (void)wh_MessageCrypto_TranslateMlKemKeyGenResponse( - magic, &res, (whMessageCrypto_MlKemKeyGenResponse*)cryptoDataOut); - *outSize = sizeof(whMessageCrypto_MlKemKeyGenResponse) + res_size; - } + (void)wh_MessageCrypto_TranslateSlhDsaKeyGenResponse(magic, &res, + cryptoDataOut); + + *outSize = sizeof(whMessageCrypto_SlhDsaKeyGenResponse) + res_size; + } return ret; -#endif /* WOLFSSL_MLKEM_NO_MAKE_KEY */ +#endif /* WOLFSSL_SLHDSA_VERIFY_ONLY */ } -static int _HandleMlKemEncaps(whServerContext* ctx, uint16_t magic, int devId, - const void* cryptoDataIn, uint16_t inSize, - void* cryptoDataOut, uint16_t* outSize) +static int _HandleSlhDsaSign(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) { -#ifdef WOLFSSL_MLKEM_NO_ENCAPSULATE +#ifdef WOLFSSL_SLHDSA_VERIFY_ONLY (void)ctx; (void)magic; (void)devId; @@ -5709,100 +5961,202 @@ static int _HandleMlKemEncaps(whServerContext* ctx, uint16_t magic, int devId, (void)outSize; return WH_ERROR_NOHANDLER; #else - int ret = WH_ERROR_OK; - MlKemKey key[1]; - whMessageCrypto_MlKemEncapsRequest req; - whMessageCrypto_MlKemEncapsResponse res; - whKeyId key_id; - uint8_t* res_ct; - uint8_t* res_ss; - word32 ct_len; - word32 ss_len; - word32 max_out; - int evict = 0; - int keyInited = 0; + int ret; + SlhDsaKey key[1]; + whMessageCrypto_SlhDsaSignRequest req; + whMessageCrypto_SlhDsaSignResponse res; + byte* in; + byte* req_context; + byte* req_addRnd; + byte* res_out; + whKeyId key_id; + word32 in_len; + word32 available; + word32 max_len; + word32 res_len; + uint32_t contextSz; + uint32_t preHashType; + uint32_t addRndSz; + uint32_t options; + int evict; + int sigLen; - if (inSize < sizeof(whMessageCrypto_MlKemEncapsRequest)) { + memset(&res, 0, sizeof(res)); + + if (inSize < sizeof(whMessageCrypto_SlhDsaSignRequest)) { return WH_ERROR_BADARGS; } - ret = wh_MessageCrypto_TranslateMlKemEncapsRequest( - magic, (whMessageCrypto_MlKemEncapsRequest*)cryptoDataIn, &req); + /* Translate the request */ + ret = wh_MessageCrypto_TranslateSlhDsaSignRequest( + magic, (whMessageCrypto_SlhDsaSignRequest*)cryptoDataIn, &req); if (ret != 0) { return ret; } - key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, ctx->comm->client_id, - req.keyId); - evict = !!(req.options & WH_MESSAGE_CRYPTO_MLKEM_ENCAPS_OPTIONS_EVICT); + in = (uint8_t*)(cryptoDataIn) + sizeof(whMessageCrypto_SlhDsaSignRequest); + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + in_len = req.sz; + contextSz = req.contextSz; + preHashType = req.preHashType; + addRndSz = req.addRndSz; + options = req.options; + evict = !!(options & WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_EVICT); - if (!_IsMlKemLevelSupported((int)req.level)) { - ret = WH_ERROR_BADARGS; - goto cleanup; + /* Validate the declared lengths against the remaining payload */ + available = inSize - sizeof(whMessageCrypto_SlhDsaSignRequest); + if (in_len > available) { + return WH_ERROR_BADARGS; } + if (contextSz > (available - in_len)) { + return WH_ERROR_BADARGS; + } + if (addRndSz > (available - in_len - contextSz)) { + return WH_ERROR_BADARGS; + } + if (contextSz > WH_CRYPTO_SLHDSA_MAX_CTX_LEN) { + return WH_ERROR_BADARGS; + } + req_context = (contextSz > 0) ? (in + in_len) : NULL; + req_addRnd = (addRndSz > 0) ? (in + in_len + contextSz) : NULL; - ret = wc_MlKemKey_Init(key, (int)req.level, NULL, devId); + /* Advance the pointer to the response data */ + res_out = + (uint8_t*)(cryptoDataOut) + sizeof(whMessageCrypto_SlhDsaSignResponse); + max_len = (word32)(WOLFHSM_CFG_COMM_DATA_LEN - + sizeof(whMessageCrypto_GenericResponseHeader) - + sizeof(whMessageCrypto_SlhDsaSignResponse)); + res_len = max_len; + + ret = _SlhDsaInitForCachedKey(key, req.param, devId); if (ret == 0) { - keyInited = 1; - /* Export the key, enforcing the derive usage policy against the same - * locked snapshot that is exported */ - ret = _MlKemKeyCacheExportEnforce(ctx, key_id, - WH_NVM_FLAGS_USAGE_DERIVE, key); + ret = _SlhDsaLoadKey(ctx, key_id, WH_NVM_FLAGS_USAGE_SIGN, key); + if (ret == WH_ERROR_OK) { + /* SLH-DSA sigs can be nearly 50kB. Bail early if it won't fit. */ + sigLen = wc_SlhDsaKey_SigSize(key); + if (sigLen <= 0) { + ret = WH_ERROR_ABORTED; + } + else if ((word32)sigLen > max_len) { + ret = WH_ERROR_BUFFER_SIZE; + } + } + if (ret == WH_ERROR_OK) { + ret = _SlhDsaSignDispatch(ctx, key, options, in, in_len, + req_context, contextSz, preHashType, + req_addRnd, addRndSz, res_out, &res_len); + } + wc_SlhDsaKey_Free(key); + } + if (evict != 0) { + /* User requested to evict from cache, even if the call failed */ + _CryptoEvictKeyLocked(ctx, key_id); } + if (ret == 0) { + res.sz = res_len; - /* Verify the exported key matches the requested level */ - if (ret == WH_ERROR_OK && key->type != (int)req.level) { - ret = WH_ERROR_BADARGS; + (void)wh_MessageCrypto_TranslateSlhDsaSignResponse( + magic, &res, (whMessageCrypto_SlhDsaSignResponse*)cryptoDataOut); + + *outSize = sizeof(whMessageCrypto_SlhDsaSignResponse) + res_len; } + return ret; +#endif /* WOLFSSL_SLHDSA_VERIFY_ONLY */ +} - if (ret == WH_ERROR_OK) { - ret = wc_MlKemKey_CipherTextSize(key, &ct_len); - if (ret == WH_ERROR_OK) { - ret = wc_MlKemKey_SharedSecretSize(key, &ss_len); - } +static int _HandleSlhDsaVerify(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) +{ + int ret; + SlhDsaKey key[1]; + whMessageCrypto_SlhDsaVerifyRequest req; + whMessageCrypto_SlhDsaVerifyResponse res; + byte* req_sig; + byte* req_hash; + byte* req_context; + whKeyId key_id; + uint32_t options; + uint32_t hash_len; + uint32_t sig_len; + uint32_t contextSz; + uint32_t preHashType; + uint32_t available; + int evict; + int result = 0; + + memset(&res, 0, sizeof(res)); + + if (inSize < sizeof(whMessageCrypto_SlhDsaVerifyRequest)) { + return WH_ERROR_BADARGS; } - if (ret == WH_ERROR_OK) { - res_ct = (uint8_t*)cryptoDataOut + sizeof(whMessageCrypto_MlKemEncapsResponse); - res_ss = res_ct + ct_len; - max_out = (word32)(WOLFHSM_CFG_COMM_DATA_LEN - - ((uint8_t*)res_ct - (uint8_t*)cryptoDataOut)); - if (ct_len + ss_len > max_out) { - ret = WH_ERROR_BADARGS; - } + /* Translate the request */ + ret = wh_MessageCrypto_TranslateSlhDsaVerifyRequest( + magic, (whMessageCrypto_SlhDsaVerifyRequest*)cryptoDataIn, &req); + if (ret != 0) { + return ret; } - if (ret == WH_ERROR_OK) { - ret = wc_MlKemKey_Encapsulate(key, res_ct, res_ss, ctx->crypto->rng); - if (ret == WH_ERROR_OK) { - res.ctSz = ct_len; - res.ssSz = ss_len; - (void)wh_MessageCrypto_TranslateMlKemEncapsResponse( - magic, &res, (whMessageCrypto_MlKemEncapsResponse*)cryptoDataOut); - *outSize = sizeof(whMessageCrypto_MlKemEncapsResponse) + ct_len + ss_len; - } - else { - /* Zero sensitive data on failure */ - wc_ForceZero(res_ss, ss_len); - } + options = req.options; + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + hash_len = req.hashSz; + sig_len = req.sigSz; + contextSz = req.contextSz; + preHashType = req.preHashType; + req_sig = + (uint8_t*)(cryptoDataIn) + sizeof(whMessageCrypto_SlhDsaVerifyRequest); + evict = !!(options & WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_EVICT); + + /* Validate lengths against available payload (overflow-safe) */ + available = inSize - sizeof(whMessageCrypto_SlhDsaVerifyRequest); + if ((sig_len > available) || (hash_len > available) || + (sig_len > (available - hash_len))) { + return WH_ERROR_BADARGS; + } + if (contextSz > (available - sig_len - hash_len)) { + return WH_ERROR_BADARGS; + } + if (contextSz > WH_CRYPTO_SLHDSA_MAX_CTX_LEN) { + return WH_ERROR_BADARGS; } - if (keyInited) { - wc_MlKemKey_Free(key); + req_hash = req_sig + sig_len; + req_context = (contextSz > 0) ? (req_hash + hash_len) : NULL; + + ret = _SlhDsaInitForCachedKey(key, req.param, devId); + if (ret == 0) { + ret = _SlhDsaLoadKey(ctx, key_id, WH_NVM_FLAGS_USAGE_VERIFY, key); + if (ret == WH_ERROR_OK) { + ret = _SlhDsaVerifyDispatch(key, options, req_sig, sig_len, + req_hash, hash_len, req_context, + contextSz, preHashType, &result); + } + wc_SlhDsaKey_Free(key); } -cleanup: if (evict != 0) { + /* User requested to evict from cache, even if the call failed */ _CryptoEvictKeyLocked(ctx, key_id); } + if (ret == 0) { + res.res = result; + + (void)wh_MessageCrypto_TranslateSlhDsaVerifyResponse( + magic, &res, (whMessageCrypto_SlhDsaVerifyResponse*)cryptoDataOut); + + *outSize = sizeof(whMessageCrypto_SlhDsaVerifyResponse); + } return ret; -#endif /* WOLFSSL_MLKEM_NO_ENCAPSULATE */ } -static int _HandleMlKemDecaps(whServerContext* ctx, uint16_t magic, int devId, - const void* cryptoDataIn, uint16_t inSize, - void* cryptoDataOut, uint16_t* outSize) +static int _HandleSlhDsaCheckPrivKey(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) { -#ifdef WOLFSSL_MLKEM_NO_DECAPSULATE +#ifdef WOLFSSL_SLHDSA_VERIFY_ONLY (void)ctx; (void)magic; (void)devId; @@ -5812,168 +6166,534 @@ static int _HandleMlKemDecaps(whServerContext* ctx, uint16_t magic, int devId, (void)outSize; return WH_ERROR_NOHANDLER; #else - int ret = WH_ERROR_OK; - MlKemKey key[1]; - whMessageCrypto_MlKemDecapsRequest req; - whMessageCrypto_MlKemDecapsResponse res; - whKeyId key_id; - byte* req_ct; - byte* res_ss; - uint32_t available; - word32 ss_len; - word32 max_out; - int evict = 0; - int keyInited = 0; + int ret; + SlhDsaKey key[1]; + whMessageCrypto_SlhDsaCheckPrivKeyRequest req; + whMessageCrypto_SlhDsaCheckPrivKeyResponse res; + byte* req_pub; + whKeyId key_id; + uint32_t pubSz; + uint32_t available; + int evict; + int result = 0; - if (inSize < sizeof(whMessageCrypto_MlKemDecapsRequest)) { + memset(&res, 0, sizeof(res)); + + if (inSize < sizeof(whMessageCrypto_SlhDsaCheckPrivKeyRequest)) { return WH_ERROR_BADARGS; } - ret = wh_MessageCrypto_TranslateMlKemDecapsRequest( - magic, (whMessageCrypto_MlKemDecapsRequest*)cryptoDataIn, &req); + ret = wh_MessageCrypto_TranslateSlhDsaCheckPrivKeyRequest( + magic, (whMessageCrypto_SlhDsaCheckPrivKeyRequest*)cryptoDataIn, &req); if (ret != 0) { return ret; } - key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, ctx->comm->client_id, - req.keyId); - evict = !!(req.options & WH_MESSAGE_CRYPTO_MLKEM_DECAPS_OPTIONS_EVICT); - - if (!_IsMlKemLevelSupported((int)req.level)) { - ret = WH_ERROR_BADARGS; - goto cleanup; - } - - available = inSize - sizeof(whMessageCrypto_MlKemDecapsRequest); - if (req.ctSz > available) { - ret = WH_ERROR_BADARGS; - goto cleanup; - } - req_ct = (byte*)cryptoDataIn + sizeof(whMessageCrypto_MlKemDecapsRequest); - - ret = wc_MlKemKey_Init(key, (int)req.level, NULL, devId); - if (ret == WH_ERROR_OK) { - keyInited = 1; - /* Export the key, enforcing the derive usage policy against the same - * locked snapshot that is exported */ - ret = _MlKemKeyCacheExportEnforce(ctx, key_id, - WH_NVM_FLAGS_USAGE_DERIVE, key); - } - - /* Verify the exported key matches the requested level */ - if (ret == WH_ERROR_OK && key->type != (int)req.level) { - ret = WH_ERROR_BADARGS; - } - - if (ret == WH_ERROR_OK) { - ret = wc_MlKemKey_SharedSecretSize(key, &ss_len); - } + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + pubSz = req.pubSz; + evict = + !!(req.options & WH_MESSAGE_CRYPTO_SLHDSA_CHECKPRIVKEY_OPTIONS_EVICT); + req_pub = (uint8_t*)(cryptoDataIn) + + sizeof(whMessageCrypto_SlhDsaCheckPrivKeyRequest); - if (ret == WH_ERROR_OK) { - res_ss = (byte*)cryptoDataOut + sizeof(whMessageCrypto_MlKemDecapsResponse); - max_out = (word32)(WOLFHSM_CFG_COMM_DATA_LEN - - ((uint8_t*)res_ss - (uint8_t*)cryptoDataOut)); - if (ss_len > max_out) { - ret = WH_ERROR_BADARGS; - } + available = inSize - sizeof(whMessageCrypto_SlhDsaCheckPrivKeyRequest); + if (pubSz > available) { + return WH_ERROR_BADARGS; } - if (ret == WH_ERROR_OK) { - ret = wc_MlKemKey_Decapsulate(key, res_ss, req_ct, req.ctSz); + ret = _SlhDsaInitForCachedKey(key, req.param, devId); + if (ret == 0) { + ret = _SlhDsaLoadKey(ctx, key_id, WH_NVM_FLAGS_USAGE_SIGN, key); if (ret == WH_ERROR_OK) { - res.ssSz = ss_len; - (void)wh_MessageCrypto_TranslateMlKemDecapsResponse( - magic, &res, (whMessageCrypto_MlKemDecapsResponse*)cryptoDataOut); - *outSize = sizeof(whMessageCrypto_MlKemDecapsResponse) + ss_len; + /* Recompute the public root from the private seeds */ + ret = wc_SlhDsaKey_CheckKey(key); } - else { - /* Zero sensitive data on failure */ - wc_ForceZero(res_ss, ss_len); + if (ret == WH_ERROR_OK) { + /* Check pubKey too, if provided */ + uint32_t expected = 2U * (uint32_t)key->params->n; + if (pubSz == 0) { + result = 1; + } + else if (pubSz != expected) { + result = 0; + } + else { + result = + (memcmp(key->sk + 2 * key->params->n, req_pub, pubSz) == 0); + } } + wc_SlhDsaKey_Free(key); } - - if (keyInited) { - wc_MlKemKey_Free(key); - } -cleanup: if (evict != 0) { _CryptoEvictKeyLocked(ctx, key_id); } + if (ret == 0) { + /* Mirror wc_SlhDsaKey_CheckKey: 0 on match, WC_KEY_MISMATCH_E on not */ + res.res = (result != 0) ? 0 : WC_KEY_MISMATCH_E; + + wh_MessageCrypto_TranslateSlhDsaCheckPrivKeyResponse( + magic, &res, + (whMessageCrypto_SlhDsaCheckPrivKeyResponse*)cryptoDataOut); + + *outSize = sizeof(whMessageCrypto_SlhDsaCheckPrivKeyResponse); + } return ret; -#endif /* WOLFSSL_MLKEM_NO_DECAPSULATE */ +#endif /* WOLFSSL_SLHDSA_VERIFY_ONLY */ } -#endif /* WOLFSSL_HAVE_MLKEM */ +#endif /* WOLFSSL_HAVE_SLHDSA */ -#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) -static int _HandlePqcSigAlgorithm(whServerContext* ctx, uint16_t magic, - int devId, const void* cryptoDataIn, - uint16_t cryptoInSize, void* cryptoDataOut, - uint16_t* cryptoOutSize, uint32_t pkAlgoType, - uint32_t pqAlgoType) +#ifdef WOLFSSL_HAVE_MLKEM +static int _IsMlKemLevelSupported(int level) { - int ret = WH_ERROR_NOHANDLER; + int ret = 0; - /* Dispatch the appropriate algorithm handler based on the requested PK type - * and the algorithm type. */ - switch (pqAlgoType) { -#ifdef WOLFSSL_HAVE_MLDSA - case WC_PQC_SIG_TYPE_MLDSA: { - switch (pkAlgoType) { - case WC_PK_TYPE_PQC_SIG_KEYGEN: - ret = _HandleMlDsaKeyGen(ctx, magic, devId, cryptoDataIn, - cryptoInSize, cryptoDataOut, - cryptoOutSize); - break; - case WC_PK_TYPE_PQC_SIG_SIGN: - ret = _HandleMlDsaSign(ctx, magic, devId, cryptoDataIn, - cryptoInSize, cryptoDataOut, - cryptoOutSize); - break; - case WC_PK_TYPE_PQC_SIG_VERIFY: - ret = _HandleMlDsaVerify(ctx, magic, devId, cryptoDataIn, - cryptoInSize, cryptoDataOut, - cryptoOutSize); - break; - case WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY: - ret = _HandleMlDsaCheckPrivKey( - ctx, magic, devId, cryptoDataIn, cryptoInSize, - cryptoDataOut, cryptoOutSize); - break; - default: - ret = WH_ERROR_NOHANDLER; - break; - } - } break; -#endif /* WOLFSSL_HAVE_MLDSA */ + switch (level) { +#ifndef WOLFSSL_NO_ML_KEM_512 + case WC_ML_KEM_512: + ret = 1; + break; +#endif +#ifndef WOLFSSL_NO_ML_KEM_768 + case WC_ML_KEM_768: + ret = 1; + break; +#endif +#ifndef WOLFSSL_NO_ML_KEM_1024 + case WC_ML_KEM_1024: + ret = 1; + break; +#endif default: - ret = WH_ERROR_NOHANDLER; + ret = 0; break; } return ret; } -#endif -#if defined(WOLFSSL_HAVE_MLKEM) -static int _HandlePqcKemAlgorithm(whServerContext* ctx, uint16_t magic, - int devId, const void* cryptoDataIn, - uint16_t cryptoInSize, void* cryptoDataOut, - uint16_t* cryptoOutSize, uint32_t pkAlgoType, - uint32_t pqAlgoType) +static int _HandleMlKemKeyGen(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) { - int ret = WH_ERROR_NOHANDLER; - - switch (pqAlgoType) { - case WC_PQC_KEM_TYPE_KYBER: { - switch (pkAlgoType) { - case WC_PK_TYPE_PQC_KEM_KEYGEN: - ret = _HandleMlKemKeyGen(ctx, magic, devId, cryptoDataIn, - cryptoInSize, cryptoDataOut, - cryptoOutSize); - break; - case WC_PK_TYPE_PQC_KEM_ENCAPS: - ret = _HandleMlKemEncaps(ctx, magic, devId, cryptoDataIn, - cryptoInSize, cryptoDataOut, +#ifdef WOLFSSL_MLKEM_NO_MAKE_KEY + (void)ctx; + (void)magic; + (void)devId; + (void)cryptoDataIn; + (void)inSize; + (void)cryptoDataOut; + (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret = WH_ERROR_OK; + MlKemKey key[1]; + whMessageCrypto_MlKemKeyGenRequest req; + whMessageCrypto_MlKemKeyGenResponse res; + uint16_t res_size = 0; + uint8_t* res_out; + uint16_t max_size; + whKeyId key_id; + uint16_t label_size = WH_NVM_LABEL_LEN; + + if (inSize < sizeof(whMessageCrypto_MlKemKeyGenRequest)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateMlKemKeyGenRequest( + magic, (whMessageCrypto_MlKemKeyGenRequest*)cryptoDataIn, &req); + if (ret != 0) { + return ret; + } + + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + res_out = + (uint8_t*)cryptoDataOut + sizeof(whMessageCrypto_MlKemKeyGenResponse); + max_size = (uint16_t)(WOLFHSM_CFG_COMM_DATA_LEN - + (res_out - (uint8_t*)cryptoDataOut)); + + if (!_IsMlKemLevelSupported((int)req.level)) { + return WH_ERROR_BADARGS; + } + + ret = wc_MlKemKey_Init(key, (int)req.level, NULL, devId); + if (ret == 0) { + ret = wc_MlKemKey_MakeKey(key, ctx->crypto->rng); + if (ret == 0) { + if ((req.flags & WH_NVM_FLAGS_EPHEMERAL) != 0) { + key_id = WH_KEYID_ERASED; + ret = wh_Crypto_MlKemSerializeKey(key, max_size, res_out, + &res_size); + } + else { + /* Hold the NVM lock so id allocation and cache import are + * atomic with respect to other server contexts under + * THREADSAFE. */ + ret = WH_SERVER_NVM_LOCK(ctx); + if (ret == WH_ERROR_OK) { + if (WH_KEYID_ISERASED(key_id)) { + ret = wh_Server_KeystoreGetUniqueId(ctx, &key_id); + } + if (ret == WH_ERROR_OK) { + ret = wh_Server_MlKemKeyCacheImport( + ctx, key, key_id, req.flags, label_size, req.label); + } + (void)WH_SERVER_NVM_UNLOCK(ctx); + } /* WH_SERVER_NVM_LOCK() */ + if (ret == WH_ERROR_OK) { + /* Best-effort public key export: when the serialized + * public key fits in the response body, return it so the + * client can skip a separate ExportPublicKey call. When it + * does not fit (small comm buffer or a large key), leave + * the body empty and keep the cached key. Plain + * MakeCacheKey callers ignore the body and see no + * regression; MakeCacheKeyAndExportPublic callers detect + * the empty body and evict the key themselves. */ + word32 pubSize = 0; + if ((wc_MlKemKey_PublicKeySize(key, &pubSize) == 0) && + ((uint32_t)pubSize <= (uint32_t)max_size) && + (wc_MlKemKey_EncodePublicKey(key, res_out, pubSize) == + 0)) { + res_size = (uint16_t)pubSize; + } + else { + res_size = 0; + } + } + } + } + wc_MlKemKey_Free(key); + } + + if (ret == WH_ERROR_OK) { + res.keyId = wh_KeyId_TranslateToClient(key_id); + res.len = res_size; + (void)wh_MessageCrypto_TranslateMlKemKeyGenResponse( + magic, &res, (whMessageCrypto_MlKemKeyGenResponse*)cryptoDataOut); + *outSize = sizeof(whMessageCrypto_MlKemKeyGenResponse) + res_size; + } + + return ret; +#endif /* WOLFSSL_MLKEM_NO_MAKE_KEY */ +} + +static int _HandleMlKemEncaps(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) +{ +#ifdef WOLFSSL_MLKEM_NO_ENCAPSULATE + (void)ctx; + (void)magic; + (void)devId; + (void)cryptoDataIn; + (void)inSize; + (void)cryptoDataOut; + (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret = WH_ERROR_OK; + MlKemKey key[1]; + whMessageCrypto_MlKemEncapsRequest req; + whMessageCrypto_MlKemEncapsResponse res; + whKeyId key_id; + uint8_t* res_ct; + uint8_t* res_ss; + word32 ct_len; + word32 ss_len; + word32 max_out; + int evict = 0; + int keyInited = 0; + + if (inSize < sizeof(whMessageCrypto_MlKemEncapsRequest)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateMlKemEncapsRequest( + magic, (whMessageCrypto_MlKemEncapsRequest*)cryptoDataIn, &req); + if (ret != 0) { + return ret; + } + + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + evict = !!(req.options & WH_MESSAGE_CRYPTO_MLKEM_ENCAPS_OPTIONS_EVICT); + + if (!_IsMlKemLevelSupported((int)req.level)) { + ret = WH_ERROR_BADARGS; + goto cleanup; + } + + ret = wc_MlKemKey_Init(key, (int)req.level, NULL, devId); + if (ret == 0) { + keyInited = 1; + /* Export the key, enforcing the derive usage policy against the same + * locked snapshot that is exported */ + ret = _MlKemKeyCacheExportEnforce(ctx, key_id, + WH_NVM_FLAGS_USAGE_DERIVE, key); + } + + /* Verify the exported key matches the requested level */ + if (ret == WH_ERROR_OK && key->type != (int)req.level) { + ret = WH_ERROR_BADARGS; + } + + if (ret == WH_ERROR_OK) { + ret = wc_MlKemKey_CipherTextSize(key, &ct_len); + if (ret == WH_ERROR_OK) { + ret = wc_MlKemKey_SharedSecretSize(key, &ss_len); + } + } + + if (ret == WH_ERROR_OK) { + res_ct = (uint8_t*)cryptoDataOut + + sizeof(whMessageCrypto_MlKemEncapsResponse); + res_ss = res_ct + ct_len; + max_out = (word32)(WOLFHSM_CFG_COMM_DATA_LEN - + ((uint8_t*)res_ct - (uint8_t*)cryptoDataOut)); + if (ct_len + ss_len > max_out) { + ret = WH_ERROR_BADARGS; + } + } + + if (ret == WH_ERROR_OK) { + ret = wc_MlKemKey_Encapsulate(key, res_ct, res_ss, ctx->crypto->rng); + if (ret == WH_ERROR_OK) { + res.ctSz = ct_len; + res.ssSz = ss_len; + (void)wh_MessageCrypto_TranslateMlKemEncapsResponse( + magic, &res, + (whMessageCrypto_MlKemEncapsResponse*)cryptoDataOut); + *outSize = + sizeof(whMessageCrypto_MlKemEncapsResponse) + ct_len + ss_len; + } + else { + /* Zero sensitive data on failure */ + wc_ForceZero(res_ss, ss_len); + } + } + + if (keyInited) { + wc_MlKemKey_Free(key); + } +cleanup: + if (evict != 0) { + _CryptoEvictKeyLocked(ctx, key_id); + } + return ret; +#endif /* WOLFSSL_MLKEM_NO_ENCAPSULATE */ +} + +static int _HandleMlKemDecaps(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) +{ +#ifdef WOLFSSL_MLKEM_NO_DECAPSULATE + (void)ctx; + (void)magic; + (void)devId; + (void)cryptoDataIn; + (void)inSize; + (void)cryptoDataOut; + (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret = WH_ERROR_OK; + MlKemKey key[1]; + whMessageCrypto_MlKemDecapsRequest req; + whMessageCrypto_MlKemDecapsResponse res; + whKeyId key_id; + byte* req_ct; + byte* res_ss; + uint32_t available; + word32 ss_len; + word32 max_out; + int evict = 0; + int keyInited = 0; + + if (inSize < sizeof(whMessageCrypto_MlKemDecapsRequest)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateMlKemDecapsRequest( + magic, (whMessageCrypto_MlKemDecapsRequest*)cryptoDataIn, &req); + if (ret != 0) { + return ret; + } + + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + evict = !!(req.options & WH_MESSAGE_CRYPTO_MLKEM_DECAPS_OPTIONS_EVICT); + + if (!_IsMlKemLevelSupported((int)req.level)) { + ret = WH_ERROR_BADARGS; + goto cleanup; + } + + available = inSize - sizeof(whMessageCrypto_MlKemDecapsRequest); + if (req.ctSz > available) { + ret = WH_ERROR_BADARGS; + goto cleanup; + } + req_ct = (byte*)cryptoDataIn + sizeof(whMessageCrypto_MlKemDecapsRequest); + + ret = wc_MlKemKey_Init(key, (int)req.level, NULL, devId); + if (ret == WH_ERROR_OK) { + keyInited = 1; + /* Export the key, enforcing the derive usage policy against the same + * locked snapshot that is exported */ + ret = _MlKemKeyCacheExportEnforce(ctx, key_id, + WH_NVM_FLAGS_USAGE_DERIVE, key); + } + + /* Verify the exported key matches the requested level */ + if (ret == WH_ERROR_OK && key->type != (int)req.level) { + ret = WH_ERROR_BADARGS; + } + + if (ret == WH_ERROR_OK) { + ret = wc_MlKemKey_SharedSecretSize(key, &ss_len); + } + + if (ret == WH_ERROR_OK) { + res_ss = + (byte*)cryptoDataOut + sizeof(whMessageCrypto_MlKemDecapsResponse); + max_out = (word32)(WOLFHSM_CFG_COMM_DATA_LEN - + ((uint8_t*)res_ss - (uint8_t*)cryptoDataOut)); + if (ss_len > max_out) { + ret = WH_ERROR_BADARGS; + } + } + + if (ret == WH_ERROR_OK) { + ret = wc_MlKemKey_Decapsulate(key, res_ss, req_ct, req.ctSz); + if (ret == WH_ERROR_OK) { + res.ssSz = ss_len; + (void)wh_MessageCrypto_TranslateMlKemDecapsResponse( + magic, &res, + (whMessageCrypto_MlKemDecapsResponse*)cryptoDataOut); + *outSize = sizeof(whMessageCrypto_MlKemDecapsResponse) + ss_len; + } + else { + /* Zero sensitive data on failure */ + wc_ForceZero(res_ss, ss_len); + } + } + + if (keyInited) { + wc_MlKemKey_Free(key); + } +cleanup: + if (evict != 0) { + _CryptoEvictKeyLocked(ctx, key_id); + } + return ret; +#endif /* WOLFSSL_MLKEM_NO_DECAPSULATE */ +} +#endif /* WOLFSSL_HAVE_MLKEM */ + +#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_SLHDSA) +static int _HandlePqcSigAlgorithm(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t cryptoInSize, void* cryptoDataOut, + uint16_t* cryptoOutSize, uint32_t pkAlgoType, + uint32_t pqAlgoType) +{ + int ret = WH_ERROR_NOHANDLER; + + /* Dispatch the appropriate algorithm handler based on the requested PK type + * and the algorithm type. */ + switch (pqAlgoType) { +#ifdef WOLFSSL_HAVE_MLDSA + case WC_PQC_SIG_TYPE_MLDSA: { + switch (pkAlgoType) { + case WC_PK_TYPE_PQC_SIG_KEYGEN: + ret = _HandleMlDsaKeyGen(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_SIGN: + ret = _HandleMlDsaSign(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_VERIFY: + ret = _HandleMlDsaVerify(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY: + ret = _HandleMlDsaCheckPrivKey( + ctx, magic, devId, cryptoDataIn, cryptoInSize, + cryptoDataOut, cryptoOutSize); + break; + default: + ret = WH_ERROR_NOHANDLER; + break; + } + } break; +#endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PQC_SIG_TYPE_SLHDSA: { + switch (pkAlgoType) { + case WC_PK_TYPE_PQC_SIG_KEYGEN: + ret = _HandleSlhDsaKeyGen(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_SIGN: + case WC_PK_TYPE_PQC_SIG_SIGN_MSG: + ret = _HandleSlhDsaSign(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_VERIFY: + case WC_PK_TYPE_PQC_SIG_VERIFY_MSG: + ret = _HandleSlhDsaVerify(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY: + ret = _HandleSlhDsaCheckPrivKey( + ctx, magic, devId, cryptoDataIn, cryptoInSize, + cryptoDataOut, cryptoOutSize); + break; + default: + ret = WH_ERROR_NOHANDLER; + break; + } + } break; +#endif /* WOLFSSL_HAVE_SLHDSA */ + default: + ret = WH_ERROR_NOHANDLER; + break; + } + + return ret; +} +#endif /* WOLFSSL_HAVE_MLDSA || HAVE_FALCON || WOLFSSL_HAVE_SLHDSA */ + +#if defined(WOLFSSL_HAVE_MLKEM) +static int _HandlePqcKemAlgorithm(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t cryptoInSize, void* cryptoDataOut, + uint16_t* cryptoOutSize, uint32_t pkAlgoType, + uint32_t pqAlgoType) +{ + int ret = WH_ERROR_NOHANDLER; + + switch (pqAlgoType) { + case WC_PQC_KEM_TYPE_KYBER: { + switch (pkAlgoType) { + case WC_PK_TYPE_PQC_KEM_KEYGEN: + ret = _HandleMlKemKeyGen(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_KEM_ENCAPS: + ret = _HandleMlKemEncaps(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, cryptoOutSize); break; case WC_PK_TYPE_PQC_KEM_DECAPS: @@ -6179,11 +6899,16 @@ int wh_Server_HandleCryptoRequest(whServerContext* ctx, uint16_t magic, break; #endif /* HAVE_ED25519 */ -#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) +#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_SLHDSA) case WC_PK_TYPE_PQC_SIG_KEYGEN: case WC_PK_TYPE_PQC_SIG_SIGN: case WC_PK_TYPE_PQC_SIG_VERIFY: case WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY: +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PK_TYPE_PQC_SIG_SIGN_MSG: + case WC_PK_TYPE_PQC_SIG_VERIFY_MSG: +#endif /* WOLFSSL_HAVE_SLHDSA */ ret = _HandlePqcSigAlgorithm( ctx, magic, devId, cryptoDataIn, cryptoInSize, cryptoDataOut, &cryptoOutSize, rqstHeader.algoType, @@ -6545,9 +7270,251 @@ static int _HandleSha224Dma(whServerContext* ctx, uint16_t magic, int devId, ret = WH_ERROR_ABORTED; } else { - memcpy(res.hash, sha224->digest, WC_SHA256_DIGEST_SIZE); - res.loLen = sha224->loLen; - res.hiLen = sha224->hiLen; + memcpy(res.hash, sha224->digest, WC_SHA256_DIGEST_SIZE); + res.loLen = sha224->loLen; + res.hiLen = sha224->hiLen; + } + } + } + + (void)wh_MessageCrypto_TranslateSha2DmaResponse( + magic, &res, (whMessageCrypto_Sha2DmaResponse*)cryptoDataOut); + *outSize = sizeof(res); + + return ret; +} +#endif /* WOLFSSL_SHA224 */ + +#ifdef WOLFSSL_SHA384 +static int _HandleSha384Dma(whServerContext* ctx, uint16_t magic, int devId, + uint16_t seq, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) +{ + (void)seq; + int ret = 0; + int preOk = 0; + whMessageCrypto_Sha512DmaRequest req; + whMessageCrypto_Sha2DmaResponse res = {0}; + wc_Sha384 sha384[1]; + const uint8_t* inlineData; + void* inAddr = NULL; + + res.hashType = WC_HASH_TYPE_SHA384; + + if (inSize < sizeof(whMessageCrypto_Sha512DmaRequest)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateSha512DmaRequest( + magic, (const whMessageCrypto_Sha512DmaRequest*)cryptoDataIn, &req); + if (ret != WH_ERROR_OK) { + return ret; + } + + if ((uint32_t)req.inSz > + (uint32_t)(inSize - sizeof(whMessageCrypto_Sha512DmaRequest))) { + return WH_ERROR_BADARGS; + } + /* Non-final: inline and DMA input must be multiples of block size */ + if (!req.isLastBlock && ((req.inSz % WC_SHA384_BLOCK_SIZE) != 0 || + (req.input.sz % WC_SHA384_BLOCK_SIZE) != 0)) { + return WH_ERROR_BADARGS; + } + /* Final: inline data must be less than one block, no DMA input */ + if (req.isLastBlock && + (req.inSz >= WC_SHA384_BLOCK_SIZE || req.input.sz != 0)) { + return WH_ERROR_BADARGS; + } + + inlineData = + (const uint8_t*)cryptoDataIn + sizeof(whMessageCrypto_Sha512DmaRequest); + + ret = wc_InitSha384_ex(sha384, NULL, devId); + if (ret != 0) { + return ret; + } + + /* SHA384 shares SHA512's internal 64-byte digest state */ + memcpy(sha384->digest, req.resumeState.hash, WC_SHA512_DIGEST_SIZE); + sha384->loLen = req.resumeState.loLen; + sha384->hiLen = req.resumeState.hiLen; + sha384->buffLen = 0; + + if (ret == 0 && req.inSz > 0) { + ret = wc_Sha384Update(sha384, inlineData, req.inSz); + } + + if (ret == 0 && req.input.sz > 0) { + ret = wh_Server_DmaProcessClientAddress( + ctx, req.input.addr, &inAddr, req.input.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret == WH_ERROR_OK) { + preOk = 1; + ret = wc_Sha384Update(sha384, inAddr, req.input.sz); + } + if (ret == WH_ERROR_ACCESS) { + res.dmaAddrStatus.badAddr = req.input; + } + } + /* Pair every successful PRE with a POST so DMA callbacks can release any + * resources they acquired, even if the Update failed. */ + if (preOk) { + (void)wh_Server_DmaProcessClientAddress( + ctx, req.input.addr, &inAddr, req.input.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + } + + if (ret == 0) { + if (req.isLastBlock) { + ret = wc_Sha384Final(sha384, res.hash); + } + else { + if (sha384->buffLen != 0) { + ret = WH_ERROR_ABORTED; + } + else { + memcpy(res.hash, sha384->digest, WC_SHA512_DIGEST_SIZE); + res.loLen = sha384->loLen; + res.hiLen = sha384->hiLen; + } + } + } + + (void)wh_MessageCrypto_TranslateSha2DmaResponse( + magic, &res, (whMessageCrypto_Sha2DmaResponse*)cryptoDataOut); + *outSize = sizeof(res); + + return ret; +} +#endif /* WOLFSSL_SHA384 */ + +#ifdef WOLFSSL_SHA512 +static int _HandleSha512Dma(whServerContext* ctx, uint16_t magic, int devId, + uint16_t seq, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) +{ + (void)seq; + int ret = 0; + int preOk = 0; + whMessageCrypto_Sha512DmaRequest req; + whMessageCrypto_Sha2DmaResponse res = {0}; + wc_Sha512 sha512[1]; + const uint8_t* inlineData; + void* inAddr = NULL; + int hashType; + + if (inSize < sizeof(whMessageCrypto_Sha512DmaRequest)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateSha512DmaRequest( + magic, (const whMessageCrypto_Sha512DmaRequest*)cryptoDataIn, &req); + if (ret != WH_ERROR_OK) { + return ret; + } + + if ((uint32_t)req.inSz > + (uint32_t)(inSize - sizeof(whMessageCrypto_Sha512DmaRequest))) { + return WH_ERROR_BADARGS; + } + /* Non-final: inline and DMA input must be multiples of block size */ + if (!req.isLastBlock && ((req.inSz % WC_SHA512_BLOCK_SIZE) != 0 || + (req.input.sz % WC_SHA512_BLOCK_SIZE) != 0)) { + return WH_ERROR_BADARGS; + } + /* Final: inline data must be less than one block, no DMA input */ + if (req.isLastBlock && + (req.inSz >= WC_SHA512_BLOCK_SIZE || req.input.sz != 0)) { + return WH_ERROR_BADARGS; + } + + inlineData = + (const uint8_t*)cryptoDataIn + sizeof(whMessageCrypto_Sha512DmaRequest); + hashType = req.resumeState.hashType; + + /* If the client requested a variant the server does not have compiled in, + * normalize hashType to plain SHA512 so the response reflects what was + * actually executed; the client detects the mismatch against its own + * hashType and returns an error. */ + switch (hashType) { +#ifndef WOLFSSL_NOSHA512_224 + case WC_HASH_TYPE_SHA512_224: + ret = wc_InitSha512_224_ex(sha512, NULL, devId); + break; +#endif +#ifndef WOLFSSL_NOSHA512_256 + case WC_HASH_TYPE_SHA512_256: + ret = wc_InitSha512_256_ex(sha512, NULL, devId); + break; +#endif + default: + ret = wc_InitSha512_ex(sha512, NULL, devId); + hashType = WC_HASH_TYPE_SHA512; + break; + } + if (ret != 0) { + return ret; + } + + res.hashType = hashType; + + memcpy(sha512->digest, req.resumeState.hash, WC_SHA512_DIGEST_SIZE); + sha512->loLen = req.resumeState.loLen; + sha512->hiLen = req.resumeState.hiLen; + sha512->buffLen = 0; + + if (ret == 0 && req.inSz > 0) { + ret = wc_Sha512Update(sha512, inlineData, req.inSz); + } + + if (ret == 0 && req.input.sz > 0) { + ret = wh_Server_DmaProcessClientAddress( + ctx, req.input.addr, &inAddr, req.input.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret == WH_ERROR_OK) { + preOk = 1; + ret = wc_Sha512Update(sha512, inAddr, req.input.sz); + } + if (ret == WH_ERROR_ACCESS) { + res.dmaAddrStatus.badAddr = req.input; + } + } + /* Pair every successful PRE with a POST so DMA callbacks can release any + * resources they acquired, even if the Update failed. */ + if (preOk) { + (void)wh_Server_DmaProcessClientAddress( + ctx, req.input.addr, &inAddr, req.input.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + } + + if (ret == 0) { + if (req.isLastBlock) { + switch (hashType) { +#ifndef WOLFSSL_NOSHA512_224 + case WC_HASH_TYPE_SHA512_224: + ret = wc_Sha512_224Final(sha512, res.hash); + break; +#endif +#ifndef WOLFSSL_NOSHA512_256 + case WC_HASH_TYPE_SHA512_256: + ret = wc_Sha512_256Final(sha512, res.hash); + break; +#endif + default: + ret = wc_Sha512Final(sha512, res.hash); + break; + } + } + else { + if (sha512->buffLen != 0) { + ret = WH_ERROR_ABORTED; + } + else { + memcpy(res.hash, sha512->digest, WC_SHA512_DIGEST_SIZE); + res.loLen = sha512->loLen; + res.hiLen = sha512->hiLen; } } } @@ -6558,66 +7525,64 @@ static int _HandleSha224Dma(whServerContext* ctx, uint16_t magic, int devId, return ret; } -#endif /* WOLFSSL_SHA224 */ +#endif /* WOLFSSL_SHA512 */ -#ifdef WOLFSSL_SHA384 -static int _HandleSha384Dma(whServerContext* ctx, uint16_t magic, int devId, - uint16_t seq, const void* cryptoDataIn, - uint16_t inSize, void* cryptoDataOut, - uint16_t* outSize) +#if defined(WOLFSSL_SHA3) +static int _HandleSha3Dma(whServerContext* ctx, int hashType, uint16_t magic, + int devId, uint16_t seq, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) { (void)seq; - int ret = 0; - int preOk = 0; - whMessageCrypto_Sha512DmaRequest req; - whMessageCrypto_Sha2DmaResponse res = {0}; - wc_Sha384 sha384[1]; - const uint8_t* inlineData; - void* inAddr = NULL; + int ret = 0; + int preOk = 0; + whMessageCrypto_Sha3DmaRequest req; + whMessageCrypto_Sha3DmaResponse res = {0}; + wc_Sha3 sha3[1]; + const uint8_t* inlineData; + void* inAddr = NULL; + _Sha3VariantOps ops; - res.hashType = WC_HASH_TYPE_SHA384; + ret = _Sha3LookupOps(hashType, &ops); + if (ret != 0) { + return ret; + } - if (inSize < sizeof(whMessageCrypto_Sha512DmaRequest)) { + if (inSize < sizeof(whMessageCrypto_Sha3DmaRequest)) { return WH_ERROR_BADARGS; } - ret = wh_MessageCrypto_TranslateSha512DmaRequest( - magic, (const whMessageCrypto_Sha512DmaRequest*)cryptoDataIn, &req); + ret = wh_MessageCrypto_TranslateSha3DmaRequest( + magic, (const whMessageCrypto_Sha3DmaRequest*)cryptoDataIn, &req); if (ret != WH_ERROR_OK) { return ret; } if ((uint32_t)req.inSz > - (uint32_t)(inSize - sizeof(whMessageCrypto_Sha512DmaRequest))) { + (uint32_t)(inSize - sizeof(whMessageCrypto_Sha3DmaRequest))) { return WH_ERROR_BADARGS; } - /* Non-final: inline and DMA input must be multiples of block size */ - if (!req.isLastBlock && ((req.inSz % WC_SHA384_BLOCK_SIZE) != 0 || - (req.input.sz % WC_SHA384_BLOCK_SIZE) != 0)) { + if (!req.isLastBlock && ((req.inSz % ops.blockSize) != 0 || + (req.input.sz % ops.blockSize) != 0)) { return WH_ERROR_BADARGS; } - /* Final: inline data must be less than one block, no DMA input */ - if (req.isLastBlock && - (req.inSz >= WC_SHA384_BLOCK_SIZE || req.input.sz != 0)) { + if (req.isLastBlock && (req.inSz >= ops.blockSize || req.input.sz != 0)) { return WH_ERROR_BADARGS; } inlineData = - (const uint8_t*)cryptoDataIn + sizeof(whMessageCrypto_Sha512DmaRequest); + (const uint8_t*)cryptoDataIn + sizeof(whMessageCrypto_Sha3DmaRequest); - ret = wc_InitSha384_ex(sha384, NULL, devId); + ret = ops.initFn(sha3, NULL, devId); if (ret != 0) { return ret; } - /* SHA384 shares SHA512's internal 64-byte digest state */ - memcpy(sha384->digest, req.resumeState.hash, WC_SHA512_DIGEST_SIZE); - sha384->loLen = req.resumeState.loLen; - sha384->hiLen = req.resumeState.hiLen; - sha384->buffLen = 0; + /* Restore Keccak state from client. initFn already zeroed t[] and i. */ + memcpy(sha3->s, req.resumeState.s, sizeof(sha3->s)); if (ret == 0 && req.inSz > 0) { - ret = wc_Sha384Update(sha384, inlineData, req.inSz); + ret = ops.updateFn(sha3, inlineData, req.inSz); } if (ret == 0 && req.input.sz > 0) { @@ -6626,14 +7591,12 @@ static int _HandleSha384Dma(whServerContext* ctx, uint16_t magic, int devId, WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); if (ret == WH_ERROR_OK) { preOk = 1; - ret = wc_Sha384Update(sha384, inAddr, req.input.sz); + ret = ops.updateFn(sha3, inAddr, req.input.sz); } if (ret == WH_ERROR_ACCESS) { res.dmaAddrStatus.badAddr = req.input; } } - /* Pair every successful PRE with a POST so DMA callbacks can release any - * resources they acquired, even if the Update failed. */ if (preOk) { (void)wh_Server_DmaProcessClientAddress( ctx, req.input.addr, &inAddr, req.input.sz, @@ -6642,272 +7605,483 @@ static int _HandleSha384Dma(whServerContext* ctx, uint16_t magic, int devId, if (ret == 0) { if (req.isLastBlock) { - ret = wc_Sha384Final(sha384, res.hash); + ret = ops.finalFn(sha3, res.hash); } else { - if (sha384->buffLen != 0) { + if (sha3->i != 0) { ret = WH_ERROR_ABORTED; } else { - memcpy(res.hash, sha384->digest, WC_SHA512_DIGEST_SIZE); - res.loLen = sha384->loLen; - res.hiLen = sha384->hiLen; + memcpy(res.resumeState.s, sha3->s, sizeof(res.resumeState.s)); } } } - (void)wh_MessageCrypto_TranslateSha2DmaResponse( - magic, &res, (whMessageCrypto_Sha2DmaResponse*)cryptoDataOut); + (void)wh_MessageCrypto_TranslateSha3DmaResponse( + magic, &res, (whMessageCrypto_Sha3DmaResponse*)cryptoDataOut); *outSize = sizeof(res); return ret; } -#endif /* WOLFSSL_SHA384 */ +#endif /* WOLFSSL_SHA3 */ -#ifdef WOLFSSL_SHA512 -static int _HandleSha512Dma(whServerContext* ctx, uint16_t magic, int devId, - uint16_t seq, const void* cryptoDataIn, - uint16_t inSize, void* cryptoDataOut, - uint16_t* outSize) +#if defined(WOLFSSL_HAVE_MLDSA) + +static int _HandleMlDsaKeyGenDma(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) +{ +#ifdef WOLFSSL_MLDSA_NO_MAKE_KEY + (void)ctx; + (void)magic; + (void)devId; + (void)cryptoDataIn; + (void)inSize; + (void)cryptoDataOut; + (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret = WH_ERROR_OK; + wc_MlDsaKey key[1]; + void* clientOutAddr = NULL; + uint16_t keySize = 0; + + whMessageCrypto_MlDsaKeyGenDmaRequest req; + whMessageCrypto_MlDsaKeyGenDmaResponse res; + + memset(&res, 0, sizeof(res)); + + if (inSize < sizeof(whMessageCrypto_MlDsaKeyGenDmaRequest)) { + return WH_ERROR_BADARGS; + } + + /* Translate the request */ + ret = wh_MessageCrypto_TranslateMlDsaKeyGenDmaRequest( + magic, (whMessageCrypto_MlDsaKeyGenDmaRequest*)cryptoDataIn, &req); + if (ret != WH_ERROR_OK) { + return ret; + } + + /* Check the ML-DSA security level is valid and supported */ + if (0 == _IsMlDsaLevelSupported(req.level)) { + ret = WH_ERROR_BADARGS; + } + else { + /* init mldsa key */ + ret = wc_MlDsaKey_Init(key, NULL, devId); + if (ret == 0) { + /* Set the ML-DSA security level */ + ret = wc_MlDsaKey_SetParams(key, req.level); + if (ret == 0) { + /* generate the key */ + ret = wc_MlDsaKey_MakeKey(key, ctx->crypto->rng); + if (ret == 0) { + /* Check incoming flags */ + if (req.flags & WH_NVM_FLAGS_EPHEMERAL) { + /* Must serialize the key into client memory */ + ret = wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, req.key.sz, + WH_DMA_OPER_CLIENT_WRITE_PRE, + (whServerDmaFlags){0}); + + if (ret == 0) { + ret = wh_Crypto_MlDsaSerializeKeyDer( + key, req.key.sz, clientOutAddr, &keySize); + if (ret == 0) { + res.keyId = WH_KEYID_ERASED; + res.keySize = keySize; + } + } + + if (ret == 0) { + ret = wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, keySize, + WH_DMA_OPER_CLIENT_WRITE_POST, + (whServerDmaFlags){0}); + } + } + else { + /* Must import the key into the cache and return keyid + */ + whKeyId keyId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); + + /* Hold the NVM lock so id allocation and cache import + * are atomic with respect to other server contexts + * under THREADSAFE. */ + ret = WH_SERVER_NVM_LOCK(ctx); + if (ret == WH_ERROR_OK) { + if (WH_KEYID_ISERASED(keyId)) { + /* Generate a new id */ + ret = + wh_Server_KeystoreGetUniqueId(ctx, &keyId); + WH_DEBUG_SERVER("UniqueId: keyId:%u, ret:%d\n", + keyId, ret); + } + if (ret == WH_ERROR_OK) { + ret = wh_Server_MlDsaKeyCacheImport( + ctx, key, keyId, req.flags, req.labelSize, + req.label); + WH_DEBUG_SERVER( + "CacheImport: keyId:%u, ret:%d\n", keyId, + ret); + } + (void)WH_SERVER_NVM_UNLOCK(ctx); + } /* WH_SERVER_NVM_LOCK() */ +#ifdef WOLFSSL_MLDSA_PUBLIC_KEY + /* Stream the public key back through the client's DMA + * buffer so it gets the pubkey without a separate + * ExportPublicKey call. A freshly generated key must + * serialize, so treat a failure as fatal: evict the + * just-committed key and propagate the error rather + * than returning a keyId with no public key. */ + if (ret == 0) { + int rc = wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, req.key.sz, + WH_DMA_OPER_CLIENT_WRITE_PRE, + (whServerDmaFlags){0}); + if (rc == 0) { + int pub_ret = wc_MlDsaKey_PublicKeyToDer( + key, (byte*)clientOutAddr, + (word32)req.key.sz, 1); + if (pub_ret > 0) { + keySize = (uint16_t)pub_ret; + } + else { + ret = (pub_ret < 0) ? pub_ret + : WH_ERROR_ABORTED; + } + (void)wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, keySize, + WH_DMA_OPER_CLIENT_WRITE_POST, + (whServerDmaFlags){0}); + } + else { + ret = rc; + } + if (ret != 0) { + _CryptoEvictKeyLocked(ctx, keyId); + } + } +#endif /* WOLFSSL_MLDSA_PUBLIC_KEY */ + if (ret == 0) { + res.keyId = wh_KeyId_TranslateToClient(keyId); + res.keySize = keySize; + } + } + } + } + wc_MlDsaKey_Free(key); + } + } + + if (ret == WH_ERROR_ACCESS) { + res.dmaAddrStatus.badAddr = req.key; + } + + /* Translate the response */ + (void)wh_MessageCrypto_TranslateMlDsaKeyGenDmaResponse( + magic, &res, (whMessageCrypto_MlDsaKeyGenDmaResponse*)cryptoDataOut); + + *outSize = sizeof(res); + + return ret; +#endif /* WOLFSSL_MLDSA_NO_MAKE_KEY */ +} + +static int _HandleMlDsaSignDma(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) { - (void)seq; - int ret = 0; - int preOk = 0; - whMessageCrypto_Sha512DmaRequest req; - whMessageCrypto_Sha2DmaResponse res = {0}; - wc_Sha512 sha512[1]; - const uint8_t* inlineData; - void* inAddr = NULL; - int hashType; +#ifdef WOLFSSL_MLDSA_NO_SIGN + (void)ctx; + (void)magic; + (void)devId; + (void)cryptoDataIn; + (void)inSize; + (void)cryptoDataOut; + (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret = 0; + wc_MlDsaKey key[1]; + void* msgAddr = NULL; + void* sigAddr = NULL; + word32 sigLen = 0; - if (inSize < sizeof(whMessageCrypto_Sha512DmaRequest)) { + whMessageCrypto_MlDsaSignDmaRequest req; + whMessageCrypto_MlDsaSignDmaResponse res; + + if (inSize < sizeof(whMessageCrypto_MlDsaSignDmaRequest)) { return WH_ERROR_BADARGS; } - ret = wh_MessageCrypto_TranslateSha512DmaRequest( - magic, (const whMessageCrypto_Sha512DmaRequest*)cryptoDataIn, &req); + /* Translate the request */ + ret = wh_MessageCrypto_TranslateMlDsaSignDmaRequest( + magic, (whMessageCrypto_MlDsaSignDmaRequest*)cryptoDataIn, &req); if (ret != WH_ERROR_OK) { return ret; } - if ((uint32_t)req.inSz > - (uint32_t)(inSize - sizeof(whMessageCrypto_Sha512DmaRequest))) { - return WH_ERROR_BADARGS; - } - /* Non-final: inline and DMA input must be multiples of block size */ - if (!req.isLastBlock && ((req.inSz % WC_SHA512_BLOCK_SIZE) != 0 || - (req.input.sz % WC_SHA512_BLOCK_SIZE) != 0)) { - return WH_ERROR_BADARGS; - } - /* Final: inline data must be less than one block, no DMA input */ - if (req.isLastBlock && - (req.inSz >= WC_SHA512_BLOCK_SIZE || req.input.sz != 0)) { - return WH_ERROR_BADARGS; - } + /* Transaction state */ + whKeyId key_id; + int evict = 0; - inlineData = - (const uint8_t*)cryptoDataIn + sizeof(whMessageCrypto_Sha512DmaRequest); - hashType = req.resumeState.hashType; - /* If the client requested a variant the server does not have compiled in, - * normalize hashType to plain SHA512 so the response reflects what was - * actually executed; the client detects the mismatch against its own - * hashType and returns an error. */ - switch (hashType) { -#ifndef WOLFSSL_NOSHA512_224 - case WC_HASH_TYPE_SHA512_224: - ret = wc_InitSha512_224_ex(sha512, NULL, devId); - break; -#endif -#ifndef WOLFSSL_NOSHA512_256 - case WC_HASH_TYPE_SHA512_256: - ret = wc_InitSha512_256_ex(sha512, NULL, devId); - break; -#endif - default: - ret = wc_InitSha512_ex(sha512, NULL, devId); - hashType = WC_HASH_TYPE_SHA512; - break; + /* Get key ID and evict flag */ + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + evict = !!(req.options & WH_MESSAGE_CRYPTO_MLDSA_SIGN_OPTIONS_EVICT); + + /* Extract context from inline data after the struct */ + uint32_t contextSz = req.contextSz; + uint32_t preHashType = req.preHashType; + byte* req_context = NULL; + if (contextSz > WH_CRYPTO_MLDSA_MAX_CTX_LEN) { + return WH_ERROR_BADARGS; } - if (ret != 0) { - return ret; + if (contextSz > 0) { + if (inSize < sizeof(whMessageCrypto_MlDsaSignDmaRequest) + contextSz) { + return WH_ERROR_BADARGS; + } + req_context = (uint8_t*)(cryptoDataIn) + + sizeof(whMessageCrypto_MlDsaSignDmaRequest); } - res.hashType = hashType; + /* Initialize key */ + ret = wc_MlDsaKey_Init(key, NULL, devId); + if (ret == 0) { + /* Export key from cache */ + /* TODO: sanity check security level against key pulled from cache? */ + /* Export the key, enforcing the sign usage policy against the same + * locked snapshot that is exported. The non-DMA sign handler enforces + * the same policy. */ + ret = _MlDsaKeyCacheExportEnforce(ctx, key_id, WH_NVM_FLAGS_USAGE_SIGN, + key); + if (ret == 0) { + /* Process client message buffer address */ + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); - memcpy(sha512->digest, req.resumeState.hash, WC_SHA512_DIGEST_SIZE); - sha512->loLen = req.resumeState.loLen; - sha512->hiLen = req.resumeState.hiLen; - sha512->buffLen = 0; + if (ret == 0) { + /* Process client signature buffer address */ + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0}); - if (ret == 0 && req.inSz > 0) { - ret = wc_Sha512Update(sha512, inlineData, req.inSz); - } + if (ret == 0) { + /* Sign the message using appropriate FIPS 204 API */ + sigLen = req.sig.sz; + if (preHashType != WC_HASH_TYPE_NONE) { + ret = wc_MlDsaKey_SignCtxHash( + key, req_context, (byte)contextSz, sigAddr, &sigLen, + msgAddr, req.msg.sz, preHashType, ctx->crypto->rng); + } + else { + ret = wc_MlDsaKey_SignCtx( + key, req_context, (byte)contextSz, sigAddr, &sigLen, + msgAddr, req.msg.sz, ctx->crypto->rng); + } + } - if (ret == 0 && req.input.sz > 0) { - ret = wh_Server_DmaProcessClientAddress( - ctx, req.input.addr, &inAddr, req.input.sz, - WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); - if (ret == WH_ERROR_OK) { - preOk = 1; - ret = wc_Sha512Update(sha512, inAddr, req.input.sz); - } - if (ret == WH_ERROR_ACCESS) { - res.dmaAddrStatus.badAddr = req.input; + if (sigAddr != NULL) { + /* Post-write processing of signature buffer */ + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.sig.addr, &sigAddr, sigLen, + WH_DMA_OPER_CLIENT_WRITE_POST, (whServerDmaFlags){0}); + } + if (msgAddr != NULL) { + /* Post-read processing of message buffer */ + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + } + } } + wc_MlDsaKey_Free(key); } - /* Pair every successful PRE with a POST so DMA callbacks can release any - * resources they acquired, even if the Update failed. */ - if (preOk) { - (void)wh_Server_DmaProcessClientAddress( - ctx, req.input.addr, &inAddr, req.input.sz, - WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + + /* Evict key if requested */ + if (evict) { + /* User requested to evict from cache, even if the call failed */ + _CryptoEvictKeyLocked(ctx, key_id); } if (ret == 0) { - if (req.isLastBlock) { - switch (hashType) { -#ifndef WOLFSSL_NOSHA512_224 - case WC_HASH_TYPE_SHA512_224: - ret = wc_Sha512_224Final(sha512, res.hash); - break; -#endif -#ifndef WOLFSSL_NOSHA512_256 - case WC_HASH_TYPE_SHA512_256: - ret = wc_Sha512_256Final(sha512, res.hash); - break; -#endif - default: - ret = wc_Sha512Final(sha512, res.hash); - break; - } - } - else { - if (sha512->buffLen != 0) { - ret = WH_ERROR_ABORTED; - } - else { - memcpy(res.hash, sha512->digest, WC_SHA512_DIGEST_SIZE); - res.loLen = sha512->loLen; - res.hiLen = sha512->hiLen; - } - } - } + /* Set response signature length */ + res.sigLen = sigLen; + *outSize = sizeof(res); - (void)wh_MessageCrypto_TranslateSha2DmaResponse( - magic, &res, (whMessageCrypto_Sha2DmaResponse*)cryptoDataOut); - *outSize = sizeof(res); + /* Translate the response */ + (void)wh_MessageCrypto_TranslateMlDsaSignDmaResponse( + magic, &res, (whMessageCrypto_MlDsaSignDmaResponse*)cryptoDataOut); + } return ret; +#endif /* WOLFSSL_MLDSA_NO_SIGN */ } -#endif /* WOLFSSL_SHA512 */ -#if defined(WOLFSSL_SHA3) -static int _HandleSha3Dma(whServerContext* ctx, int hashType, uint16_t magic, - int devId, uint16_t seq, const void* cryptoDataIn, - uint16_t inSize, void* cryptoDataOut, - uint16_t* outSize) +static int _HandleMlDsaVerifyDma(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) { - (void)seq; - int ret = 0; - int preOk = 0; - whMessageCrypto_Sha3DmaRequest req; - whMessageCrypto_Sha3DmaResponse res = {0}; - wc_Sha3 sha3[1]; - const uint8_t* inlineData; - void* inAddr = NULL; - _Sha3VariantOps ops; +#ifdef WOLFSSL_MLDSA_NO_VERIFY + (void)ctx; + (void)magic; + (void)devId; + (void)cryptoDataIn; + (void)inSize; + (void)cryptoDataOut; + (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret = 0; + wc_MlDsaKey key[1]; + void* msgAddr = NULL; + void* sigAddr = NULL; + int verified = 0; - ret = _Sha3LookupOps(hashType, &ops); - if (ret != 0) { - return ret; - } + whMessageCrypto_MlDsaVerifyDmaRequest req; + whMessageCrypto_MlDsaVerifyDmaResponse res; - if (inSize < sizeof(whMessageCrypto_Sha3DmaRequest)) { + if (inSize < sizeof(whMessageCrypto_MlDsaVerifyDmaRequest)) { return WH_ERROR_BADARGS; } - ret = wh_MessageCrypto_TranslateSha3DmaRequest( - magic, (const whMessageCrypto_Sha3DmaRequest*)cryptoDataIn, &req); + /* Translate the request */ + ret = wh_MessageCrypto_TranslateMlDsaVerifyDmaRequest( + magic, (whMessageCrypto_MlDsaVerifyDmaRequest*)cryptoDataIn, &req); if (ret != WH_ERROR_OK) { return ret; } - if ((uint32_t)req.inSz > - (uint32_t)(inSize - sizeof(whMessageCrypto_Sha3DmaRequest))) { - return WH_ERROR_BADARGS; - } - if (!req.isLastBlock && ((req.inSz % ops.blockSize) != 0 || - (req.input.sz % ops.blockSize) != 0)) { + /* Transaction state */ + whKeyId key_id; + int evict = 0; + + /* Get key ID and evict flag */ + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + evict = !!(req.options & WH_MESSAGE_CRYPTO_MLDSA_VERIFY_OPTIONS_EVICT); + + /* Extract context from inline data after the struct */ + uint32_t contextSz = req.contextSz; + uint32_t preHashType = req.preHashType; + byte* req_context = NULL; + if (contextSz > WH_CRYPTO_MLDSA_MAX_CTX_LEN) { return WH_ERROR_BADARGS; } - if (req.isLastBlock && (req.inSz >= ops.blockSize || req.input.sz != 0)) { - return WH_ERROR_BADARGS; + if (contextSz > 0) { + if (inSize < + sizeof(whMessageCrypto_MlDsaVerifyDmaRequest) + contextSz) { + return WH_ERROR_BADARGS; + } + req_context = (uint8_t*)(cryptoDataIn) + + sizeof(whMessageCrypto_MlDsaVerifyDmaRequest); } - inlineData = - (const uint8_t*)cryptoDataIn + sizeof(whMessageCrypto_Sha3DmaRequest); - - ret = ops.initFn(sha3, NULL, devId); + /* Initialize key */ + ret = wc_MlDsaKey_Init(key, NULL, devId); if (ret != 0) { return ret; } - /* Restore Keccak state from client. initFn already zeroed t[] and i. */ - memcpy(sha3->s, req.resumeState.s, sizeof(sha3->s)); - - if (ret == 0 && req.inSz > 0) { - ret = ops.updateFn(sha3, inlineData, req.inSz); - } - - if (ret == 0 && req.input.sz > 0) { + /* Export the key, enforcing the verify usage policy against the same + * locked snapshot that is exported. The non-DMA verify handler enforces + * the same policy. */ + ret = _MlDsaKeyCacheExportEnforce(ctx, key_id, WH_NVM_FLAGS_USAGE_VERIFY, + key); + if (ret == 0) { + /* Process client signature buffer address */ ret = wh_Server_DmaProcessClientAddress( - ctx, req.input.addr, &inAddr, req.input.sz, + ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); - if (ret == WH_ERROR_OK) { - preOk = 1; - ret = ops.updateFn(sha3, inAddr, req.input.sz); - } - if (ret == WH_ERROR_ACCESS) { - res.dmaAddrStatus.badAddr = req.input; - } - } - if (preOk) { - (void)wh_Server_DmaProcessClientAddress( - ctx, req.input.addr, &inAddr, req.input.sz, - WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); - } - if (ret == 0) { - if (req.isLastBlock) { - ret = ops.finalFn(sha3, res.hash); - } - else { - if (sha3->i != 0) { - ret = WH_ERROR_ABORTED; + if (ret == 0) { + /* Process client message buffer address */ + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + + if (ret == 0) { + /* Verify the signature using appropriate FIPS 204 API */ + if (preHashType != WC_HASH_TYPE_NONE) { + ret = wc_MlDsaKey_VerifyCtxHash( + key, sigAddr, req.sig.sz, req_context, (byte)contextSz, + msgAddr, req.msg.sz, preHashType, &verified); + } + else { + ret = wc_MlDsaKey_VerifyCtx(key, sigAddr, req.sig.sz, + req_context, (byte)contextSz, + msgAddr, req.msg.sz, &verified); + } } - else { - memcpy(res.resumeState.s, sha3->s, sizeof(res.resumeState.s)); + + if (sigAddr != NULL) { + /* Post-read processing of signature buffer */ + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + } + + if (msgAddr != NULL) { + /* Post-read processing of message buffer */ + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); } } } - (void)wh_MessageCrypto_TranslateSha3DmaResponse( - magic, &res, (whMessageCrypto_Sha3DmaResponse*)cryptoDataOut); - *outSize = sizeof(res); + /* Evict key if requested */ + if (evict) { + /* User requested to evict from cache, even if the call failed */ + _CryptoEvictKeyLocked(ctx, key_id); + } + + if (ret == 0) { + /* Set verification result */ + res.verifyResult = verified; + + /* Translate the response */ + (void)wh_MessageCrypto_TranslateMlDsaVerifyDmaResponse( + magic, &res, + (whMessageCrypto_MlDsaVerifyDmaResponse*)cryptoDataOut); + + *outSize = sizeof(res); + } + wc_MlDsaKey_Free(key); return ret; +#endif /* WOLFSSL_MLDSA_NO_VERIFY */ } -#endif /* WOLFSSL_SHA3 */ -#if defined(WOLFSSL_HAVE_MLDSA) +static int _HandleMlDsaCheckPrivKeyDma(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) +{ + (void)ctx; + (void)magic; + (void)devId; + (void)cryptoDataIn; + (void)inSize; + (void)cryptoDataOut; + (void)outSize; + return WH_ERROR_NOHANDLER; +} +#endif /* WOLFSSL_HAVE_MLDSA */ -static int _HandleMlDsaKeyGenDma(whServerContext* ctx, uint16_t magic, - int devId, const void* cryptoDataIn, - uint16_t inSize, void* cryptoDataOut, - uint16_t* outSize) +#ifdef WOLFSSL_HAVE_SLHDSA +static int _HandleSlhDsaKeyGenDma(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) { -#ifdef WOLFSSL_MLDSA_NO_MAKE_KEY +#ifdef WOLFSSL_SLHDSA_VERIFY_ONLY (void)ctx; (void)magic; (void)devId; @@ -6917,159 +8091,165 @@ static int _HandleMlDsaKeyGenDma(whServerContext* ctx, uint16_t magic, (void)outSize; return WH_ERROR_NOHANDLER; #else - int ret = WH_ERROR_OK; - wc_MlDsaKey key[1]; - void* clientOutAddr = NULL; - uint16_t keySize = 0; + int ret = WH_ERROR_OK; + SlhDsaKey key[1]; + void* clientOutAddr = NULL; + void* clientSeedAddr = NULL; + uint16_t keySize = 0; + /* Track which buffer failed. 0 is key, non-0 is seed */ + int seedAccessFail = 0; - whMessageCrypto_MlDsaKeyGenDmaRequest req; - whMessageCrypto_MlDsaKeyGenDmaResponse res; + whMessageCrypto_SlhDsaKeyGenDmaRequest req; + whMessageCrypto_SlhDsaKeyGenDmaResponse res; memset(&res, 0, sizeof(res)); - if (inSize < sizeof(whMessageCrypto_MlDsaKeyGenDmaRequest)) { + if (inSize < sizeof(whMessageCrypto_SlhDsaKeyGenDmaRequest)) { return WH_ERROR_BADARGS; } - /* Translate the request */ - ret = wh_MessageCrypto_TranslateMlDsaKeyGenDmaRequest( - magic, (whMessageCrypto_MlDsaKeyGenDmaRequest*)cryptoDataIn, &req); + ret = wh_MessageCrypto_TranslateSlhDsaKeyGenDmaRequest( + magic, (whMessageCrypto_SlhDsaKeyGenDmaRequest*)cryptoDataIn, &req); if (ret != WH_ERROR_OK) { return ret; } - /* Check the ML-DSA security level is valid and supported */ - if (0 == _IsMlDsaLevelSupported(req.level)) { - ret = WH_ERROR_BADARGS; + if (0 == _IsSlhDsaParamSupported((int)req.param)) { + return WH_ERROR_BADARGS; } - else { - /* init mldsa key */ - ret = wc_MlDsaKey_Init(key, NULL, devId); - if (ret == 0) { - /* Set the ML-DSA security level */ - ret = wc_MlDsaKey_SetParams(key, req.level); - if (ret == 0) { - /* generate the key */ - ret = wc_MlDsaKey_MakeKey(key, ctx->crypto->rng); - if (ret == 0) { - /* Check incoming flags */ - if (req.flags & WH_NVM_FLAGS_EPHEMERAL) { - /* Must serialize the key into client memory */ - ret = wh_Server_DmaProcessClientAddress( - ctx, req.key.addr, &clientOutAddr, req.key.sz, - WH_DMA_OPER_CLIENT_WRITE_PRE, - (whServerDmaFlags){0}); - if (ret == 0) { - ret = wh_Crypto_MlDsaSerializeKeyDer( - key, req.key.sz, clientOutAddr, &keySize); - if (ret == 0) { - res.keyId = WH_KEYID_ERASED; - res.keySize = keySize; - } - } + ret = wc_SlhDsaKey_Init(key, (enum SlhDsaParam)req.param, NULL, devId); + if (ret == 0) { + if (req.seed.sz > 0) { + /* The seed is the contiguous SK.seed || SK.prf || PK.seed */ + ret = wh_Server_DmaProcessClientAddress( + ctx, req.seed.addr, &clientSeedAddr, req.seed.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret == WH_ERROR_ACCESS) { + seedAccessFail = 1; + } + if (ret == 0) { + word32 n = (word32)(req.seed.sz / 3); + if (((req.seed.sz % 3) != 0) || (n != key->params->n)) { + ret = WH_ERROR_BADARGS; + } + else { + const byte* seed = (const byte*)clientSeedAddr; + ret = wc_SlhDsaKey_MakeKeyWithRandom(key, seed, n, seed + n, + n, seed + 2 * n, n); + } + (void)wh_Server_DmaProcessClientAddress( + ctx, req.seed.addr, &clientSeedAddr, req.seed.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + } + } + else { + ret = wc_SlhDsaKey_MakeKey(key, ctx->crypto->rng); + } - if (ret == 0) { - ret = wh_Server_DmaProcessClientAddress( - ctx, req.key.addr, &clientOutAddr, keySize, - WH_DMA_OPER_CLIENT_WRITE_POST, - (whServerDmaFlags){0}); - } - } - else { - /* Must import the key into the cache and return keyid - */ - whKeyId keyId = wh_KeyId_TranslateFromClient( - WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); + if (ret == 0) { + if (req.flags & WH_NVM_FLAGS_EPHEMERAL) { + /* Must serialize the key into client memory */ + ret = wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, req.key.sz, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0}); - /* Hold the NVM lock so id allocation and cache import - * are atomic with respect to other server contexts - * under THREADSAFE. */ - ret = WH_SERVER_NVM_LOCK(ctx); - if (ret == WH_ERROR_OK) { - if (WH_KEYID_ISERASED(keyId)) { - /* Generate a new id */ - ret = - wh_Server_KeystoreGetUniqueId(ctx, &keyId); - WH_DEBUG_SERVER("UniqueId: keyId:%u, ret:%d\n", - keyId, ret); - } - if (ret == WH_ERROR_OK) { - ret = wh_Server_MlDsaKeyCacheImport( - ctx, key, keyId, req.flags, req.labelSize, - req.label); - WH_DEBUG_SERVER( - "CacheImport: keyId:%u, ret:%d\n", keyId, - ret); - } - (void)WH_SERVER_NVM_UNLOCK(ctx); - } /* WH_SERVER_NVM_LOCK() */ -#ifdef WOLFSSL_MLDSA_PUBLIC_KEY - /* Stream the public key back through the client's DMA - * buffer so it gets the pubkey without a separate - * ExportPublicKey call. A freshly generated key must - * serialize, so treat a failure as fatal: evict the - * just-committed key and propagate the error rather - * than returning a keyId with no public key. */ - if (ret == 0) { - int rc = wh_Server_DmaProcessClientAddress( - ctx, req.key.addr, &clientOutAddr, req.key.sz, - WH_DMA_OPER_CLIENT_WRITE_PRE, - (whServerDmaFlags){0}); - if (rc == 0) { - int pub_ret = wc_MlDsaKey_PublicKeyToDer( - key, (byte*)clientOutAddr, - (word32)req.key.sz, 1); - if (pub_ret > 0) { - keySize = (uint16_t)pub_ret; - } - else { - ret = (pub_ret < 0) ? pub_ret - : WH_ERROR_ABORTED; - } - (void)wh_Server_DmaProcessClientAddress( - ctx, req.key.addr, &clientOutAddr, keySize, - WH_DMA_OPER_CLIENT_WRITE_POST, - (whServerDmaFlags){0}); - } - else { - ret = rc; - } - if (ret != 0) { - _CryptoEvictKeyLocked(ctx, keyId); - } + if (ret == 0) { + int postRet; + + ret = wh_Crypto_SlhDsaSerializeKeyDer( + key, (uint16_t)req.key.sz, clientOutAddr, &keySize); + if (ret == 0) { + res.keyId = WH_KEYID_ERASED; + res.keySize = keySize; + } + + /* Release the range PRE mapped, on failure too */ + postRet = wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, req.key.sz, + WH_DMA_OPER_CLIENT_WRITE_POST, (whServerDmaFlags){0}); + if (ret == 0) { + ret = postRet; + } + } + } + else { + /* Must import the key into the cache and return keyid */ + whKeyId keyId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); + + /* Ensure alloc and import are atomic */ + ret = WH_SERVER_NVM_LOCK(ctx); + if (ret == WH_ERROR_OK) { + if (WH_KEYID_ISERASED(keyId)) { + ret = wh_Server_KeystoreGetUniqueId(ctx, &keyId); + } + if (ret == WH_ERROR_OK) { + ret = wh_Server_SlhDsaKeyCacheImport( + ctx, key, keyId, req.flags, req.labelSize, + req.label); + } + (void)WH_SERVER_NVM_UNLOCK(ctx); + } /* WH_SERVER_NVM_LOCK() */ + + /* Return the public key through the client's DMA buffer */ + if (ret == 0) { + int postRet; + int rc = wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, req.key.sz, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0}); + if (rc == 0) { + int pub_ret = wc_SlhDsaKey_PublicKeyToDer( + key, (byte*)clientOutAddr, (word32)req.key.sz, 1); + if (pub_ret > 0) { + keySize = (uint16_t)pub_ret; } -#endif /* WOLFSSL_MLDSA_PUBLIC_KEY */ + else { + ret = (pub_ret < 0) ? pub_ret : WH_ERROR_ABORTED; + } + /* Release the whole range PRE mapped */ + postRet = wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, req.key.sz, + WH_DMA_OPER_CLIENT_WRITE_POST, + (whServerDmaFlags){0}); if (ret == 0) { - res.keyId = wh_KeyId_TranslateToClient(keyId); - res.keySize = keySize; + ret = postRet; } } + else { + ret = rc; + } + if (ret != 0) { + _CryptoEvictKeyLocked(ctx, keyId); + } + } + if (ret == 0) { + res.keyId = wh_KeyId_TranslateToClient(keyId); + res.keySize = keySize; } } - wc_MlDsaKey_Free(key); } + wc_SlhDsaKey_Free(key); } if (ret == WH_ERROR_ACCESS) { - res.dmaAddrStatus.badAddr = req.key; + res.dmaAddrStatus.badAddr = seedAccessFail ? req.seed : req.key; } - /* Translate the response */ - (void)wh_MessageCrypto_TranslateMlDsaKeyGenDmaResponse( - magic, &res, (whMessageCrypto_MlDsaKeyGenDmaResponse*)cryptoDataOut); + (void)wh_MessageCrypto_TranslateSlhDsaKeyGenDmaResponse( + magic, &res, (whMessageCrypto_SlhDsaKeyGenDmaResponse*)cryptoDataOut); *outSize = sizeof(res); return ret; -#endif /* WOLFSSL_MLDSA_NO_MAKE_KEY */ +#endif /* WOLFSSL_SLHDSA_VERIFY_ONLY */ } -static int _HandleMlDsaSignDma(whServerContext* ctx, uint16_t magic, int devId, - const void* cryptoDataIn, uint16_t inSize, - void* cryptoDataOut, uint16_t* outSize) +static int _HandleSlhDsaSignDma(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) { -#ifdef WOLFSSL_MLDSA_NO_SIGN +#ifdef WOLFSSL_SLHDSA_VERIFY_ONLY (void)ctx; (void)magic; (void)devId; @@ -7079,282 +8259,250 @@ static int _HandleMlDsaSignDma(whServerContext* ctx, uint16_t magic, int devId, (void)outSize; return WH_ERROR_NOHANDLER; #else - int ret = 0; - wc_MlDsaKey key[1]; - void* msgAddr = NULL; - void* sigAddr = NULL; - word32 sigLen = 0; + int ret = 0; + SlhDsaKey key[1]; + void* msgAddr = NULL; + void* sigAddr = NULL; + int msgPre = 0; + int sigPre = 0; + word32 sigLen = 0; + whKeyId key_id; + int evict; + uint32_t contextSz; + uint32_t preHashType; + uint32_t addRndSz; + byte* req_context = NULL; + byte* req_addRnd = NULL; + uint32_t inline_len; + + whMessageCrypto_SlhDsaSignDmaRequest req; + whMessageCrypto_SlhDsaSignDmaResponse res; - whMessageCrypto_MlDsaSignDmaRequest req; - whMessageCrypto_MlDsaSignDmaResponse res; + memset(&res, 0, sizeof(res)); - if (inSize < sizeof(whMessageCrypto_MlDsaSignDmaRequest)) { + if (inSize < sizeof(whMessageCrypto_SlhDsaSignDmaRequest)) { return WH_ERROR_BADARGS; } - /* Translate the request */ - ret = wh_MessageCrypto_TranslateMlDsaSignDmaRequest( - magic, (whMessageCrypto_MlDsaSignDmaRequest*)cryptoDataIn, &req); + ret = wh_MessageCrypto_TranslateSlhDsaSignDmaRequest( + magic, (whMessageCrypto_SlhDsaSignDmaRequest*)cryptoDataIn, &req); if (ret != WH_ERROR_OK) { return ret; } - /* Transaction state */ - whKeyId key_id; - int evict = 0; - - - /* Get key ID and evict flag */ key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); - evict = !!(req.options & WH_MESSAGE_CRYPTO_MLDSA_SIGN_OPTIONS_EVICT); + evict = !!(req.options & WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_EVICT); - /* Extract context from inline data after the struct */ - uint32_t contextSz = req.contextSz; - uint32_t preHashType = req.preHashType; - byte* req_context = NULL; - if (contextSz > WH_CRYPTO_MLDSA_MAX_CTX_LEN) { + contextSz = req.contextSz; + preHashType = req.preHashType; + addRndSz = req.addRndSz; + if (contextSz > WH_CRYPTO_SLHDSA_MAX_CTX_LEN) { + return WH_ERROR_BADARGS; + } + inline_len = inSize - sizeof(whMessageCrypto_SlhDsaSignDmaRequest); + if ((contextSz > inline_len) || (addRndSz > (inline_len - contextSz))) { return WH_ERROR_BADARGS; } if (contextSz > 0) { - if (inSize < sizeof(whMessageCrypto_MlDsaSignDmaRequest) + contextSz) { - return WH_ERROR_BADARGS; - } req_context = (uint8_t*)(cryptoDataIn) + - sizeof(whMessageCrypto_MlDsaSignDmaRequest); + sizeof(whMessageCrypto_SlhDsaSignDmaRequest); + } + if (addRndSz > 0) { + req_addRnd = (uint8_t*)(cryptoDataIn) + + sizeof(whMessageCrypto_SlhDsaSignDmaRequest) + contextSz; } - /* Initialize key */ - ret = wc_MlDsaKey_Init(key, NULL, devId); + ret = _SlhDsaInitForCachedKey(key, req.param, devId); if (ret == 0) { - /* Export key from cache */ - /* TODO: sanity check security level against key pulled from cache? */ - /* Export the key, enforcing the sign usage policy against the same - * locked snapshot that is exported. The non-DMA sign handler enforces - * the same policy. */ - ret = _MlDsaKeyCacheExportEnforce(ctx, key_id, WH_NVM_FLAGS_USAGE_SIGN, - key); + ret = _SlhDsaLoadKey(ctx, key_id, WH_NVM_FLAGS_USAGE_SIGN, key); if (ret == 0) { - /* Process client message buffer address */ ret = wh_Server_DmaProcessClientAddress( ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret == 0) { + msgPre = 1; + } + else if (ret == WH_ERROR_ACCESS) { + res.dmaAddrStatus.badAddr = req.msg; + } if (ret == 0) { - /* Process client signature buffer address */ ret = wh_Server_DmaProcessClientAddress( ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0}); + if (ret == 0) { + sigPre = 1; + /* Clear so a failed sign does not copy back stale data */ + memset(sigAddr, 0, (size_t)req.sig.sz); + } + else if (ret == WH_ERROR_ACCESS) { + res.dmaAddrStatus.badAddr = req.sig; + } if (ret == 0) { - /* Sign the message using appropriate FIPS 204 API */ - sigLen = req.sig.sz; - if (preHashType != WC_HASH_TYPE_NONE) { - ret = wc_MlDsaKey_SignCtxHash( - key, req_context, (byte)contextSz, - sigAddr, &sigLen, msgAddr, req.msg.sz, - preHashType, ctx->crypto->rng); - } - else { - ret = wc_MlDsaKey_SignCtx( - key, req_context, (byte)contextSz, - sigAddr, &sigLen, msgAddr, req.msg.sz, - ctx->crypto->rng); - } + sigLen = (word32)req.sig.sz; + ret = _SlhDsaSignDispatch( + ctx, key, req.options, (const byte*)msgAddr, + (word32)req.msg.sz, req_context, contextSz, preHashType, + req_addRnd, addRndSz, (byte*)sigAddr, &sigLen); } - if (sigAddr != NULL) { - /* Post-write processing of signature buffer */ - (void)wh_Server_DmaProcessClientAddress( - ctx, (uintptr_t)req.sig.addr, &sigAddr, sigLen, - WH_DMA_OPER_CLIENT_WRITE_POST, - (whServerDmaFlags){0}); + if (sigPre) { + /* Release the full range PRE mapped, not sigLen */ + int postRet = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, + WH_DMA_OPER_CLIENT_WRITE_POST, (whServerDmaFlags){0}); + if (ret == 0) { + ret = postRet; + } } - if (msgAddr != NULL) { - /* Post-read processing of message buffer */ - (void)wh_Server_DmaProcessClientAddress( - ctx, (uintptr_t)req.msg.addr, &msgAddr, - req.msg.sz, WH_DMA_OPER_CLIENT_READ_POST, - (whServerDmaFlags){0}); + if (msgPre) { + int postRet = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + if (ret == 0) { + ret = postRet; + } } } } - wc_MlDsaKey_Free(key); + wc_SlhDsaKey_Free(key); } - /* Evict key if requested */ if (evict) { /* User requested to evict from cache, even if the call failed */ _CryptoEvictKeyLocked(ctx, key_id); } if (ret == 0) { - /* Set response signature length */ res.sigLen = sigLen; - *outSize = sizeof(res); - - /* Translate the response */ - (void)wh_MessageCrypto_TranslateMlDsaSignDmaResponse( - magic, &res, (whMessageCrypto_MlDsaSignDmaResponse*)cryptoDataOut); } + (void)wh_MessageCrypto_TranslateSlhDsaSignDmaResponse( + magic, &res, (whMessageCrypto_SlhDsaSignDmaResponse*)cryptoDataOut); + + *outSize = sizeof(res); + return ret; -#endif /* WOLFSSL_MLDSA_NO_SIGN */ +#endif /* WOLFSSL_SLHDSA_VERIFY_ONLY */ } -static int _HandleMlDsaVerifyDma(whServerContext* ctx, uint16_t magic, - int devId, const void* cryptoDataIn, - uint16_t inSize, void* cryptoDataOut, - uint16_t* outSize) +static int _HandleSlhDsaVerifyDma(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) { -#ifdef WOLFSSL_MLDSA_NO_VERIFY - (void)ctx; - (void)magic; - (void)devId; - (void)cryptoDataIn; - (void)inSize; - (void)cryptoDataOut; - (void)outSize; - return WH_ERROR_NOHANDLER; -#else - int ret = 0; - wc_MlDsaKey key[1]; - void* msgAddr = NULL; - void* sigAddr = NULL; - int verified = 0; + int ret = 0; + SlhDsaKey key[1]; + void* msgAddr = NULL; + void* sigAddr = NULL; + int sigPre = 0; + int msgPre = 0; + whKeyId key_id; + int evict; + int result = 0; + uint32_t contextSz; + uint32_t preHashType; + byte* req_context = NULL; + + whMessageCrypto_SlhDsaVerifyDmaRequest req; + whMessageCrypto_SlhDsaVerifyDmaResponse res; - whMessageCrypto_MlDsaVerifyDmaRequest req; - whMessageCrypto_MlDsaVerifyDmaResponse res; + memset(&res, 0, sizeof(res)); - if (inSize < sizeof(whMessageCrypto_MlDsaVerifyDmaRequest)) { + if (inSize < sizeof(whMessageCrypto_SlhDsaVerifyDmaRequest)) { return WH_ERROR_BADARGS; } - /* Translate the request */ - ret = wh_MessageCrypto_TranslateMlDsaVerifyDmaRequest( - magic, (whMessageCrypto_MlDsaVerifyDmaRequest*)cryptoDataIn, &req); + ret = wh_MessageCrypto_TranslateSlhDsaVerifyDmaRequest( + magic, (whMessageCrypto_SlhDsaVerifyDmaRequest*)cryptoDataIn, &req); if (ret != WH_ERROR_OK) { return ret; } - /* Transaction state */ - whKeyId key_id; - int evict = 0; - - /* Get key ID and evict flag */ key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); - evict = !!(req.options & WH_MESSAGE_CRYPTO_MLDSA_VERIFY_OPTIONS_EVICT); + evict = !!(req.options & WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_EVICT); - /* Extract context from inline data after the struct */ - uint32_t contextSz = req.contextSz; - uint32_t preHashType = req.preHashType; - byte* req_context = NULL; - if (contextSz > WH_CRYPTO_MLDSA_MAX_CTX_LEN) { + contextSz = req.contextSz; + preHashType = req.preHashType; + if (contextSz > WH_CRYPTO_SLHDSA_MAX_CTX_LEN) { return WH_ERROR_BADARGS; } if (contextSz > 0) { - if (inSize < sizeof(whMessageCrypto_MlDsaVerifyDmaRequest) + contextSz) { + if (inSize < + sizeof(whMessageCrypto_SlhDsaVerifyDmaRequest) + contextSz) { return WH_ERROR_BADARGS; } req_context = (uint8_t*)(cryptoDataIn) + - sizeof(whMessageCrypto_MlDsaVerifyDmaRequest); - } - - /* Initialize key */ - ret = wc_MlDsaKey_Init(key, NULL, devId); - if (ret != 0) { - return ret; + sizeof(whMessageCrypto_SlhDsaVerifyDmaRequest); } - /* Export the key, enforcing the verify usage policy against the same - * locked snapshot that is exported. The non-DMA verify handler enforces - * the same policy. */ - ret = _MlDsaKeyCacheExportEnforce(ctx, key_id, WH_NVM_FLAGS_USAGE_VERIFY, - key); + ret = _SlhDsaInitForCachedKey(key, req.param, devId); if (ret == 0) { - /* Process client signature buffer address */ - ret = wh_Server_DmaProcessClientAddress( - ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, - WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); - + ret = _SlhDsaLoadKey(ctx, key_id, WH_NVM_FLAGS_USAGE_VERIFY, key); if (ret == 0) { - /* Process client message buffer address */ ret = wh_Server_DmaProcessClientAddress( - ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret == WH_ERROR_ACCESS) { + res.dmaAddrStatus.badAddr = req.sig; + } + sigPre = (ret == 0); if (ret == 0) { - /* Verify the signature using appropriate FIPS 204 API */ - if (preHashType != WC_HASH_TYPE_NONE) { - ret = wc_MlDsaKey_VerifyCtxHash( - key, sigAddr, req.sig.sz, req_context, (byte)contextSz, - msgAddr, req.msg.sz, preHashType, &verified); + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret == WH_ERROR_ACCESS) { + res.dmaAddrStatus.badAddr = req.msg; } - else { - ret = wc_MlDsaKey_VerifyCtx( - key, sigAddr, req.sig.sz, req_context, (byte)contextSz, - msgAddr, req.msg.sz, &verified); + msgPre = (ret == 0); + + if (ret == 0) { + ret = _SlhDsaVerifyDispatch( + key, req.options, (const byte*)sigAddr, + (word32)req.sig.sz, (const byte*)msgAddr, + (word32)req.msg.sz, req_context, contextSz, preHashType, + &result); } - } - if (sigAddr != NULL) { - /* Post-read processing of signature buffer */ + if (msgPre) { + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + } + } + if (sigPre) { (void)wh_Server_DmaProcessClientAddress( ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); } - - if (msgAddr != NULL) { - /* Post-read processing of message buffer */ - (void)wh_Server_DmaProcessClientAddress( - ctx, (uintptr_t)req.msg.addr, &msgAddr, - req.msg.sz, WH_DMA_OPER_CLIENT_READ_POST, - (whServerDmaFlags){0}); - } } + wc_SlhDsaKey_Free(key); } - /* Evict key if requested */ if (evict) { /* User requested to evict from cache, even if the call failed */ _CryptoEvictKeyLocked(ctx, key_id); } if (ret == 0) { - /* Set verification result */ - res.verifyResult = verified; + res.verifyResult = result; + } - /* Translate the response */ - (void)wh_MessageCrypto_TranslateMlDsaVerifyDmaResponse( - magic, &res, - (whMessageCrypto_MlDsaVerifyDmaResponse*)cryptoDataOut); + (void)wh_MessageCrypto_TranslateSlhDsaVerifyDmaResponse( + magic, &res, (whMessageCrypto_SlhDsaVerifyDmaResponse*)cryptoDataOut); - *outSize = sizeof(res); - } + *outSize = sizeof(res); - wc_MlDsaKey_Free(key); return ret; -#endif /* WOLFSSL_MLDSA_NO_VERIFY */ -} - -static int _HandleMlDsaCheckPrivKeyDma(whServerContext* ctx, uint16_t magic, - int devId, const void* cryptoDataIn, - uint16_t inSize, void* cryptoDataOut, - uint16_t* outSize) -{ - (void)ctx; - (void)magic; - (void)devId; - (void)cryptoDataIn; - (void)inSize; - (void)cryptoDataOut; - (void)outSize; - return WH_ERROR_NOHANDLER; } -#endif /* WOLFSSL_HAVE_MLDSA */ +#endif /* WOLFSSL_HAVE_SLHDSA */ -#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) +#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_SLHDSA) static int _HandlePqcSigAlgorithmDma(whServerContext* ctx, uint16_t magic, int devId, const void* cryptoDataIn, uint16_t cryptoInSize, void* cryptoDataOut, @@ -7395,6 +8543,38 @@ static int _HandlePqcSigAlgorithmDma(whServerContext* ctx, uint16_t magic, } } break; #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PQC_SIG_TYPE_SLHDSA: { + switch (pkAlgoType) { + case WC_PK_TYPE_PQC_SIG_KEYGEN: + ret = _HandleSlhDsaKeyGenDma(ctx, magic, devId, + cryptoDataIn, cryptoInSize, + cryptoDataOut, cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_SIGN: + case WC_PK_TYPE_PQC_SIG_SIGN_MSG: + ret = _HandleSlhDsaSignDma(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_VERIFY: + case WC_PK_TYPE_PQC_SIG_VERIFY_MSG: + ret = _HandleSlhDsaVerifyDma(ctx, magic, devId, + cryptoDataIn, cryptoInSize, + cryptoDataOut, cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY: + /* The 2n-byte public key fits the comm buffer */ + ret = _HandleSlhDsaCheckPrivKey( + ctx, magic, devId, cryptoDataIn, cryptoInSize, + cryptoDataOut, cryptoOutSize); + break; + default: + ret = WH_ERROR_NOHANDLER; + break; + } + } break; +#endif /* WOLFSSL_HAVE_SLHDSA */ default: ret = WH_ERROR_NOHANDLER; break; @@ -9367,17 +10547,22 @@ int wh_Server_HandleCryptoDmaRequest(whServerContext* ctx, uint16_t magic, case WC_ALGO_TYPE_PK: switch (rqstHeader.algoType) { -#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) +#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_SLHDSA) case WC_PK_TYPE_PQC_SIG_KEYGEN: case WC_PK_TYPE_PQC_SIG_SIGN: case WC_PK_TYPE_PQC_SIG_VERIFY: case WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY: +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PK_TYPE_PQC_SIG_SIGN_MSG: + case WC_PK_TYPE_PQC_SIG_VERIFY_MSG: +#endif /* WOLFSSL_HAVE_SLHDSA */ ret = _HandlePqcSigAlgorithmDma( ctx, magic, devId, cryptoDataIn, cryptoInSize, cryptoDataOut, &cryptoOutSize, rqstHeader.algoType, rqstHeader.algoSubType); break; -#endif /* WOLFSSL_HAVE_MLDSA || HAVE_FALCON */ +#endif /* WOLFSSL_HAVE_MLDSA || HAVE_FALCON || WOLFSSL_HAVE_SLHDSA */ #if defined(WOLFSSL_HAVE_MLKEM) case WC_PK_TYPE_PQC_KEM_KEYGEN: case WC_PK_TYPE_PQC_KEM_ENCAPS: diff --git a/src/wh_server_keystore.c b/src/wh_server_keystore.c index e520469b2..33fd245ea 100644 --- a/src/wh_server_keystore.c +++ b/src/wh_server_keystore.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * @@ -590,6 +590,34 @@ static int _ExportMldsaPublicKey(whServerContext* server, whKeyId keyId, } #endif +#ifdef WOLFSSL_HAVE_SLHDSA +static int _ExportSlhDsaPublicKey(whServerContext* server, whKeyId keyId, + uint8_t* out, uint16_t* outSz) +{ + int ret = WH_ERROR_OK; + SlhDsaKey key[1]; + int pub_ret; + int devId = (server->crypto != NULL) ? server->devId : INVALID_DEVID; + + /* The decoder detects the real parameter set from the key OID */ + ret = wc_SlhDsaKey_Init(key, WC_SLHDSA_DEFAULT_PARAM, NULL, devId); + if (ret == 0) { + ret = wh_Server_SlhDsaKeyCacheExport(server, keyId, key); + if (ret == 0) { + pub_ret = wc_SlhDsaKey_PublicKeyToDer(key, out, (word32)*outSz, 1); + if (pub_ret > 0) { + *outSz = (uint16_t)pub_ret; + } + else { + ret = (pub_ret == 0) ? WH_ERROR_ABORTED : pub_ret; + } + } + wc_SlhDsaKey_Free(key); + } + return ret; +} +#endif /* WOLFSSL_HAVE_SLHDSA */ + #ifdef HAVE_CURVE25519 static int _ExportCurve25519PublicKey(whServerContext* server, whKeyId keyId, uint8_t* out, uint16_t* outSz) @@ -2955,6 +2983,12 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic, stage, &stageMax); break; #endif /* WOLFSSL_HAVE_MLDSA && WOLFSSL_MLDSA_PUBLIC_KEY */ +#ifdef WOLFSSL_HAVE_SLHDSA + case WH_KEY_ALGO_SLHDSA: + ret = _ExportSlhDsaPublicKey(server, serverKeyId, + stage, &stageMax); + break; +#endif /* WOLFSSL_HAVE_SLHDSA */ #ifdef HAVE_CURVE25519 case WH_KEY_ALGO_CURVE25519: ret = _ExportCurve25519PublicKey(server, serverKeyId, @@ -3169,6 +3203,12 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic, out, &max_der); break; #endif /* WOLFSSL_HAVE_MLDSA && WOLFSSL_MLDSA_PUBLIC_KEY */ +#ifdef WOLFSSL_HAVE_SLHDSA + case WH_KEY_ALGO_SLHDSA: + ret = _ExportSlhDsaPublicKey(server, serverKeyId, + out, &max_der); + break; +#endif /* WOLFSSL_HAVE_SLHDSA */ #ifdef HAVE_CURVE25519 case WH_KEY_ALGO_CURVE25519: ret = _ExportCurve25519PublicKey(server, diff --git a/test-refactor/client-server/wh_test_crypto_slhdsa.c b/test-refactor/client-server/wh_test_crypto_slhdsa.c new file mode 100644 index 000000000..b2cb26f2c --- /dev/null +++ b/test-refactor/client-server/wh_test_crypto_slhdsa.c @@ -0,0 +1,3038 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM 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. + * + * wolfHSM 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 wolfHSM. If not, see . + */ +/* + * test-refactor/client-server/wh_test_crypto_slhdsa.c + * + * SLH-DSA tests routed through the server. + * + * Only 128s fits the comm buffer, so other parameter sets need DMA. The DMA + * tests use 128f since it signs much faster. + */ + +#include "wolfhsm/wh_settings.h" + +#if !defined(WOLFHSM_CFG_NO_CRYPTO) + +#include +#include + +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/random.h" +#include "wolfssl/wolfcrypt/wc_slhdsa.h" +#include "wolfssl/wolfcrypt/dilithium.h" +#include "wolfssl/wolfcrypt/error-crypt.h" + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_client_crypto.h" +#include "wolfhsm/wh_client_cryptocb.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" + +#ifdef WOLFSSL_HAVE_SLHDSA + +#if defined(WOLFSSL_SLHDSA_PARAM_128S) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) +#define WH_TEST_SLHDSA_COMM_PARAM SLHDSA_SHAKE128S +#define WH_TEST_SLHDSA_COMM_SIG_LEN WC_SLHDSA_SHAKE128S_SIG_LEN +#endif /* WOLFSSL_SLHDSA_PARAM_128S && !WOLFSSL_SLHDSA_VERIFY_ONLY */ + +#if defined(WOLFSSL_SLHDSA_PARAM_128F) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) +#define WH_TEST_SLHDSA_FAST_PARAM SLHDSA_SHAKE128F +#define WH_TEST_SLHDSA_FAST_SIG_LEN WC_SLHDSA_SHAKE128F_SIG_LEN +#endif /* WOLFSSL_SLHDSA_PARAM_128F && !WOLFSSL_SLHDSA_VERIFY_ONLY */ + +/* DMA tests need no comm-buffer room: prefer 128F, fall back to 128S */ +#if defined(WH_TEST_SLHDSA_FAST_PARAM) +#define WH_TEST_SLHDSA_DMA_PARAM WH_TEST_SLHDSA_FAST_PARAM +#define WH_TEST_SLHDSA_DMA_SIG_LEN WH_TEST_SLHDSA_FAST_SIG_LEN +#elif defined(WH_TEST_SLHDSA_COMM_PARAM) +#define WH_TEST_SLHDSA_DMA_PARAM WH_TEST_SLHDSA_COMM_PARAM +#define WH_TEST_SLHDSA_DMA_SIG_LEN WH_TEST_SLHDSA_COMM_SIG_LEN +#endif /* WH_TEST_SLHDSA_FAST_PARAM || WH_TEST_SLHDSA_COMM_PARAM */ + +/* No verify-only exclusion: the KAT is all a verify-only build can run */ +#if defined(WOLFSSL_SLHDSA_PARAM_128S) +#define WH_TEST_SLHDSA_KAT_PARAM SLHDSA_SHAKE128S +#define WH_TEST_SLHDSA_KAT_PUB_LEN WC_SLHDSA_SHAKE128S_PUB_LEN +#define WH_TEST_SLHDSA_KAT_SIG_LEN WC_SLHDSA_SHAKE128S_SIG_LEN +#endif /* WOLFSSL_SLHDSA_PARAM_128S */ + +#ifdef WH_TEST_SLHDSA_COMM_PARAM + +/* Ephemeral key generate, sign and verify through the wolfCrypt API */ +static int _whTest_SlhDsaWolfCryptImpl(whClientContext* ctx, int devId) +{ + int ret = 0; + int verified = 0; + SlhDsaKey key[1]; + WC_RNG rng[1]; + byte msg[] = "Test message for SLH-DSA signing"; + byte sig[WH_TEST_SLHDSA_COMM_SIG_LEN]; + word32 sigSz = sizeof(sig); + + ret = wc_InitRng_ex(rng, NULL, WH_CLIENT_DEVID(ctx)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret); + return ret; + } + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to initialize SLH-DSA key: %d\n", ret); + (void)wc_FreeRng(rng); + return ret; + } + + if (ret == 0) { + ret = wc_SlhDsaKey_MakeKey(key, rng); + if (ret != 0) { + WH_ERROR_PRINT("Failed to generate SLH-DSA key: %d\n", ret); + } + } + if (ret == 0) { + ret = + wc_SlhDsaKey_Sign(key, NULL, 0, msg, sizeof(msg), sig, &sigSz, rng); + if (ret != 0) { + WH_ERROR_PRINT("Failed to sign with SLH-DSA: %d\n", ret); + } + } + if (ret == 0) { + ret = wc_SlhDsaKey_Verify(key, NULL, 0, msg, sizeof(msg), sig, sigSz); + if (ret != 0) { + WH_ERROR_PRINT("Failed to verify SLH-DSA signature: %d\n", ret); + } + else { + verified = 1; + } + } + /* Tamper check: a corrupted signature must not verify. */ + if ((ret == 0) && verified) { + sig[0] ^= 1; + ret = wc_SlhDsaKey_Verify(key, NULL, 0, msg, sizeof(msg), sig, sigSz); + if (ret == 0) { + WH_ERROR_PRINT("SLH-DSA verified a tampered signature\n"); + ret = -1; + } + else { + ret = 0; + } + } + + if (ret == 0) { + WH_TEST_PRINT("SLH-DSA WOLFCRYPT DEVID=0x%X SUCCESS\n", devId); + } + + wc_SlhDsaKey_Free(key); + (void)wc_FreeRng(rng); + return ret; +} + +/* Pure, context and pre-hash signing over the comm buffer */ +static int _whTest_CryptoSlhDsaClient(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + SlhDsaKey key[1]; + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to initialize SLH-DSA key: %d\n", ret); + return ret; + } + + ret = wh_Client_SlhDsaMakeExportKey(ctx, WH_TEST_SLHDSA_COMM_PARAM, key); + if (ret != 0) { + WH_ERROR_PRINT("Failed to generate SLH-DSA key: %d\n", ret); + goto done; + } + + { + byte msg[] = "Test message for non-DMA SLH-DSA"; + byte sig[WH_TEST_SLHDSA_COMM_SIG_LEN]; + word32 sigLen = sizeof(sig); + int verified = 0; + + ret = wh_Client_SlhDsaSign(ctx, msg, sizeof(msg), sig, &sigLen, key, + NULL, 0, WC_HASH_TYPE_NONE, NULL, 0, 1, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to sign using SLH-DSA non-DMA: %d\n", ret); + goto done; + } + if (sigLen != WH_TEST_SLHDSA_COMM_SIG_LEN) { + WH_ERROR_PRINT("SLH-DSA signature length %u, expected %u\n", + (unsigned)sigLen, + (unsigned)WH_TEST_SLHDSA_COMM_SIG_LEN); + ret = WH_TEST_FAIL; + goto done; + } + + ret = wh_Client_SlhDsaVerify(ctx, sig, sigLen, msg, sizeof(msg), + &verified, key, NULL, 0, WC_HASH_TYPE_NONE, + 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to verify SLH-DSA non-DMA: %d\n", ret); + goto done; + } + if (!verified) { + WH_ERROR_PRINT("SLH-DSA non-DMA verification failed\n"); + ret = WH_TEST_FAIL; + goto done; + } + + /* A tampered signature must come back as a result, not an error */ + sig[0] ^= 0xFF; + ret = wh_Client_SlhDsaVerify(ctx, sig, sigLen, msg, sizeof(msg), + &verified, key, NULL, 0, WC_HASH_TYPE_NONE, + 0); + if (ret != 0) { + WH_ERROR_PRINT("Verify with modified sig returned %d\n", ret); + goto done; + } + if (verified) { + WH_ERROR_PRINT("SLH-DSA non-DMA verified a bad signature\n"); + ret = WH_TEST_FAIL; + goto done; + } + + verified = 1; + /* A failed request must still clear the result */ + ret = wh_Client_SlhDsaVerify(ctx, sig, sigLen - 1, msg, sizeof(msg), + &verified, key, NULL, 0, WC_HASH_TYPE_NONE, + 0); + if ((ret == 0) || verified) { + WH_ERROR_PRINT("SLH-DSA non-DMA short signature: ret %d, " + "verified %d\n", + ret, verified); + ret = WH_TEST_FAIL; + goto done; + } + ret = 0; + } + + /* FIPS 205 context string */ + { + byte msg[] = "Context test message non-DMA"; + const byte context[] = {'w', 'o', 'l', 'f', 'H', 'S', 'M'}; + byte sig[WH_TEST_SLHDSA_COMM_SIG_LEN]; + word32 sigLen = sizeof(sig); + int verified = 0; + + ret = wh_Client_SlhDsaSign(ctx, msg, sizeof(msg), sig, &sigLen, key, + context, (byte)sizeof(context), + WC_HASH_TYPE_NONE, NULL, 0, 1, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to sign with context: %d\n", ret); + goto done; + } + + ret = wh_Client_SlhDsaVerify( + ctx, sig, sigLen, msg, sizeof(msg), &verified, key, context, + (byte)sizeof(context), WC_HASH_TYPE_NONE, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to verify with context: %d\n", ret); + goto done; + } + if (!verified) { + WH_ERROR_PRINT("SLH-DSA context verification failed\n"); + ret = WH_TEST_FAIL; + goto done; + } + + /* The context is bound into the signature, so a different one fails */ + ret = wh_Client_SlhDsaVerify(ctx, sig, sigLen, msg, sizeof(msg), + &verified, key, NULL, 0, WC_HASH_TYPE_NONE, + 0); + if (ret != 0) { + WH_ERROR_PRINT("Verify with dropped context returned %d\n", ret); + goto done; + } + if (verified) { + WH_ERROR_PRINT("SLH-DSA verified across differing contexts\n"); + ret = WH_TEST_FAIL; + goto done; + } + } + +#ifndef NO_SHA256 + /* HashSLH-DSA over a caller-supplied digest */ + { + byte digest[WC_SHA256_DIGEST_SIZE]; + byte sig[WH_TEST_SLHDSA_COMM_SIG_LEN]; + word32 sigLen = sizeof(sig); + int verified = 0; + + memset(digest, 0x5A, sizeof(digest)); + + ret = + wh_Client_SlhDsaSign(ctx, digest, sizeof(digest), sig, &sigLen, key, + NULL, 0, WC_HASH_TYPE_SHA256, NULL, 0, 1, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to pre-hash sign: %d\n", ret); + goto done; + } + + ret = wh_Client_SlhDsaVerify(ctx, sig, sigLen, digest, sizeof(digest), + &verified, key, NULL, 0, + WC_HASH_TYPE_SHA256, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to pre-hash verify: %d\n", ret); + goto done; + } + if (!verified) { + WH_ERROR_PRINT("SLH-DSA pre-hash verification failed\n"); + ret = WH_TEST_FAIL; + goto done; + } + } +#endif /* !NO_SHA256 */ + + WH_TEST_PRINT("SLH-DSA NON-DMA DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + wc_SlhDsaKey_Free(key); + return ret; +} + +/* Sign with a server-only key; the caller supplies the randomizer */ +static int _whTest_CryptoSlhDsaCachedKey(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + whKeyId keyId = WH_KEYID_ERASED; + SlhDsaKey pub[1]; + SlhDsaKey handle[1]; + int pubInit = 0; + int handleInit = 0; + uint8_t label[] = "SlhDsaCached"; + byte addRnd[WC_SLHDSA_MAX_SEED]; + + memset(addRnd, 0x42, sizeof(addRnd)); + + ret = wc_SlhDsaKey_Init(pub, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to init SLH-DSA pub key: %d\n", ret); + return ret; + } + pubInit = 1; + + ret = wh_Client_SlhDsaMakeCacheKeyAndExportPublic( + ctx, WH_TEST_SLHDSA_COMM_PARAM, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY, sizeof(label), + label, pub); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache SLH-DSA key: %d\n", ret); + goto done; + } + + /* A bare handle: no key material, only the server key ID */ + ret = wc_SlhDsaKey_Init(handle, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to init SLH-DSA handle: %d\n", ret); + goto done; + } + handleInit = 1; + + ret = wh_Client_SlhDsaSetKeyId(handle, keyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to set SLH-DSA key id: %d\n", ret); + goto done; + } + + { + byte msg[] = "Signed by a key that never left the HSM"; + byte sig[WH_TEST_SLHDSA_COMM_SIG_LEN]; + word32 sigLen = sizeof(sig); + + /* A caller-supplied randomizer lets the server sign with its key */ + ret = wc_SlhDsaKey_SignWithRandom(handle, NULL, 0, msg, sizeof(msg), + sig, &sigLen, addRnd); + if (ret != 0) { + WH_ERROR_PRINT("Failed to sign with a cached SLH-DSA key: %d\n", + ret); + goto done; + } + + /* The exported public key verifies it, through the server */ + ret = wc_SlhDsaKey_Verify(pub, NULL, 0, msg, sizeof(msg), sig, sigLen); + if (ret != 0) { + WH_ERROR_PRINT("Cached-key signature did not verify: %d\n", ret); + goto done; + } + + /* Same randomizer and message must give the same signature */ + { + byte sig2[WH_TEST_SLHDSA_COMM_SIG_LEN]; + word32 sig2Len = sizeof(sig2); + + ret = wc_SlhDsaKey_SignWithRandom(handle, NULL, 0, msg, sizeof(msg), + sig2, &sig2Len, addRnd); + if (ret != 0) { + WH_ERROR_PRINT("Second cached-key sign failed: %d\n", ret); + goto done; + } + if ((sig2Len != sigLen) || (memcmp(sig, sig2, sigLen) != 0)) { + WH_ERROR_PRINT("Cached-key SLH-DSA signatures differ\n"); + ret = WH_TEST_FAIL; + goto done; + } + + /* A different randomizer must change the signature */ + memset(addRnd, 0x17, sizeof(addRnd)); + sig2Len = sizeof(sig2); + ret = wc_SlhDsaKey_SignWithRandom(handle, NULL, 0, msg, sizeof(msg), + sig2, &sig2Len, addRnd); + if (ret != 0) { + WH_ERROR_PRINT("Third cached-key sign failed: %d\n", ret); + goto done; + } + if ((sig2Len == sigLen) && (memcmp(sig, sig2, sigLen) == 0)) { + WH_ERROR_PRINT("Cached-key randomizer was ignored\n"); + ret = WH_TEST_FAIL; + goto done; + } + ret = wc_SlhDsaKey_Verify(pub, NULL, 0, msg, sizeof(msg), sig2, + sig2Len); + if (ret != 0) { + WH_ERROR_PRINT("Re-randomized signature did not verify: %d\n", + ret); + goto done; + } + } + } + + WH_TEST_PRINT("SLH-DSA CACHED KEY DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + if (handleInit) { + wc_SlhDsaKey_Free(handle); + } + if (pubInit) { + wc_SlhDsaKey_Free(pub); + } + return ret; +} + +/* FIPS 205 internal interface: the caller builds M' */ +static int _whTest_CryptoSlhDsaMPrime(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + SlhDsaKey key[1]; + /* M' for a pure signature with an empty context: 0x00 || ctxSz || msg */ + byte mprime[] = {0x00, 0x00, 'm', 'p', 'r', 'i', 'm', 'e'}; + byte sig[WH_TEST_SLHDSA_COMM_SIG_LEN]; + word32 sigLen = sizeof(sig); + byte addRnd[WC_SLHDSA_MAX_SEED]; + + memset(addRnd, 0x42, sizeof(addRnd)); + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to initialize SLH-DSA key: %d\n", ret); + return ret; + } + + ret = wh_Client_SlhDsaMakeExportKey(ctx, WH_TEST_SLHDSA_COMM_PARAM, key); + if (ret != 0) { + WH_ERROR_PRINT("Failed to generate SLH-DSA key: %d\n", ret); + goto done; + } + + ret = wc_SlhDsaKey_SignMsgWithRandom(key, mprime, sizeof(mprime), sig, + &sigLen, addRnd); + if (ret != 0) { + WH_ERROR_PRINT("Failed to sign M': %d\n", ret); + goto done; + } + + ret = wc_SlhDsaKey_VerifyMsg(key, mprime, sizeof(mprime), sig, sigLen); + if (ret != 0) { + WH_ERROR_PRINT("Failed to verify M' signature: %d\n", ret); + goto done; + } + + /* The same signature must not verify against a different M' */ + mprime[2] ^= 0xFF; + ret = wc_SlhDsaKey_VerifyMsg(key, mprime, sizeof(mprime), sig, sigLen); + if (ret == 0) { + WH_ERROR_PRINT("M' verification accepted the wrong message\n"); + ret = WH_TEST_FAIL; + goto done; + } + + WH_TEST_PRINT("SLH-DSA MPRIME DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + wc_SlhDsaKey_Free(key); + return ret; +} + +/* wc_SlhDsaKey_CheckKey against a server-resident private key. */ +static int _whTest_CryptoSlhDsaCheckPrivKey(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + whKeyId keyId = WH_KEYID_ERASED; + SlhDsaKey pub[1]; + SlhDsaKey handle[1]; + int pubInit = 0; + int handleInit = 0; + uint8_t label[] = "SlhDsaCheck"; + + ret = wc_SlhDsaKey_Init(pub, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + return ret; + } + pubInit = 1; + + ret = wh_Client_SlhDsaMakeCacheKeyAndExportPublic( + ctx, WH_TEST_SLHDSA_COMM_PARAM, &keyId, WH_NVM_FLAGS_USAGE_SIGN, + sizeof(label), label, pub); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache SLH-DSA key: %d\n", ret); + goto done; + } + + ret = wc_SlhDsaKey_Init(handle, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + goto done; + } + handleInit = 1; + (void)wh_Client_SlhDsaSetKeyId(handle, keyId); + + { + byte expected[WC_SLHDSA_MAX_PUB_LEN]; + int pubSz; + + pubSz = wc_SlhDsaKey_PublicSize(pub); + if ((pubSz <= 0) || ((word32)pubSz > sizeof(expected))) { + WH_ERROR_PRINT("Bad SLH-DSA public size %d\n", pubSz); + ret = WH_TEST_FAIL; + goto done; + } + ret = wc_SlhDsaKey_ExportPublic(pub, expected, (word32*)&pubSz); + if (ret != 0) { + WH_ERROR_PRINT("Failed to export SLH-DSA public key: %d\n", ret); + goto done; + } + + /* CheckKey reads the public key from the handle itself */ + ret = wc_SlhDsaKey_CheckKey(handle); + if (ret != 0) { + WH_ERROR_PRINT("CheckKey rejected the cached key: %d\n", ret); + goto done; + } + + ret = + wh_Client_SlhDsaCheckPrivKey(ctx, handle, expected, (word32)pubSz); + if (ret != 0) { + WH_ERROR_PRINT("CheckPrivKey rejected the matching key: %d\n", ret); + goto done; + } + + /* A public key from another key pair must be rejected */ + expected[0] ^= 0xFF; + ret = + wh_Client_SlhDsaCheckPrivKey(ctx, handle, expected, (word32)pubSz); + if (ret != WC_KEY_MISMATCH_E) { + WH_ERROR_PRINT("CheckPrivKey accepted a mismatched key: %d\n", ret); + ret = WH_TEST_FAIL; + goto done; + } + } + + WH_TEST_PRINT("SLH-DSA CHECKPRIVKEY DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + if (handleInit) { + wc_SlhDsaKey_Free(handle); + } + if (pubInit) { + wc_SlhDsaKey_Free(pub); + } + return ret; +} + +#ifndef NO_SHA256 +/* A digest length that does not match the hash type is an error, not res=0 */ +static int _whTest_CryptoSlhDsaBadDigestLen(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + SlhDsaKey key[1]; + byte digest[WC_SHA256_DIGEST_SIZE]; + byte sig[WH_TEST_SLHDSA_COMM_SIG_LEN]; + word32 sigLen = sizeof(sig); + int verified = 1; + + memset(digest, 0x3C, sizeof(digest)); + memset(sig, 0, sizeof(sig)); + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + return ret; + } + + ret = wh_Client_SlhDsaMakeExportKey(ctx, WH_TEST_SLHDSA_COMM_PARAM, key); + if (ret != 0) { + WH_ERROR_PRINT("Failed to generate SLH-DSA key: %d\n", ret); + goto done; + } + + ret = wh_Client_SlhDsaSign(ctx, digest, sizeof(digest), sig, &sigLen, key, + NULL, 0, WC_HASH_TYPE_SHA256, NULL, 0, 1, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to pre-hash sign: %d\n", ret); + goto done; + } + + /* Same signature, same hash type, digest one byte short */ + ret = wh_Client_SlhDsaVerify(ctx, sig, sigLen, digest, + (word32)sizeof(digest) - 1, &verified, key, + NULL, 0, WC_HASH_TYPE_SHA256, 0); + if (ret == 0) { + WH_ERROR_PRINT("Short digest reported as a verify result (res=%d) " + "instead of an error\n", + verified); + ret = WH_TEST_FAIL; + goto done; + } + + WH_TEST_PRINT("SLH-DSA BAD DIGEST LEN DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + wc_SlhDsaKey_Free(key); + return ret; +} +#endif /* !NO_SHA256 */ + +/* A short buffer must get WH_ERROR_BUFFER_SIZE and the needed length */ +static int _whTest_CryptoSlhDsaBufferTooSmall(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + SlhDsaKey key[1]; + const byte msg[] = "slh-dsa buf size test"; + uint8_t small_sig[16] = {0}; + word32 small_buf_sz = (word32)sizeof(small_sig); + word32 sig_len; + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + return ret; + } + + ret = wh_Client_SlhDsaMakeExportKey(ctx, WH_TEST_SLHDSA_COMM_PARAM, key); + if (ret != 0) { + WH_ERROR_PRINT("Failed to generate SLH-DSA key: %d\n", ret); + goto done; + } + + sig_len = small_buf_sz; + ret = + wh_Client_SlhDsaSign(ctx, msg, (word32)sizeof(msg), small_sig, &sig_len, + key, NULL, 0, WC_HASH_TYPE_NONE, NULL, 0, 1, 0); + if (ret != WH_ERROR_BUFFER_SIZE) { + WH_ERROR_PRINT("SlhDsaSign small buf expected WH_ERROR_BUFFER_SIZE, " + "got %d\n", + ret); + ret = WH_TEST_FAIL; + goto done; + } + if (sig_len <= small_buf_sz) { + WH_ERROR_PRINT("SlhDsaSign small buf reported size %u not greater " + "than %u\n", + (unsigned)sig_len, (unsigned)small_buf_sz); + ret = WH_TEST_FAIL; + goto done; + } + + WH_TEST_PRINT("SLH-DSA BUFFER SIZE DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + wc_SlhDsaKey_Free(key); + return ret; +} + +#endif /* WH_TEST_SLHDSA_COMM_PARAM */ + +#ifdef WH_TEST_SLHDSA_FAST_PARAM +/* NIST CAVP SLH-DSA-SHAKE-128f keyGen vector (tgId=4, tcId=31) */ +static const byte whTestSlhDsaKatSeed[] = { + /* SK.seed */ + 0x39, 0x56, 0xAB, 0x39, 0x1B, 0x4D, 0x22, 0xFC, 0x90, 0x7A, 0xF0, 0x74, + 0x03, 0x26, 0xD0, 0x61, + /* SK.prf */ + 0xAB, 0x0E, 0xB2, 0x06, 0x43, 0x6F, 0x2B, 0x86, 0xEB, 0xE0, 0x86, 0xD7, + 0x77, 0x39, 0xB3, 0xE4, + /* PK.seed */ + 0x56, 0x50, 0x5C, 0x22, 0x9F, 0x4E, 0x7F, 0xA6, 0xB2, 0x01, 0x71, 0x4C, + 0x7D, 0xCC, 0x9D, 0xA3}; + +static const byte whTestSlhDsaKatPub[] = { + /* PK.seed */ + 0x56, 0x50, 0x5C, 0x22, 0x9F, 0x4E, 0x7F, 0xA6, 0xB2, 0x01, 0x71, 0x4C, + 0x7D, 0xCC, 0x9D, 0xA3, + /* PK.root */ + 0x66, 0x57, 0x8F, 0x1F, 0x24, 0xC3, 0xFE, 0x37, 0x1C, 0x97, 0xC1, 0x4C, + 0xE0, 0xE7, 0x9C, 0xDC}; + +/* Seeded keygen over the comm buffer; only the small key material is sent */ +static int _whTest_CryptoSlhDsaSeededKat(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + SlhDsaKey key[1]; + byte pub[WC_SLHDSA_MAX_PUB_LEN]; + word32 pubSz = sizeof(pub); + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_FAST_PARAM, NULL, devId); + if (ret != 0) { + return ret; + } + + /* Seeded keygen through wolfCrypt, as an application would */ + { + word32 n = (word32)(sizeof(whTestSlhDsaKatSeed) / 3); + + ret = wc_SlhDsaKey_MakeKeyWithRandom(key, whTestSlhDsaKatSeed, n, + whTestSlhDsaKatSeed + n, n, + whTestSlhDsaKatSeed + 2 * n, n); + } + if (ret != 0) { + WH_ERROR_PRINT("Failed seeded SLH-DSA keygen: %d\n", ret); + goto done; + } + + ret = wc_SlhDsaKey_ExportPublic(key, pub, &pubSz); + if (ret != 0) { + WH_ERROR_PRINT("Failed to export seeded public key: %d\n", ret); + goto done; + } + + if ((pubSz != sizeof(whTestSlhDsaKatPub)) || + (memcmp(pub, whTestSlhDsaKatPub, pubSz) != 0)) { + WH_ERROR_PRINT("Seeded SLH-DSA public key does not match the KAT\n"); + ret = WH_TEST_FAIL; + goto done; + } + + WH_TEST_PRINT("SLH-DSA SEEDED KAT DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + wc_SlhDsaKey_Free(key); + return ret; +} + +#endif /* WH_TEST_SLHDSA_FAST_PARAM */ + +#ifdef WH_TEST_SLHDSA_KAT_PARAM + +/* SLH-DSA-SHAKE-128s key and signature for verify-only builds */ +static const byte whTestSlhDsaVerifyPub[WH_TEST_SLHDSA_KAT_PUB_LEN] = { + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, + 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x6d, 0x05, 0x9c, 0x8a, 0x12, 0xa4, + 0xea, 0xee, 0xa4, 0xfb, 0x9f, 0x1a, 0x75, 0x99, 0x8b, 0x0f}; + +static const byte whTestSlhDsaVerifyMsg[] = + "wolfHSM SLH-DSA verify-only known answer test"; + +static const byte whTestSlhDsaVerifySig[WH_TEST_SLHDSA_KAT_SIG_LEN] = { + 0x83, 0x12, 0x67, 0x69, 0xbf, 0x6c, 0xf3, 0xc7, 0x30, 0x02, 0xc9, 0x9b, + 0x12, 0x4e, 0xfe, 0x9d, 0x7e, 0xdf, 0x36, 0x5c, 0x2d, 0x94, 0xe0, 0x7e, + 0xab, 0x39, 0x62, 0xb5, 0x71, 0xd3, 0xbe, 0xb6, 0xfe, 0xd0, 0x37, 0xa7, + 0x92, 0xec, 0x17, 0xb1, 0x46, 0x0f, 0xe9, 0x6a, 0xcc, 0x91, 0x5c, 0x23, + 0x61, 0xda, 0x5e, 0x24, 0x27, 0xfb, 0x99, 0xc3, 0x47, 0x8a, 0x5c, 0x3f, + 0xdf, 0xe5, 0xe3, 0xed, 0x06, 0x23, 0x11, 0x06, 0xba, 0x1b, 0xf8, 0xe3, + 0x61, 0xd1, 0xa7, 0xb2, 0x3b, 0x97, 0x58, 0x2d, 0x2a, 0xe3, 0xa0, 0x00, + 0x6a, 0x4c, 0xb1, 0x5d, 0x83, 0x9c, 0x70, 0x20, 0x56, 0x2a, 0x11, 0x22, + 0xb8, 0x3d, 0xdf, 0x46, 0x09, 0xe8, 0xe3, 0x8e, 0x00, 0xf6, 0xf4, 0x8f, + 0x9c, 0x37, 0xfe, 0x1e, 0x76, 0xa3, 0x42, 0xfa, 0xbc, 0xcb, 0x92, 0x43, + 0x29, 0xd6, 0x2a, 0x9f, 0xe8, 0x7b, 0x02, 0x65, 0x95, 0x7f, 0x5d, 0xaa, + 0x80, 0x53, 0x6d, 0xe1, 0x87, 0x3c, 0x77, 0x6c, 0x35, 0x4a, 0x9c, 0x58, + 0xd5, 0xbc, 0x6a, 0xfd, 0xbd, 0x42, 0x67, 0xa3, 0x8b, 0x09, 0xac, 0xea, + 0x81, 0x27, 0x53, 0xf2, 0xd3, 0xd5, 0x1e, 0x4b, 0xa0, 0xcc, 0x1a, 0x17, + 0x6a, 0x59, 0x21, 0x7c, 0x34, 0xe1, 0xa5, 0xf8, 0x2e, 0x4e, 0x50, 0x21, + 0x91, 0xa2, 0x36, 0x58, 0x1d, 0x3b, 0x6d, 0xc8, 0x08, 0xd2, 0x6a, 0x64, + 0x42, 0x0c, 0x05, 0x63, 0x95, 0x0b, 0xeb, 0x19, 0xf1, 0x37, 0x15, 0x1b, + 0xe2, 0xaf, 0xd9, 0xa8, 0x75, 0x9e, 0x78, 0x4f, 0x00, 0x36, 0x69, 0x2a, + 0x62, 0xa1, 0x0c, 0x8a, 0xd6, 0x51, 0x36, 0x2f, 0xa3, 0xb1, 0x8e, 0xf4, + 0xa0, 0xed, 0x19, 0xc8, 0xed, 0x2e, 0xbb, 0x5d, 0x1e, 0x1a, 0x0d, 0xd3, + 0x10, 0xa0, 0xb4, 0x07, 0xee, 0x4d, 0xaf, 0x26, 0xfd, 0x9d, 0x49, 0x20, + 0x42, 0xbb, 0x83, 0x45, 0xb5, 0x0f, 0x42, 0x8d, 0xed, 0xa1, 0xe1, 0xc5, + 0xda, 0x4d, 0xfb, 0xd5, 0x88, 0xec, 0x2f, 0x3b, 0xdb, 0x50, 0xa3, 0xa9, + 0x7a, 0x43, 0xa7, 0x1d, 0x7a, 0x90, 0x4a, 0x63, 0xe4, 0x00, 0xfd, 0xa3, + 0x48, 0x53, 0x7a, 0x74, 0xe6, 0xf5, 0x76, 0x2f, 0x12, 0x47, 0x01, 0xb5, + 0xa5, 0xc4, 0xec, 0x34, 0x26, 0x07, 0xdb, 0xed, 0x22, 0x9e, 0xd0, 0xc3, + 0xa2, 0x11, 0x3b, 0x05, 0x34, 0xd1, 0x7d, 0x0d, 0xe7, 0x89, 0x43, 0x1c, + 0x9b, 0x61, 0x14, 0x86, 0x75, 0xd5, 0xba, 0x98, 0x88, 0xad, 0xb8, 0x61, + 0xae, 0x4f, 0x3b, 0x3d, 0x37, 0x4d, 0xe0, 0xc6, 0x5b, 0xae, 0xb1, 0xd2, + 0x3c, 0xb5, 0x84, 0x73, 0x6b, 0xa2, 0xec, 0xb7, 0xb9, 0x22, 0xe8, 0x0b, + 0x67, 0x9e, 0x3c, 0x6f, 0x69, 0x20, 0xb0, 0xb1, 0x34, 0xd1, 0x68, 0xe7, + 0x35, 0x01, 0xf6, 0xea, 0x8a, 0xa7, 0x8c, 0xbd, 0x13, 0x79, 0x6c, 0xde, + 0x2e, 0x55, 0x26, 0x08, 0x2a, 0x60, 0x59, 0x87, 0xb1, 0x36, 0x22, 0x67, + 0x2d, 0xea, 0x25, 0xfb, 0x48, 0x4f, 0x63, 0xa3, 0x03, 0x84, 0xc5, 0x02, + 0x65, 0x4f, 0xf5, 0xa1, 0xb0, 0xc3, 0xd8, 0x35, 0xd0, 0x80, 0xb0, 0x65, + 0xa5, 0xa5, 0xad, 0x89, 0x12, 0x37, 0xa9, 0x04, 0x50, 0xc8, 0x41, 0x2e, + 0x46, 0x93, 0x42, 0x8e, 0xba, 0xa1, 0xb9, 0xf0, 0x9d, 0xbd, 0xd7, 0xd4, + 0x83, 0x38, 0x53, 0xf6, 0xf7, 0xf8, 0x1f, 0x43, 0xc3, 0x28, 0x0f, 0x5b, + 0xfe, 0x27, 0x43, 0x98, 0xb9, 0x4c, 0xf2, 0xa1, 0xa0, 0x6a, 0x70, 0x1e, + 0x65, 0x69, 0x16, 0xc0, 0x07, 0x1d, 0x17, 0x42, 0x41, 0xc0, 0x9d, 0x92, + 0x69, 0xd5, 0xae, 0x1d, 0x02, 0xab, 0x04, 0xde, 0x3d, 0xd3, 0xc7, 0x58, + 0x7c, 0x24, 0xbb, 0x96, 0xf5, 0x88, 0xdb, 0xc6, 0x5b, 0x55, 0xe0, 0x43, + 0x73, 0xca, 0xa0, 0xbf, 0x2c, 0xdf, 0xfa, 0x9f, 0x25, 0x5c, 0xa6, 0x93, + 0x41, 0x02, 0x26, 0x73, 0xc2, 0xda, 0x83, 0xa1, 0x87, 0xa0, 0x34, 0x68, + 0xdb, 0x74, 0xfe, 0xa9, 0x2d, 0x9c, 0x5c, 0xe1, 0x4d, 0xb5, 0xae, 0x1b, + 0x8c, 0x56, 0xf9, 0xc4, 0x6c, 0xac, 0xa6, 0xef, 0x44, 0xd5, 0x81, 0xb9, + 0x2d, 0xf4, 0x29, 0x72, 0x4b, 0xd9, 0x8a, 0xe7, 0x5f, 0x44, 0x8b, 0xbc, + 0x08, 0x89, 0xf4, 0x47, 0x56, 0x56, 0xfe, 0x2e, 0xd0, 0xa7, 0x5b, 0x72, + 0xab, 0x4c, 0x5c, 0x04, 0x68, 0x43, 0x76, 0x5c, 0xfb, 0xfb, 0x63, 0x0c, + 0x33, 0xe3, 0xe3, 0x8b, 0xac, 0xfb, 0xd3, 0x56, 0x95, 0x99, 0x77, 0xb1, + 0x0f, 0x22, 0x94, 0x54, 0xde, 0xf9, 0xd7, 0x81, 0xfd, 0xaa, 0xc7, 0x97, + 0xfc, 0xbf, 0xdf, 0x74, 0xc0, 0x96, 0xbe, 0x97, 0xaf, 0xb3, 0x54, 0xc0, + 0x58, 0x65, 0x4e, 0xa2, 0x0a, 0x06, 0x26, 0x14, 0xea, 0x02, 0x9a, 0x32, + 0xc5, 0x0f, 0x7d, 0x7b, 0x99, 0x6d, 0x92, 0xb2, 0x73, 0xf1, 0xa7, 0x55, + 0xba, 0x96, 0x4b, 0xe0, 0xdd, 0x24, 0x5b, 0x30, 0xb4, 0x6a, 0x71, 0xce, + 0xb6, 0xb8, 0xad, 0xe2, 0x07, 0x0a, 0x8f, 0xbb, 0xee, 0x9f, 0x90, 0x64, + 0xe0, 0xa3, 0xb7, 0x5b, 0x28, 0xe9, 0x7c, 0x7c, 0x88, 0x2a, 0x6a, 0x25, + 0x76, 0x4d, 0x21, 0xd1, 0x3e, 0xf8, 0x1c, 0x57, 0xbd, 0x8d, 0x37, 0x26, + 0xf7, 0x32, 0xe9, 0x3e, 0xdd, 0xe0, 0x87, 0x8f, 0x1d, 0x5d, 0xfb, 0x1a, + 0xa4, 0xa8, 0x8c, 0x0e, 0xf7, 0xeb, 0x23, 0x5d, 0xaa, 0x60, 0xab, 0xa8, + 0x97, 0x93, 0xec, 0xd3, 0x22, 0x53, 0x13, 0xe9, 0xa2, 0x38, 0x05, 0x17, + 0x41, 0x02, 0x8a, 0xa3, 0x17, 0x11, 0x3a, 0x06, 0xd4, 0xb2, 0x4b, 0xb6, + 0x51, 0x9d, 0xaa, 0x46, 0xc3, 0xac, 0x5b, 0x13, 0x23, 0x86, 0xc5, 0x76, + 0x76, 0x59, 0xb7, 0xbc, 0x84, 0x05, 0xec, 0xd5, 0xc0, 0xeb, 0x3b, 0xc3, + 0xbe, 0x8a, 0x45, 0xcf, 0x97, 0x2e, 0xb0, 0x86, 0x61, 0xdc, 0x2f, 0x65, + 0x96, 0xec, 0x4d, 0x54, 0x3f, 0x75, 0x08, 0xf8, 0x0e, 0x1d, 0xed, 0x3e, + 0x5e, 0x9e, 0xe1, 0xd4, 0x75, 0xbc, 0xc5, 0x95, 0xc0, 0xd7, 0x89, 0x9d, + 0xad, 0xe4, 0xf2, 0xbe, 0xee, 0xf1, 0x1b, 0x60, 0xf0, 0x18, 0x30, 0xf0, + 0x84, 0x1b, 0xa6, 0xaa, 0x2f, 0x69, 0x9a, 0x14, 0x71, 0xa3, 0x54, 0xf1, + 0x20, 0x61, 0x48, 0x5a, 0x22, 0x4b, 0x12, 0x15, 0xfc, 0x0a, 0xd6, 0xaf, + 0x9e, 0x23, 0x84, 0x24, 0x17, 0x15, 0xd5, 0xf8, 0x99, 0x4d, 0x54, 0x31, + 0xf6, 0xe0, 0xf5, 0xfd, 0xea, 0xfe, 0x3b, 0x77, 0x98, 0x61, 0xbd, 0xdf, + 0x67, 0x0c, 0xae, 0xa1, 0x45, 0xa6, 0xf8, 0xb2, 0xf7, 0x43, 0xed, 0x40, + 0x99, 0x30, 0x83, 0xba, 0xcd, 0x8f, 0x0c, 0x9f, 0x9f, 0x27, 0xe1, 0xda, + 0x3a, 0xbe, 0x05, 0xe1, 0x10, 0x5e, 0x46, 0x7c, 0xdd, 0x3e, 0x66, 0xda, + 0x25, 0x2b, 0x3d, 0xc0, 0xe2, 0x14, 0xc1, 0x9e, 0x51, 0xf7, 0xc6, 0xe0, + 0x6e, 0x97, 0xc5, 0x54, 0xba, 0x63, 0xcf, 0x62, 0x75, 0xaf, 0xe6, 0xc5, + 0x94, 0x41, 0x99, 0xcd, 0x3a, 0x1c, 0x28, 0x59, 0x34, 0x63, 0xcb, 0x31, + 0xd2, 0x0e, 0x8d, 0xf9, 0xaf, 0x25, 0xc6, 0x62, 0xfa, 0x24, 0x14, 0x85, + 0xda, 0xd1, 0x4e, 0xa2, 0x39, 0xcd, 0x0a, 0x43, 0x26, 0x09, 0x6a, 0xfd, + 0x17, 0xb0, 0x3f, 0x3d, 0x3c, 0x08, 0x15, 0xcc, 0x9b, 0xe8, 0x0d, 0x57, + 0xa0, 0x73, 0x81, 0x5d, 0x79, 0xb4, 0x0a, 0x48, 0x41, 0xa1, 0xf4, 0x03, + 0x65, 0x3b, 0xb0, 0xd4, 0x85, 0xc8, 0x57, 0xb1, 0x5f, 0xf4, 0xfe, 0x40, + 0x19, 0xaa, 0x86, 0x79, 0x9f, 0x0e, 0x70, 0x7b, 0xbf, 0xbc, 0xf8, 0x01, + 0xe5, 0xf5, 0x11, 0xca, 0x76, 0x60, 0x41, 0x7e, 0x15, 0x98, 0x88, 0x02, + 0x0a, 0xc8, 0xa0, 0xf7, 0x5d, 0xd0, 0xb5, 0xb6, 0xe9, 0xe9, 0x42, 0x94, + 0x00, 0xba, 0x2c, 0x0b, 0x7f, 0xea, 0xf7, 0xf5, 0xe7, 0x7c, 0xe2, 0x3e, + 0x3f, 0x3f, 0x2e, 0xbe, 0x9f, 0x0c, 0x7a, 0x25, 0x5c, 0x4d, 0x71, 0x4d, + 0x78, 0x40, 0x18, 0x07, 0x28, 0x44, 0xfc, 0x6e, 0x9c, 0xc7, 0x04, 0x47, + 0x42, 0x15, 0x22, 0xf9, 0xb9, 0x12, 0xec, 0xc5, 0xd0, 0x71, 0x89, 0xc0, + 0x0d, 0x7a, 0xa0, 0x31, 0x33, 0x01, 0x85, 0x6b, 0x70, 0x5f, 0x1a, 0x95, + 0x76, 0x13, 0x20, 0x07, 0xa3, 0x69, 0x45, 0x08, 0x3d, 0xf5, 0x0b, 0xa1, + 0x1e, 0x78, 0x9d, 0xbe, 0x8b, 0x5c, 0x0c, 0x55, 0xbc, 0x67, 0xf8, 0xae, + 0xaf, 0x87, 0x5f, 0x1a, 0x12, 0x48, 0x18, 0x2c, 0x36, 0xfb, 0x33, 0x10, + 0x80, 0x30, 0x6c, 0x24, 0xdb, 0x35, 0xe1, 0x2f, 0x28, 0x9b, 0xa5, 0x6e, + 0x36, 0x27, 0xd2, 0x70, 0x6e, 0xdc, 0xec, 0x4a, 0x6c, 0xac, 0xf4, 0x8e, + 0xb1, 0x49, 0x64, 0x15, 0xc7, 0x9f, 0x9d, 0xc9, 0x08, 0x0b, 0x28, 0x18, + 0xaf, 0x2d, 0xb8, 0x0d, 0x50, 0x3f, 0x0c, 0x3d, 0x90, 0x8e, 0x5b, 0xea, + 0xa2, 0xef, 0x38, 0x68, 0xcd, 0x10, 0xda, 0xec, 0x29, 0xef, 0xd6, 0xa0, + 0xe8, 0x32, 0xb2, 0x14, 0xeb, 0xf5, 0x8c, 0x5e, 0x48, 0x76, 0xbc, 0x11, + 0x9d, 0xc3, 0x75, 0xc2, 0x06, 0xfa, 0xf6, 0xd0, 0xd3, 0x80, 0x82, 0x12, + 0x2a, 0xb2, 0xe0, 0xa0, 0x79, 0xf2, 0x63, 0x9b, 0x55, 0x60, 0x9a, 0x52, + 0xa9, 0xc9, 0xbf, 0xf2, 0xaa, 0x7a, 0x63, 0x3a, 0xc5, 0x29, 0x4d, 0x28, + 0xd1, 0x44, 0x5b, 0x8b, 0x94, 0x76, 0x50, 0xc5, 0xcc, 0x40, 0x74, 0x5b, + 0x77, 0x95, 0xeb, 0x99, 0x2e, 0xe0, 0xcd, 0xda, 0xe0, 0xcb, 0xba, 0xa0, + 0x93, 0x43, 0x66, 0xe4, 0xe7, 0x34, 0xa2, 0x0a, 0xa8, 0x0c, 0xf5, 0x58, + 0x3b, 0x60, 0x2e, 0x2a, 0xf8, 0xf0, 0xe5, 0x1f, 0x93, 0x33, 0x7b, 0x30, + 0xa3, 0xf0, 0xa0, 0x1d, 0x58, 0x22, 0xa0, 0xa5, 0x1b, 0x1a, 0x53, 0x72, + 0xb3, 0x10, 0xf6, 0xb6, 0xb5, 0x27, 0x25, 0xa0, 0x09, 0x23, 0x71, 0x81, + 0x6b, 0x4a, 0x9a, 0x04, 0xdb, 0x3a, 0x9f, 0xe4, 0xb1, 0x54, 0xb2, 0xff, + 0xa6, 0xaa, 0xd3, 0x4b, 0xcc, 0xd5, 0xcf, 0xa3, 0x54, 0x1b, 0x43, 0xe6, + 0x5d, 0x17, 0x7b, 0x57, 0x5f, 0xe7, 0xa3, 0x21, 0x71, 0xa1, 0xd4, 0xa9, + 0x61, 0xdc, 0xb5, 0xde, 0x4e, 0x13, 0xe2, 0x8f, 0x9f, 0xd8, 0x78, 0x32, + 0x36, 0x23, 0x0f, 0x57, 0x44, 0x59, 0xd8, 0xf4, 0x2f, 0x17, 0x82, 0x2f, + 0xca, 0x17, 0x55, 0x95, 0x8a, 0x01, 0x95, 0x1d, 0x8c, 0x97, 0x47, 0x49, + 0x22, 0x7f, 0x8c, 0xd8, 0x75, 0xbc, 0x9f, 0x52, 0x9f, 0xb9, 0xcf, 0xcb, + 0x89, 0x38, 0x63, 0xb8, 0x41, 0x9f, 0xc4, 0xb6, 0x66, 0x79, 0x51, 0x6f, + 0x6b, 0x1d, 0xd8, 0x46, 0x0c, 0x15, 0x4c, 0x28, 0x73, 0xb9, 0xa9, 0x2a, + 0x93, 0x46, 0xae, 0x2f, 0x12, 0x4c, 0xb2, 0x4f, 0x01, 0x57, 0xc9, 0x80, + 0x05, 0x01, 0xec, 0xe5, 0x39, 0x4c, 0xdc, 0x56, 0x35, 0x17, 0x54, 0x18, + 0x94, 0x2c, 0x6b, 0xb5, 0x1c, 0x3c, 0x5a, 0xef, 0x35, 0x0a, 0x26, 0x09, + 0xa4, 0x5d, 0x97, 0x32, 0x34, 0x96, 0xd2, 0xbd, 0x8d, 0x41, 0xcb, 0xb5, + 0x55, 0xb4, 0xd0, 0x42, 0x29, 0x33, 0x34, 0x47, 0xe4, 0xec, 0x3a, 0xf1, + 0xcd, 0x03, 0xce, 0xc6, 0x88, 0xab, 0x52, 0x7e, 0x65, 0x01, 0xb3, 0x61, + 0x96, 0xfc, 0xc5, 0x6d, 0x84, 0x50, 0x38, 0xdd, 0x70, 0xf3, 0x0b, 0xf1, + 0x18, 0x83, 0xed, 0x99, 0x14, 0x6c, 0x16, 0xe0, 0xaa, 0x17, 0xc5, 0xe1, + 0xf2, 0xbb, 0x45, 0x7c, 0xb2, 0xa3, 0x92, 0x6a, 0xd4, 0x5f, 0xee, 0x52, + 0x41, 0xd7, 0xae, 0xdc, 0x39, 0x1f, 0xeb, 0x57, 0xbc, 0x08, 0x8a, 0x40, + 0xf4, 0xdd, 0x75, 0x4a, 0x99, 0x90, 0x76, 0x1d, 0x3c, 0xec, 0x07, 0xd7, + 0x1d, 0xc1, 0x3b, 0x66, 0x01, 0xbc, 0x78, 0x4d, 0x74, 0xb1, 0x7a, 0x1d, + 0x39, 0x88, 0x71, 0xfd, 0xf5, 0x85, 0xf5, 0xa8, 0xbd, 0x8f, 0xb9, 0x5c, + 0xe2, 0x81, 0xee, 0x94, 0x63, 0x27, 0x7f, 0x24, 0x80, 0x17, 0xe8, 0x8f, + 0x43, 0x6f, 0x32, 0x63, 0x6e, 0x9e, 0xd4, 0x70, 0x1b, 0x33, 0x76, 0x25, + 0x1f, 0x0e, 0x35, 0x51, 0xb3, 0xfb, 0x34, 0x75, 0xaa, 0x20, 0xf6, 0x9f, + 0x94, 0xae, 0x3b, 0xf3, 0x9c, 0xcf, 0x9d, 0x04, 0xac, 0x25, 0xbd, 0xb0, + 0xc2, 0xe5, 0xb2, 0x9b, 0xd2, 0x39, 0xd5, 0x9b, 0x35, 0x3a, 0x93, 0xee, + 0x0e, 0xf7, 0xdf, 0x52, 0xf6, 0x73, 0xa4, 0x65, 0x02, 0x40, 0xce, 0xb7, + 0x86, 0x79, 0xd2, 0xe1, 0xad, 0x16, 0x6f, 0xf4, 0x56, 0xaf, 0xf0, 0x46, + 0x72, 0x74, 0x69, 0x71, 0x37, 0x6f, 0x12, 0x13, 0x7a, 0x07, 0x22, 0x90, + 0xf2, 0x8f, 0x6b, 0x65, 0xf2, 0x12, 0xc5, 0x5f, 0x0a, 0x06, 0x38, 0x7a, + 0xa5, 0xd2, 0x6c, 0xfc, 0x21, 0x7a, 0xe4, 0xc4, 0x99, 0x3b, 0x16, 0xaa, + 0xcd, 0x00, 0x21, 0x2e, 0xfb, 0x41, 0x0d, 0xa0, 0xb9, 0x58, 0x06, 0xae, + 0x18, 0x4e, 0x38, 0x46, 0xf8, 0x69, 0xc9, 0xbc, 0x2c, 0x16, 0x74, 0x39, + 0x38, 0x80, 0x14, 0x61, 0x12, 0x99, 0x90, 0x3a, 0x7b, 0x62, 0xdf, 0xeb, + 0x54, 0xbf, 0xbe, 0x71, 0xa9, 0xbf, 0x38, 0xcf, 0xd6, 0x53, 0x0d, 0x89, + 0x8a, 0x5e, 0x4f, 0x4f, 0x3f, 0x9e, 0x1d, 0x45, 0xb0, 0x6b, 0xbf, 0x0b, + 0xd2, 0x55, 0x0c, 0x47, 0x44, 0x7e, 0xbd, 0x63, 0xb2, 0xd1, 0x37, 0x3c, + 0x12, 0xfd, 0x7d, 0x87, 0xd2, 0xc4, 0xee, 0x35, 0xea, 0x74, 0x4a, 0x92, + 0xef, 0xcb, 0x4f, 0x6f, 0x33, 0xca, 0x11, 0x8b, 0x75, 0x51, 0x6e, 0x83, + 0xfd, 0xe3, 0x8e, 0xc2, 0xe4, 0xe3, 0x70, 0x5e, 0x8b, 0x86, 0x92, 0x5e, + 0xdb, 0xc6, 0x73, 0x6f, 0x08, 0x56, 0xeb, 0x3c, 0x8f, 0xf1, 0x99, 0xf1, + 0x54, 0x1f, 0x77, 0xb2, 0x84, 0xae, 0x6e, 0xcb, 0xc7, 0x9c, 0x08, 0xd2, + 0x20, 0x9d, 0x5a, 0x68, 0x4c, 0xd6, 0x6c, 0x0d, 0xb8, 0x73, 0x84, 0x94, + 0x66, 0x41, 0x75, 0xf5, 0xab, 0x2c, 0x95, 0x07, 0x54, 0xba, 0xd9, 0x32, + 0xd8, 0xbd, 0x29, 0x69, 0xd0, 0x58, 0xd9, 0x23, 0x97, 0x06, 0x44, 0x00, + 0xe8, 0xbf, 0xab, 0x6f, 0xe9, 0xc8, 0xb5, 0x99, 0x0b, 0xdd, 0x84, 0xf4, + 0xd5, 0x68, 0xe9, 0x43, 0xf3, 0x1e, 0x35, 0x7e, 0xf2, 0x97, 0x08, 0x48, + 0x7e, 0x33, 0x18, 0x16, 0x57, 0x4e, 0x05, 0x22, 0xed, 0xb8, 0x3f, 0x57, + 0xe0, 0x4e, 0x51, 0x2e, 0xe4, 0xf5, 0x4c, 0x0c, 0x31, 0xb5, 0x2e, 0xae, + 0x2f, 0x37, 0x66, 0x31, 0x2e, 0xb3, 0x92, 0x02, 0xb2, 0x53, 0xb7, 0x49, + 0x4a, 0xbc, 0x24, 0x7c, 0xa2, 0x7e, 0x6c, 0xda, 0x5c, 0xb8, 0xb4, 0x17, + 0x75, 0xaf, 0xe4, 0x77, 0xb3, 0x58, 0x29, 0x33, 0xa4, 0x46, 0x82, 0x4f, + 0x07, 0x26, 0xd0, 0x03, 0xdc, 0x68, 0x13, 0x12, 0x12, 0x4b, 0x25, 0xb2, + 0x2f, 0xc1, 0xa6, 0xbf, 0xe3, 0x5b, 0x0f, 0x86, 0x33, 0x91, 0x64, 0x3c, + 0x95, 0x49, 0x58, 0x46, 0xfb, 0xe6, 0xd7, 0x21, 0xa4, 0x9c, 0x19, 0x9a, + 0x2a, 0x54, 0xdc, 0xb0, 0x2b, 0x59, 0xf9, 0x8a, 0x8a, 0x07, 0xf5, 0x8e, + 0x92, 0x21, 0xf5, 0xc5, 0xc1, 0xa0, 0x82, 0xf4, 0x57, 0xdd, 0xfd, 0x37, + 0xb1, 0xdd, 0xc2, 0xed, 0x48, 0x7f, 0x0f, 0x6c, 0x7f, 0xff, 0xed, 0x90, + 0x6e, 0x2a, 0xc8, 0x7b, 0x05, 0x3d, 0xeb, 0xcc, 0x47, 0xe2, 0xf5, 0x53, + 0xd1, 0xd4, 0x2c, 0x0f, 0xc1, 0xd0, 0x9a, 0xfd, 0x56, 0x3d, 0xf0, 0x40, + 0xab, 0x99, 0xf1, 0x57, 0xd8, 0x8a, 0xdd, 0x66, 0xf0, 0xea, 0x63, 0xe1, + 0x13, 0x53, 0x27, 0x78, 0x0b, 0x70, 0x7f, 0x98, 0x1d, 0x53, 0xbd, 0xd1, + 0xd6, 0x3b, 0x9c, 0x1e, 0x97, 0x5f, 0x40, 0x1f, 0xbd, 0x5e, 0x7a, 0x7b, + 0x62, 0x9d, 0x68, 0x94, 0x89, 0x05, 0xd7, 0x62, 0xf1, 0xc3, 0x2a, 0x66, + 0xae, 0xf4, 0xaa, 0x8c, 0xd3, 0x9f, 0x61, 0xd4, 0xfb, 0x2c, 0xe3, 0xe5, + 0xe8, 0xa8, 0x98, 0x4d, 0xf4, 0x43, 0xeb, 0x44, 0xfe, 0xec, 0x56, 0x3a, + 0x5a, 0xa8, 0x9c, 0x7c, 0x25, 0x69, 0x57, 0xd2, 0x7e, 0x50, 0x84, 0x75, + 0x4d, 0x29, 0x2e, 0x23, 0x35, 0x1e, 0xfe, 0x89, 0xc9, 0x3c, 0xef, 0xdd, + 0xa9, 0x3b, 0xf4, 0x90, 0x2e, 0xa5, 0xd3, 0xff, 0x1a, 0x84, 0x1a, 0x73, + 0x1c, 0x3c, 0x54, 0x83, 0x71, 0xb5, 0x1b, 0x23, 0xb6, 0x72, 0x4a, 0x39, + 0x71, 0xb9, 0x30, 0x64, 0x0c, 0x26, 0x91, 0x81, 0x41, 0x33, 0x03, 0x75, + 0x16, 0x3c, 0x58, 0xa8, 0x28, 0x8f, 0x63, 0x37, 0xb2, 0x2e, 0x62, 0x3e, + 0x2d, 0x26, 0x45, 0x3e, 0x73, 0x59, 0xb8, 0x7d, 0x2d, 0xf8, 0x1e, 0xbf, + 0x04, 0xd0, 0x81, 0xe1, 0x86, 0x14, 0xc4, 0xcf, 0xd8, 0xde, 0xc9, 0xc3, + 0x04, 0xcf, 0xc5, 0x43, 0x89, 0x79, 0x05, 0xea, 0x55, 0x80, 0x0e, 0xc0, + 0xf0, 0xed, 0xc7, 0x41, 0x51, 0xfd, 0x7c, 0x17, 0x18, 0x6d, 0x69, 0x56, + 0x6d, 0xc6, 0x6f, 0xe8, 0xd8, 0xd6, 0xe0, 0x62, 0x27, 0x55, 0x56, 0x13, + 0xaf, 0xf4, 0xa1, 0x2c, 0xf9, 0xfd, 0x95, 0xa8, 0x31, 0xee, 0xae, 0x69, + 0x85, 0xdb, 0x03, 0x31, 0xe0, 0x0c, 0xc3, 0x9b, 0xa4, 0x6e, 0x7d, 0xc2, + 0x13, 0xe6, 0x9c, 0x10, 0x4a, 0x04, 0xa2, 0x6f, 0xaa, 0xd9, 0x62, 0xca, + 0xb7, 0xef, 0xf5, 0xd5, 0xfd, 0x11, 0xc6, 0x87, 0x9e, 0x51, 0x08, 0xe2, + 0x12, 0xc5, 0xe1, 0x5e, 0x2b, 0x5c, 0x17, 0xc1, 0xf3, 0x1f, 0x7e, 0x02, + 0x1c, 0x5b, 0x64, 0x47, 0xb8, 0xa0, 0xf9, 0x33, 0xed, 0x4f, 0x07, 0x07, + 0x6b, 0xc1, 0xba, 0x36, 0x77, 0xf7, 0xa1, 0xcb, 0x2c, 0x85, 0x9a, 0x60, + 0x5b, 0x9d, 0xfa, 0x09, 0xd9, 0x92, 0x24, 0x38, 0x20, 0x34, 0x26, 0xf1, + 0x2f, 0x9b, 0xc2, 0xd9, 0x7a, 0x70, 0xca, 0xc6, 0x4a, 0xd7, 0x41, 0x5a, + 0x5f, 0x54, 0x2f, 0x65, 0x58, 0xf8, 0xc0, 0x7a, 0x77, 0x08, 0xee, 0x15, + 0xdc, 0x1a, 0x00, 0xef, 0xdd, 0x62, 0xaf, 0x4a, 0x64, 0x7b, 0xaa, 0x16, + 0x4e, 0xd8, 0x4b, 0x22, 0x2c, 0x6b, 0x61, 0xbc, 0xcf, 0xab, 0x48, 0x75, + 0x52, 0xa8, 0xd8, 0x6a, 0xbd, 0xb0, 0x32, 0x86, 0xdc, 0x66, 0x3b, 0x0e, + 0x1a, 0xff, 0x5d, 0x84, 0x66, 0xf0, 0x98, 0x06, 0x68, 0xf0, 0xd2, 0x6f, + 0x0f, 0x07, 0x76, 0x22, 0xa7, 0xfc, 0xfe, 0x8a, 0x85, 0xea, 0xe4, 0x38, + 0xfb, 0x37, 0x28, 0x9a, 0xa9, 0x57, 0xef, 0xe5, 0x00, 0x8c, 0x46, 0xcd, + 0x95, 0xac, 0x46, 0xa3, 0x5c, 0xdb, 0xc0, 0x40, 0x9b, 0x66, 0x62, 0x18, + 0xaf, 0xfe, 0x51, 0x08, 0x0f, 0xd5, 0x92, 0x2e, 0x52, 0x7b, 0xe5, 0xbf, + 0x34, 0x2b, 0x7a, 0x13, 0xe2, 0x7d, 0xaa, 0xff, 0x21, 0xae, 0x2e, 0x6d, + 0x65, 0x33, 0x89, 0xd7, 0x29, 0xa8, 0x4f, 0x9c, 0xc9, 0x6e, 0x67, 0xed, + 0x9c, 0xc8, 0xa6, 0xa7, 0x19, 0x36, 0x29, 0xe2, 0x47, 0x9c, 0xef, 0xda, + 0xd4, 0x3a, 0x07, 0x2b, 0xcd, 0xe8, 0xba, 0xb6, 0x53, 0x19, 0x47, 0x61, + 0x8f, 0x73, 0x5a, 0x94, 0x8d, 0x68, 0x9e, 0x87, 0xec, 0x54, 0x48, 0xa4, + 0x51, 0x57, 0xea, 0x7a, 0x56, 0x66, 0x62, 0x7c, 0x62, 0xc4, 0x8e, 0x84, + 0x34, 0xf8, 0xda, 0xab, 0xe8, 0x89, 0x43, 0x6f, 0x8f, 0x63, 0x63, 0x60, + 0x9b, 0xd0, 0xcc, 0x73, 0xfc, 0x38, 0x32, 0x40, 0xef, 0xea, 0x69, 0x62, + 0x2b, 0x1e, 0x91, 0xd1, 0x2a, 0xed, 0x55, 0xd7, 0x7e, 0x3c, 0x25, 0xf4, + 0x71, 0xe8, 0x28, 0x6c, 0xe0, 0x9e, 0x78, 0xd9, 0x03, 0x15, 0x76, 0xbf, + 0x02, 0xe9, 0x82, 0x34, 0x90, 0xcf, 0x1e, 0xdb, 0xb1, 0xd4, 0xc8, 0xfc, + 0x06, 0x83, 0x6f, 0x41, 0xa9, 0xe9, 0x87, 0x4e, 0x12, 0x3e, 0x14, 0x6e, + 0x70, 0x7f, 0xb3, 0x90, 0xa1, 0xd7, 0x6f, 0x6b, 0x20, 0xe9, 0x6b, 0x8a, + 0x0c, 0x5d, 0x96, 0x6e, 0xff, 0xdb, 0xe3, 0x69, 0xfd, 0x50, 0xaf, 0x9e, + 0x0f, 0x8e, 0x25, 0x31, 0x5d, 0xb5, 0x04, 0xce, 0x94, 0xbc, 0x3a, 0x16, + 0x74, 0x9a, 0xb5, 0x4d, 0x94, 0xb1, 0x32, 0x3d, 0x59, 0x35, 0xcd, 0x42, + 0x0c, 0xe6, 0x76, 0x0c, 0x3a, 0xc2, 0x53, 0x33, 0xfc, 0x77, 0xca, 0xa8, + 0x3e, 0x25, 0x21, 0xc8, 0x73, 0x54, 0x5b, 0x36, 0xd3, 0x79, 0xbf, 0x9f, + 0xaa, 0x73, 0x43, 0x70, 0xd7, 0xce, 0xd6, 0x2e, 0xe0, 0x31, 0x8a, 0x84, + 0x59, 0x03, 0xc6, 0xa7, 0x92, 0x55, 0x5c, 0x2f, 0x8e, 0x93, 0x54, 0x5e, + 0x60, 0x4e, 0x17, 0x1c, 0x7d, 0x50, 0x7d, 0x2a, 0x2b, 0x0c, 0xcb, 0x09, + 0xcf, 0x80, 0xfd, 0x2d, 0x52, 0xa4, 0x55, 0xa7, 0x83, 0x9f, 0x1f, 0x5c, + 0xad, 0xc3, 0xe2, 0x16, 0x2f, 0xaa, 0xbb, 0x99, 0x60, 0x70, 0x60, 0xaa, + 0x06, 0x76, 0x48, 0xa0, 0x29, 0x53, 0x3e, 0x9a, 0x5e, 0xd7, 0xa9, 0xeb, + 0xf6, 0xc0, 0xb5, 0x0f, 0xae, 0x77, 0x76, 0xaa, 0x20, 0x06, 0x3c, 0x14, + 0x94, 0x4a, 0x5e, 0xe8, 0x4b, 0xf7, 0xfb, 0x9c, 0x00, 0xf8, 0xf4, 0x1a, + 0xba, 0x22, 0x09, 0x5b, 0x6a, 0xea, 0x00, 0x35, 0x28, 0x1c, 0xf7, 0xec, + 0x90, 0x62, 0x37, 0x78, 0x1f, 0xee, 0x7b, 0x77, 0x7f, 0x8a, 0x3d, 0xc3, + 0x2b, 0x66, 0x75, 0x02, 0xa4, 0x69, 0xd1, 0x75, 0x92, 0xe8, 0xb7, 0xd8, + 0xfb, 0xaf, 0x8a, 0xc3, 0xec, 0xe9, 0x2e, 0x0f, 0x92, 0x68, 0x6b, 0x07, + 0x0e, 0x79, 0x5d, 0x4e, 0xd8, 0x81, 0xc3, 0xd0, 0x39, 0xbb, 0x4c, 0x66, + 0xbd, 0x8a, 0x45, 0x9c, 0x90, 0x1e, 0x42, 0xa4, 0x28, 0xae, 0x6b, 0x92, + 0x63, 0x1f, 0x90, 0x2c, 0xdf, 0x57, 0x6d, 0x8d, 0x8b, 0x66, 0x13, 0x50, + 0x45, 0x71, 0xdf, 0xb7, 0x0e, 0xc2, 0x67, 0x9c, 0x11, 0xfa, 0x2e, 0x80, + 0xc0, 0xa2, 0x52, 0x5d, 0x3a, 0x89, 0xf1, 0xe6, 0x62, 0x0b, 0x17, 0x97, + 0x9e, 0xf1, 0xeb, 0x70, 0x49, 0x54, 0x4d, 0xb5, 0x1e, 0x28, 0x78, 0x9f, + 0x51, 0x83, 0x03, 0xc0, 0xbe, 0x99, 0xd1, 0xdc, 0x05, 0xa9, 0x75, 0xdd, + 0x2c, 0x67, 0xfc, 0xa8, 0x4d, 0x30, 0x01, 0x6a, 0x80, 0x32, 0x88, 0x55, + 0x5d, 0x25, 0x5a, 0x9e, 0x9c, 0x66, 0xbd, 0x3a, 0x78, 0xbd, 0x93, 0x23, + 0xf5, 0x84, 0x89, 0x37, 0x2c, 0x74, 0xae, 0x90, 0x22, 0x88, 0xcb, 0xc6, + 0xf5, 0x8b, 0xef, 0x61, 0xf3, 0xc4, 0xfe, 0x07, 0x4e, 0x35, 0x59, 0x8e, + 0xee, 0x4e, 0xa9, 0x0b, 0x4f, 0xe0, 0xe2, 0x5b, 0x57, 0x73, 0xab, 0x53, + 0x69, 0x4f, 0x5f, 0xb9, 0xa3, 0x6d, 0xc1, 0xae, 0xe8, 0xaf, 0xf4, 0x31, + 0xa5, 0x67, 0xc6, 0xae, 0xbd, 0x0c, 0x1b, 0x08, 0x6e, 0xcc, 0xb8, 0xcf, + 0x8d, 0xc3, 0xa8, 0x02, 0xc1, 0x95, 0x1e, 0x87, 0x0e, 0x7d, 0xa6, 0x19, + 0x21, 0x39, 0xd6, 0x64, 0x95, 0xe6, 0xd2, 0xf0, 0x40, 0xb5, 0x8e, 0x5d, + 0xa3, 0xfa, 0x30, 0x13, 0x8e, 0x92, 0x54, 0x7d, 0xa3, 0x5a, 0x38, 0xfe, + 0x29, 0xd2, 0xef, 0x1f, 0xb5, 0x7d, 0x45, 0x38, 0xf3, 0x3a, 0x53, 0xa2, + 0xd2, 0x3c, 0xc8, 0x9c, 0xad, 0xbd, 0x35, 0x1d, 0x33, 0x1f, 0x66, 0x9c, + 0xe7, 0x11, 0x53, 0x7e, 0x08, 0xe0, 0x74, 0x8f, 0x19, 0x42, 0x09, 0x22, + 0x32, 0xb4, 0x8c, 0x53, 0xe9, 0xef, 0x9a, 0xfc, 0x49, 0x88, 0xae, 0x6b, + 0x1a, 0x3b, 0x30, 0x94, 0x0d, 0xd4, 0xbe, 0xff, 0x6d, 0x74, 0xdf, 0x23, + 0xb6, 0xc9, 0x7a, 0x40, 0xdd, 0x16, 0xfe, 0xab, 0x9e, 0xfc, 0xcb, 0xab, + 0xc6, 0x2e, 0x9a, 0x68, 0x86, 0x9b, 0x98, 0xaa, 0xcd, 0x98, 0xfd, 0x30, + 0xf6, 0x68, 0x7c, 0x90, 0xf4, 0x22, 0xfd, 0x39, 0x75, 0xcc, 0x03, 0x19, + 0x26, 0xf3, 0xb5, 0x7a, 0x19, 0x96, 0x6f, 0xed, 0x20, 0xff, 0x54, 0xd1, + 0x35, 0x49, 0x96, 0x57, 0x57, 0xdd, 0xc5, 0x51, 0xf2, 0x53, 0x9a, 0xd5, + 0x14, 0xfa, 0xe6, 0x31, 0xa2, 0xa4, 0x41, 0x69, 0x4c, 0x86, 0xa5, 0xcb, + 0x72, 0x75, 0x2d, 0x5a, 0x60, 0x0e, 0xa0, 0x1e, 0xcf, 0xcc, 0xb1, 0x5c, + 0x8a, 0xdc, 0x54, 0x0c, 0x19, 0xf0, 0xa6, 0x3f, 0x38, 0xdd, 0xdc, 0xf5, + 0xff, 0xec, 0xf6, 0xa0, 0x4a, 0x93, 0xb6, 0xbb, 0x9b, 0x90, 0xd2, 0xcb, + 0x3d, 0xb4, 0xa9, 0x03, 0xe1, 0x77, 0xa7, 0x55, 0xb5, 0xd1, 0xa3, 0xc1, + 0xb0, 0xe0, 0x7c, 0xf7, 0x25, 0x0b, 0x8a, 0x79, 0x3d, 0x0d, 0xaf, 0xf1, + 0x9b, 0xd2, 0x88, 0xcd, 0x9d, 0xfd, 0xc8, 0x2d, 0x38, 0x0a, 0xf7, 0xe1, + 0xbc, 0x4d, 0x9e, 0xa0, 0x2f, 0x19, 0x81, 0x3e, 0xdf, 0x12, 0x92, 0x2f, + 0xcb, 0x39, 0x76, 0xe0, 0xbe, 0x11, 0x60, 0x2b, 0x8a, 0xbb, 0xfa, 0xc2, + 0x61, 0x4c, 0x6d, 0xb7, 0x6a, 0x5d, 0xdb, 0xff, 0x13, 0x27, 0x1e, 0xa8, + 0xad, 0xf3, 0x3c, 0xa0, 0x86, 0x8f, 0x25, 0x58, 0x25, 0x50, 0x05, 0xa8, + 0x54, 0xd4, 0x8a, 0x8a, 0x64, 0x5e, 0x02, 0x4c, 0x60, 0x79, 0x1e, 0x43, + 0x51, 0x7a, 0x88, 0x4d, 0xef, 0x6e, 0x70, 0xcf, 0xc2, 0xe4, 0x06, 0xde, + 0x86, 0x7a, 0xe5, 0x77, 0x1a, 0x49, 0xdd, 0x7a, 0xc0, 0x1c, 0x34, 0x41, + 0x0d, 0x6e, 0x84, 0xec, 0xaf, 0xd8, 0xb9, 0x2c, 0x6c, 0xde, 0xef, 0x4a, + 0x09, 0xa9, 0x8b, 0x21, 0x3d, 0x4c, 0x3d, 0xba, 0x29, 0x42, 0xb9, 0x0c, + 0x84, 0x75, 0x1a, 0xad, 0xc8, 0x12, 0x02, 0xe1, 0x02, 0x41, 0x03, 0x04, + 0xe3, 0x6f, 0xb9, 0x27, 0xd7, 0x99, 0x30, 0xfc, 0xf6, 0xf6, 0x9e, 0x96, + 0x6a, 0x48, 0x55, 0x93, 0xd3, 0xd9, 0x88, 0xbe, 0x35, 0x19, 0xaa, 0xa1, + 0x08, 0x24, 0x5c, 0xfa, 0xfe, 0x0f, 0xa0, 0xa4, 0xfd, 0xc7, 0x9e, 0xd1, + 0xa4, 0x9d, 0x2c, 0xa6, 0xf1, 0xe9, 0xe2, 0x49, 0xa2, 0x4f, 0x1d, 0x71, + 0x12, 0x0f, 0xd0, 0x9d, 0x81, 0x75, 0x7e, 0x92, 0xa5, 0x87, 0x76, 0x70, + 0x5c, 0x35, 0x77, 0xf6, 0x13, 0xe5, 0xd6, 0x70, 0xab, 0x88, 0xe6, 0xb6, + 0xa3, 0x5f, 0x3b, 0x45, 0x5f, 0x69, 0xbc, 0x1e, 0xc7, 0x77, 0x13, 0xc6, + 0xfd, 0x2a, 0xa3, 0xab, 0x97, 0xec, 0x10, 0xdb, 0x30, 0x77, 0x80, 0xa0, + 0x88, 0x4b, 0x2e, 0x94, 0xac, 0x3b, 0x6d, 0xe1, 0xb3, 0xbc, 0x03, 0xb0, + 0xc7, 0x40, 0x03, 0xd2, 0x6e, 0xd2, 0xa9, 0x96, 0x52, 0xc7, 0x1b, 0x66, + 0x43, 0x16, 0x6a, 0x8e, 0x53, 0x8c, 0x0c, 0xe5, 0x8f, 0xeb, 0x5a, 0x91, + 0x03, 0x8e, 0x5a, 0x42, 0x6a, 0x1f, 0xa7, 0x7d, 0x7c, 0x8d, 0xde, 0x7d, + 0x24, 0xb0, 0x56, 0xb5, 0x9f, 0xc8, 0x23, 0x98, 0x15, 0x07, 0x4e, 0xf5, + 0x47, 0x91, 0xed, 0x51, 0x87, 0x87, 0x96, 0x74, 0x3b, 0x60, 0x78, 0xc8, + 0x72, 0x49, 0xd6, 0x52, 0xe8, 0x2d, 0xd7, 0x2e, 0xfd, 0x78, 0x78, 0xd0, + 0xd6, 0x60, 0x4d, 0xd2, 0xd7, 0xb5, 0x95, 0xc6, 0x4e, 0xab, 0x95, 0x5d, + 0xba, 0x1d, 0x96, 0xc1, 0x37, 0x53, 0xaf, 0xa5, 0x43, 0x5f, 0x16, 0xa6, + 0xc4, 0xda, 0x63, 0x4d, 0x87, 0x2e, 0x13, 0x22, 0xad, 0xf2, 0x45, 0x8a, + 0x1c, 0xb5, 0x90, 0x28, 0x01, 0x6b, 0x5d, 0x9f, 0x63, 0x17, 0x4f, 0x70, + 0xe7, 0xa5, 0xdb, 0x6e, 0x42, 0x65, 0x87, 0xfe, 0xa7, 0xf3, 0x79, 0x44, + 0x50, 0xad, 0x70, 0x01, 0x40, 0xd5, 0x32, 0x79, 0x26, 0x43, 0x23, 0x6f, + 0x59, 0x0d, 0x0a, 0xd4, 0xfe, 0x60, 0x5d, 0x25, 0x4e, 0xf3, 0xdc, 0xe4, + 0x43, 0xcd, 0xc8, 0x2b, 0x09, 0x3c, 0xcf, 0xc0, 0xaf, 0x7c, 0x93, 0x4e, + 0x52, 0xc0, 0xf5, 0xee, 0x6a, 0x1d, 0x6d, 0x78, 0x7e, 0x70, 0x9e, 0x7f, + 0x63, 0xa3, 0x75, 0x95, 0x1a, 0x2f, 0x03, 0xfb, 0x90, 0xa0, 0xe4, 0x8a, + 0x18, 0xf1, 0xd6, 0x5d, 0x79, 0xb7, 0x87, 0x5a, 0x86, 0x31, 0x5c, 0xd4, + 0x51, 0x22, 0x03, 0x11, 0x5a, 0x50, 0x71, 0x0d, 0xee, 0x1d, 0x0a, 0x71, + 0x8e, 0x5e, 0xba, 0x4e, 0x94, 0x5b, 0x78, 0xa8, 0x4c, 0xce, 0x4a, 0x59, + 0xd0, 0xb8, 0x2a, 0xa6, 0xd8, 0x53, 0xaf, 0x7a, 0x13, 0x76, 0x6e, 0xa5, + 0x44, 0xfc, 0x23, 0x45, 0x0f, 0x07, 0x53, 0x93, 0x1e, 0xf4, 0xac, 0x9c, + 0x7b, 0x85, 0xfe, 0x8f, 0x2b, 0x5c, 0x29, 0x1c, 0x01, 0x83, 0x3c, 0xc3, + 0xa1, 0x54, 0x30, 0x25, 0x7c, 0x1f, 0x11, 0xf9, 0xaa, 0x78, 0x85, 0x46, + 0xb4, 0x08, 0x9b, 0x93, 0x71, 0x80, 0x3b, 0x05, 0x08, 0xf0, 0xfd, 0x77, + 0x6a, 0xbf, 0x5e, 0xd1, 0x82, 0x33, 0xfc, 0xb9, 0x9d, 0x3d, 0x79, 0x7c, + 0x7a, 0x22, 0xde, 0x7d, 0x0c, 0xa7, 0x0e, 0x4f, 0x28, 0xac, 0x5c, 0xc6, + 0xe7, 0x53, 0xdc, 0x6d, 0x8c, 0x69, 0x70, 0x99, 0x21, 0x0c, 0xe4, 0x3a, + 0x68, 0x20, 0x94, 0xb9, 0x72, 0x40, 0xe5, 0x8f, 0x3e, 0xe4, 0xea, 0x3d, + 0xd8, 0xfb, 0x26, 0x2d, 0xf4, 0x3e, 0x83, 0x1a, 0x77, 0x74, 0xdc, 0x4e, + 0x14, 0xe1, 0xb2, 0xf6, 0xac, 0x3c, 0xca, 0x28, 0xac, 0xab, 0x99, 0xc8, + 0x4b, 0x0f, 0x74, 0xde, 0x86, 0xb4, 0xc7, 0xc7, 0x09, 0x6f, 0x7a, 0xff, + 0x2d, 0x09, 0xc4, 0x38, 0xba, 0xfe, 0xe1, 0xd0, 0x92, 0x33, 0xff, 0xbe, + 0xde, 0x4f, 0xf1, 0x35, 0x37, 0x0d, 0x3b, 0x1e, 0x06, 0x4a, 0xb9, 0x6a, + 0x18, 0xfc, 0x57, 0xd9, 0x3a, 0x6c, 0x87, 0xd2, 0x6a, 0x0d, 0x11, 0xf9, + 0xc8, 0xfa, 0x33, 0x8f, 0xee, 0x9b, 0x82, 0xbb, 0x33, 0xf9, 0x23, 0xf0, + 0xb9, 0xb4, 0x02, 0xb5, 0x25, 0x61, 0x73, 0x8e, 0xbb, 0x96, 0xc3, 0x87, + 0x34, 0x8b, 0x8a, 0x85, 0x1d, 0x86, 0xa9, 0x92, 0xec, 0x9f, 0x9b, 0x61, + 0xb7, 0x7d, 0xb2, 0x3e, 0xe0, 0x38, 0xbb, 0x46, 0xc8, 0x2f, 0x2a, 0xe4, + 0xfe, 0x07, 0x9c, 0x5a, 0x43, 0xd3, 0xb6, 0xce, 0xcc, 0x16, 0x62, 0x57, + 0xaf, 0x06, 0x0d, 0xec, 0xff, 0x53, 0xbc, 0xa8, 0x81, 0x59, 0x05, 0x05, + 0x77, 0x0a, 0x92, 0x6a, 0xb3, 0xa7, 0x9a, 0x02, 0x52, 0xb5, 0x3f, 0x23, + 0xc2, 0x73, 0xa7, 0x98, 0xf8, 0x8f, 0x4e, 0x3f, 0xcf, 0x0d, 0xcd, 0xda, + 0x2a, 0xdc, 0xff, 0x53, 0x7e, 0x96, 0x04, 0x75, 0x13, 0xc6, 0x8a, 0x92, + 0x30, 0x1c, 0x45, 0xb4, 0x17, 0x9f, 0x91, 0x84, 0xc9, 0xc1, 0xc3, 0xb0, + 0xed, 0x11, 0x70, 0x3a, 0xcf, 0xc0, 0xb0, 0xd0, 0x48, 0x10, 0x10, 0x2e, + 0x1e, 0x18, 0x45, 0x1c, 0x25, 0xad, 0xfc, 0x8c, 0xe9, 0xf1, 0xee, 0xf7, + 0xa3, 0xa1, 0x9d, 0x12, 0x8c, 0xa8, 0xb5, 0x3f, 0x17, 0x5c, 0x11, 0x2e, + 0xd5, 0x4e, 0x2e, 0xb3, 0x2e, 0xf4, 0x76, 0xc9, 0xb4, 0x86, 0xa9, 0xf5, + 0xb8, 0xd0, 0x59, 0xde, 0x51, 0x22, 0xdd, 0xc0, 0xec, 0x84, 0x01, 0xd1, + 0x3a, 0x9f, 0x35, 0x76, 0x7f, 0xd3, 0x26, 0x1d, 0x0c, 0xcc, 0x8d, 0x8c, + 0x9b, 0x89, 0x5f, 0xb8, 0xd8, 0xa2, 0xa8, 0x33, 0x23, 0x93, 0x92, 0x5d, + 0x69, 0x6a, 0x56, 0xf8, 0xb6, 0x17, 0x87, 0x1f, 0xcd, 0xc0, 0x87, 0xd2, + 0x30, 0x8a, 0xd5, 0x49, 0xb3, 0x2b, 0x1b, 0x3f, 0xe5, 0xf5, 0x43, 0xe5, + 0xe1, 0x87, 0xdb, 0x2a, 0x8e, 0xa3, 0xd3, 0x53, 0x93, 0x16, 0xdf, 0x47, + 0xb1, 0x8c, 0xdc, 0xb1, 0x45, 0xa2, 0x81, 0xdc, 0xa8, 0xc4, 0x2c, 0xe6, + 0x79, 0x13, 0x0b, 0x40, 0x9f, 0x49, 0xba, 0x83, 0x67, 0xf5, 0x69, 0x9e, + 0x7f, 0x18, 0x67, 0xc7, 0x0a, 0xbd, 0x44, 0xc9, 0xb5, 0x87, 0x7e, 0xe9, + 0x07, 0x11, 0xef, 0xb1, 0x9d, 0xb0, 0xf5, 0xf3, 0x1b, 0x23, 0xd1, 0xff, + 0xfa, 0x67, 0x71, 0xa3, 0x3d, 0x0e, 0x52, 0x86, 0x78, 0x34, 0x8c, 0x56, + 0x04, 0xb5, 0x2b, 0x54, 0x6b, 0x9f, 0xd2, 0x33, 0xe2, 0xb1, 0x53, 0x88, + 0xca, 0xd2, 0xf8, 0x07, 0xf8, 0x91, 0x89, 0x7a, 0xd9, 0xfe, 0xb4, 0x45, + 0xe7, 0x93, 0x42, 0xcd, 0x42, 0xcb, 0x3f, 0x35, 0xc3, 0xd6, 0xce, 0x90, + 0x06, 0xec, 0xd1, 0x99, 0xa8, 0xba, 0xef, 0x48, 0xc7, 0x08, 0x47, 0xd3, + 0xaa, 0xb1, 0xf9, 0xf9, 0xe0, 0x18, 0x12, 0x0a, 0x9a, 0x29, 0x32, 0x9a, + 0xcf, 0x58, 0x57, 0x35, 0xf4, 0x1a, 0x16, 0x48, 0x4b, 0x10, 0xaa, 0xbf, + 0x17, 0x62, 0xe4, 0xe5, 0xba, 0x1a, 0x45, 0x19, 0xb4, 0x72, 0xf9, 0x9e, + 0xe7, 0xda, 0x08, 0x13, 0xb7, 0x04, 0x19, 0x5e, 0x56, 0x4b, 0x0c, 0xe4, + 0xfc, 0xac, 0x9d, 0x4f, 0x4d, 0xf2, 0xe2, 0x1a, 0x6f, 0x9a, 0xc4, 0xb8, + 0xc3, 0x50, 0x05, 0xf1, 0x5c, 0x52, 0x4d, 0x37, 0x3c, 0x7a, 0xe5, 0xa1, + 0xdd, 0x7c, 0xe5, 0xc0, 0xcb, 0x0d, 0xe7, 0xa5, 0x43, 0xd5, 0xe6, 0xbb, + 0x38, 0x36, 0xec, 0x39, 0x9a, 0x17, 0x6a, 0x64, 0x1d, 0xe3, 0xe8, 0x6e, + 0xb5, 0x4d, 0x09, 0xd4, 0x3e, 0xe9, 0x67, 0x43, 0x41, 0xd9, 0x7e, 0x3e, + 0xa0, 0x27, 0x17, 0xb3, 0xb8, 0x4b, 0xfc, 0xe4, 0x2d, 0x28, 0x96, 0xf1, + 0x94, 0xf8, 0x52, 0x0e, 0x3e, 0x0a, 0x9f, 0x2e, 0x34, 0x89, 0x25, 0x6b, + 0xd6, 0xb0, 0x98, 0xd6, 0xb5, 0x19, 0x60, 0xc7, 0x67, 0x34, 0xff, 0xb9, + 0x6c, 0x3a, 0x05, 0xcf, 0xf0, 0x7d, 0x8a, 0xc7, 0xd1, 0xdc, 0x94, 0x6d, + 0x56, 0x66, 0x27, 0x49, 0xf8, 0xce, 0x46, 0x66, 0xdc, 0x37, 0x2d, 0x35, + 0x22, 0x32, 0xbe, 0xcb, 0xcd, 0x22, 0xd4, 0xfc, 0x30, 0xbb, 0xe9, 0x2b, + 0x05, 0x4c, 0xfa, 0x67, 0x08, 0x2d, 0x7d, 0x2e, 0x89, 0xbc, 0xc2, 0x12, + 0x9c, 0x1f, 0xb3, 0x11, 0x05, 0x19, 0xd6, 0xe8, 0x06, 0x08, 0xc5, 0x94, + 0x8c, 0x59, 0x86, 0x80, 0x8a, 0x98, 0xc5, 0x40, 0x67, 0x64, 0x5b, 0x16, + 0x9e, 0xa2, 0xbd, 0xc0, 0x1f, 0x85, 0x8e, 0xa5, 0xdf, 0x44, 0x7c, 0x1e, + 0xc0, 0x3c, 0xa7, 0xa6, 0xae, 0x94, 0x24, 0xc8, 0x2c, 0x97, 0xd0, 0xd5, + 0x5d, 0x22, 0x48, 0x14, 0xd8, 0x7d, 0x10, 0x4c, 0x7b, 0x90, 0x97, 0x53, + 0xde, 0x03, 0xbc, 0x0d, 0x96, 0x6e, 0xb9, 0x6a, 0xf6, 0xcd, 0xbf, 0xa2, + 0x60, 0x53, 0xce, 0x7f, 0x6f, 0x70, 0x71, 0x7d, 0x76, 0x41, 0x71, 0x7a, + 0x4f, 0x10, 0x30, 0xdf, 0xdd, 0x98, 0xbd, 0x98, 0x65, 0x2e, 0xa6, 0x41, + 0xad, 0x24, 0x64, 0x46, 0xab, 0xd4, 0xb5, 0xc9, 0xef, 0x8a, 0xf4, 0x90, + 0xbd, 0x25, 0x46, 0x09, 0xf8, 0x56, 0xb6, 0xe1, 0x6c, 0x70, 0x5e, 0xed, + 0x7d, 0xc1, 0x90, 0xe2, 0x49, 0x65, 0x82, 0x54, 0xe4, 0x32, 0x1c, 0x6c, + 0xe6, 0x47, 0x85, 0xda, 0xff, 0xd5, 0x16, 0x7f, 0x21, 0xad, 0x7d, 0x18, + 0xe8, 0x89, 0x78, 0xdf, 0x36, 0xfa, 0xd6, 0x5f, 0xef, 0x2c, 0x78, 0x41, + 0x16, 0xeb, 0xbe, 0x8f, 0x38, 0xa3, 0x09, 0x4b, 0xee, 0x70, 0xce, 0xcb, + 0x40, 0xcb, 0x3c, 0xe8, 0xe3, 0x9a, 0xae, 0x68, 0xb1, 0x28, 0x60, 0x28, + 0x4e, 0x90, 0x94, 0x58, 0xcf, 0x78, 0xe1, 0xa4, 0x62, 0xd0, 0xa9, 0x78, + 0x88, 0xf0, 0xb8, 0x1f, 0x89, 0xbd, 0x08, 0xb6, 0x91, 0xf9, 0xcd, 0xaa, + 0x63, 0x63, 0x05, 0x88, 0x6b, 0xb6, 0xf2, 0xea, 0x47, 0xe6, 0x4e, 0x99, + 0x2b, 0xe7, 0x53, 0x8d, 0xee, 0xcd, 0x15, 0xf3, 0x76, 0xab, 0x58, 0xc4, + 0x75, 0x99, 0x24, 0xfa, 0x8e, 0xb0, 0x48, 0x44, 0x72, 0x63, 0xfa, 0xf6, + 0xa6, 0xca, 0xca, 0x9d, 0x3c, 0x92, 0x38, 0xfd, 0xbb, 0x37, 0xa4, 0x8f, + 0xee, 0x2b, 0x33, 0x87, 0x51, 0x76, 0x94, 0xda, 0xa8, 0x03, 0xa4, 0x3e, + 0xce, 0x2e, 0x8d, 0x25, 0x23, 0xcb, 0x4e, 0x9c, 0xd1, 0x72, 0xe7, 0x33, + 0xa0, 0x47, 0x8e, 0x3f, 0x0c, 0x34, 0x0e, 0x4a, 0xbf, 0x72, 0xab, 0xfe, + 0x53, 0xcb, 0x90, 0x7d, 0x8a, 0x0d, 0xc1, 0xeb, 0x57, 0x49, 0xc0, 0x3a, + 0x83, 0x39, 0xfd, 0xf1, 0x25, 0xc1, 0x97, 0x9d, 0x3a, 0x72, 0x64, 0x37, + 0x59, 0x20, 0x79, 0xfa, 0x4f, 0xb0, 0x77, 0x9c, 0x86, 0x78, 0x6a, 0x54, + 0xc5, 0xed, 0xcd, 0xed, 0x6f, 0xb0, 0x7c, 0x1e, 0x6a, 0x41, 0x35, 0x6b, + 0x90, 0x2b, 0x22, 0x46, 0x46, 0x07, 0xbd, 0x28, 0xed, 0x20, 0xa8, 0xb3, + 0xaa, 0x6f, 0x32, 0xb6, 0x4b, 0x39, 0xed, 0x55, 0x1a, 0xb8, 0xfd, 0x1c, + 0x7f, 0xfe, 0x29, 0x5a, 0x4b, 0xa2, 0x90, 0xde, 0x2a, 0x3c, 0x00, 0x81, + 0x18, 0x0c, 0x86, 0x9c, 0x2b, 0x5d, 0x25, 0x80, 0xee, 0xcf, 0x30, 0x8b, + 0x0e, 0x7a, 0x59, 0x70, 0x13, 0x14, 0x64, 0x0d, 0xcf, 0xcf, 0x5a, 0xe3, + 0x8b, 0xed, 0xee, 0x6e, 0xf0, 0x59, 0xc2, 0x18, 0x9d, 0xc7, 0x38, 0x96, + 0xfd, 0x65, 0x26, 0x15, 0x6a, 0x21, 0xbe, 0xdf, 0xe6, 0x06, 0x8a, 0x97, + 0xcd, 0x9b, 0x2f, 0xf4, 0x01, 0x9f, 0x2f, 0x8f, 0x5f, 0x5c, 0xb0, 0x1e, + 0x4b, 0x42, 0xfe, 0x11, 0xa8, 0xd7, 0xff, 0x56, 0xbe, 0x20, 0x0f, 0xa9, + 0xa5, 0x4b, 0xc7, 0xf2, 0x1a, 0x7e, 0xca, 0x61, 0x79, 0xe3, 0x9a, 0x86, + 0x58, 0xe8, 0x51, 0x00, 0xcd, 0x42, 0x84, 0xb7, 0xf2, 0xba, 0xf8, 0xe8, + 0x99, 0xbe, 0xb4, 0xd7, 0x75, 0x99, 0x1c, 0xa7, 0x50, 0xeb, 0x3d, 0xa0, + 0x51, 0x52, 0x2b, 0xf0, 0x18, 0xac, 0x41, 0xa0, 0xb7, 0x83, 0x3a, 0xbd, + 0x3c, 0x43, 0xd4, 0x72, 0xcb, 0xd8, 0xd0, 0xfc, 0x53, 0x8a, 0xbe, 0xf8, + 0x06, 0x96, 0x17, 0x43, 0x8f, 0x2e, 0xac, 0xaf, 0x89, 0xec, 0x89, 0x89, + 0x1b, 0x63, 0xea, 0x20, 0x16, 0x08, 0xc3, 0x08, 0x84, 0xac, 0x52, 0xf5, + 0xd0, 0x47, 0x5d, 0xaa, 0x8e, 0xde, 0x6f, 0xc4, 0x65, 0x27, 0x4e, 0x3b, + 0xb8, 0x1c, 0x4b, 0x8f, 0x02, 0x9e, 0x18, 0x36, 0xa2, 0x6d, 0xb0, 0x7e, + 0x80, 0x8b, 0x08, 0x92, 0xb3, 0x0c, 0xb2, 0x27, 0xb6, 0x00, 0xf8, 0x4d, + 0x79, 0xb2, 0xd5, 0x18, 0x2a, 0xdf, 0xc9, 0x42, 0x5c, 0xb7, 0xda, 0xb2, + 0xea, 0xdf, 0x0b, 0x52, 0xf3, 0x0a, 0x65, 0x98, 0x0a, 0xeb, 0xe0, 0xa5, + 0xfe, 0x91, 0xa7, 0x59, 0x51, 0x1e, 0xfe, 0xee, 0x9c, 0x4f, 0x87, 0xe6, + 0x38, 0xda, 0x8c, 0xf3, 0x55, 0x29, 0xb0, 0xa5, 0xe5, 0x2a, 0x39, 0xb7, + 0xc9, 0x82, 0x76, 0x79, 0xeb, 0xee, 0x16, 0x2c, 0x9b, 0x69, 0x39, 0x79, + 0x6c, 0xda, 0x2f, 0x8f, 0xc8, 0x36, 0xa8, 0x38, 0xc7, 0x3c, 0x35, 0x72, + 0xad, 0x6d, 0x64, 0xdf, 0xde, 0x6b, 0x87, 0xac, 0x11, 0x85, 0x1e, 0xbd, + 0x7f, 0x8d, 0xe5, 0xba, 0xaa, 0xc9, 0xca, 0x0f, 0x9e, 0x93, 0xfb, 0xf7, + 0x6a, 0xb3, 0x32, 0x4f, 0x26, 0xec, 0x2f, 0xb7, 0x76, 0xbd, 0x59, 0x04, + 0xb6, 0x34, 0x9b, 0x78, 0xf8, 0x27, 0x81, 0x09, 0x4c, 0x4e, 0x0b, 0x55, + 0xff, 0x67, 0x02, 0xc4, 0xfe, 0x79, 0xaa, 0xdf, 0x17, 0xc7, 0x3a, 0x63, + 0x34, 0x65, 0xea, 0xc3, 0xf3, 0x10, 0x2a, 0xbd, 0xcd, 0x93, 0xe3, 0x73, + 0x95, 0x5c, 0x31, 0x57, 0x4b, 0x01, 0x45, 0xf2, 0x3c, 0x2d, 0x98, 0x8e, + 0x4b, 0xe2, 0xe6, 0x02, 0x3f, 0xa0, 0x07, 0xcd, 0x4c, 0x48, 0xa0, 0x29, + 0x65, 0xb2, 0x4a, 0x20, 0x2b, 0x0d, 0x81, 0xb4, 0x67, 0xd4, 0xae, 0x48, + 0xd4, 0x6f, 0x3a, 0x34, 0xec, 0xa5, 0xb6, 0xde, 0x2b, 0xe1, 0xd0, 0x1d, + 0x68, 0xcd, 0xda, 0xa0, 0x83, 0xc5, 0xe0, 0x05, 0x90, 0x58, 0x2e, 0x4a, + 0xc7, 0x56, 0xc7, 0x24, 0x2c, 0x7a, 0xce, 0xcb, 0x52, 0x63, 0x6a, 0x0c, + 0xcf, 0x85, 0x40, 0x40, 0x50, 0xf0, 0xe2, 0x72, 0xa4, 0xb1, 0xee, 0x18, + 0xce, 0xfe, 0x2f, 0x9e, 0x53, 0xe1, 0x92, 0xe5, 0xdc, 0xbc, 0x11, 0x8e, + 0x74, 0x7b, 0x72, 0xa9, 0x6d, 0xcd, 0x72, 0xe5, 0x12, 0x81, 0xfa, 0xba, + 0xf8, 0xc1, 0xfc, 0x1a, 0x8b, 0xcd, 0xf2, 0x9a, 0xf8, 0x64, 0xa0, 0x16, + 0x67, 0x43, 0x76, 0x0f, 0x75, 0x79, 0xcb, 0xf0, 0xf7, 0xf7, 0x5a, 0x9b, + 0x3a, 0xca, 0x14, 0xd0, 0xda, 0x88, 0xce, 0xf2, 0x37, 0xaa, 0xc6, 0xed, + 0xa6, 0x52, 0xeb, 0xed, 0xfd, 0xf2, 0xf4, 0x5d, 0xa0, 0x78, 0xbc, 0x32, + 0xd9, 0xbd, 0x6f, 0xfa, 0x29, 0xe6, 0xfd, 0xb3, 0x5f, 0x73, 0x09, 0xf0, + 0xb7, 0x9c, 0x55, 0xb0, 0x02, 0x34, 0xa4, 0xaf, 0xaf, 0xf0, 0x60, 0x11, + 0x94, 0xcd, 0x14, 0xab, 0x31, 0x84, 0x18, 0x82, 0xb4, 0xbd, 0x64, 0x35, + 0x80, 0x45, 0x2d, 0xf1, 0x84, 0xb8, 0xb1, 0x41, 0xda, 0xa6, 0xbd, 0x40, + 0x16, 0xaf, 0x52, 0x89, 0xb3, 0x41, 0xb6, 0xe5, 0xd4, 0x1f, 0xa3, 0x30, + 0x94, 0xc5, 0x88, 0x37, 0xe5, 0x41, 0x5c, 0x8c, 0x06, 0x2f, 0x57, 0xcd, + 0x88, 0xb0, 0x20, 0x64, 0xf9, 0x54, 0xe4, 0xe7, 0x0d, 0x4f, 0x0a, 0xdd, + 0xe1, 0x43, 0xfb, 0xc0, 0x61, 0x16, 0x2b, 0x61, 0x3d, 0x3d, 0xb2, 0xc0, + 0xd6, 0xfa, 0x34, 0xca, 0x38, 0x27, 0x1c, 0x63, 0x3e, 0x35, 0x94, 0x10, + 0xe3, 0x07, 0x32, 0xc5, 0xd9, 0xe6, 0x57, 0x0f, 0xc9, 0x2e, 0x35, 0xf4, + 0x53, 0x26, 0x38, 0xb0, 0x26, 0xa5, 0x26, 0x67, 0x99, 0x6d, 0xdd, 0xa0, + 0x59, 0x72, 0x6e, 0x08, 0x9f, 0x87, 0x56, 0x03, 0xb5, 0x72, 0x36, 0xb2, + 0x74, 0xd6, 0xf3, 0x5b, 0x8b, 0xb5, 0xf4, 0xa3, 0x48, 0x11, 0xda, 0xcc, + 0x1e, 0xfa, 0xe1, 0xee, 0x5c, 0x98, 0x32, 0x25, 0x9d, 0x65, 0xbd, 0x56, + 0xe3, 0xae, 0x08, 0xe4, 0xde, 0x60, 0x7e, 0x06, 0x81, 0x02, 0x05, 0x13, + 0x9b, 0xc1, 0x9e, 0x57, 0x1f, 0x19, 0x17, 0x6b, 0x52, 0x13, 0xdb, 0x72, + 0x12, 0xf1, 0x22, 0x63, 0xb1, 0x27, 0x66, 0x0c, 0x19, 0x68, 0x15, 0xa8, + 0x5e, 0x20, 0x49, 0x9a, 0xfe, 0x95, 0x6e, 0x9e, 0xcb, 0x22, 0x93, 0x52, + 0xba, 0x5f, 0x0a, 0x19, 0xb3, 0x05, 0x00, 0x3f, 0x09, 0x3f, 0x7f, 0xd5, + 0x8c, 0x26, 0xf7, 0xfa, 0x5c, 0x82, 0xb0, 0x2d, 0xa4, 0xcf, 0x75, 0x0b, + 0x12, 0x2b, 0x19, 0xe6, 0x0a, 0x0e, 0xb2, 0x43, 0xd4, 0xf3, 0xbe, 0x41, + 0x22, 0x9d, 0xe4, 0x38, 0xc2, 0x5a, 0x2f, 0x68, 0x86, 0xe3, 0x05, 0x84, + 0x93, 0x68, 0xe7, 0x70, 0x2a, 0x0a, 0x48, 0x5a, 0x54, 0x06, 0xe5, 0x66, + 0x44, 0x1a, 0x53, 0x9c, 0x4e, 0x52, 0x00, 0xdb, 0x42, 0x10, 0xae, 0xfc, + 0x99, 0x9d, 0x96, 0x8a, 0xab, 0x9a, 0x30, 0x0b, 0xe3, 0x4d, 0xf0, 0x2e, + 0x6a, 0x2e, 0x7b, 0x0f, 0x76, 0x95, 0x82, 0x5e, 0x41, 0x71, 0x22, 0x44, + 0x13, 0x62, 0x73, 0x2f, 0x5e, 0x04, 0x1a, 0xb4, 0x51, 0xf6, 0x13, 0x9c, + 0x9f, 0xf6, 0xb3, 0x7e, 0x9f, 0xac, 0x71, 0x1d, 0x01, 0x34, 0x4f, 0x5e, + 0x1d, 0xdb, 0x1d, 0x2c, 0x45, 0x02, 0x00, 0xbc, 0xe0, 0xdb, 0x55, 0x22, + 0xdd, 0xec, 0xa5, 0x41, 0x28, 0x59, 0x74, 0x31, 0x2c, 0xbf, 0x1c, 0xd3, + 0xbe, 0xa8, 0x74, 0x8f, 0x55, 0xe3, 0x37, 0x78, 0x12, 0x43, 0x25, 0x70, + 0x64, 0x9d, 0xc3, 0x21, 0xc1, 0x75, 0xcf, 0x23, 0x5f, 0x34, 0xb0, 0x19, + 0x8a, 0x78, 0x19, 0xdf, 0x0e, 0x49, 0x24, 0x29, 0x3b, 0x31, 0x90, 0xc3, + 0x59, 0xa8, 0xa4, 0xbc, 0xa2, 0x9d, 0x9e, 0xfb, 0xa4, 0xab, 0x43, 0xa5, + 0x93, 0x0b, 0x8b, 0xb5, 0x4d, 0x1c, 0xa6, 0x0c, 0x08, 0x07, 0x49, 0x8c, + 0x8d, 0xad, 0x2a, 0x61, 0xf5, 0x6a, 0x56, 0x69, 0x4f, 0x2a, 0x3d, 0xa2, + 0x02, 0xa5, 0x6f, 0x93, 0x61, 0x66, 0x1c, 0x53, 0x2a, 0x8a, 0xc3, 0xe5, + 0x6c, 0x16, 0xe4, 0xda, 0xf5, 0x63, 0x35, 0xf5, 0xd3, 0x37, 0x8e, 0x97, + 0x32, 0x12, 0x7e, 0x5b, 0x07, 0xe7, 0xb5, 0x66, 0x81, 0x14, 0x53, 0x36, + 0x75, 0x42, 0xac, 0x67, 0xd5, 0x8c, 0x8e, 0x94, 0x93, 0xfb, 0x0d, 0x24, + 0x0e, 0x18, 0x83, 0x6c, 0x48, 0xd2, 0xbe, 0xd7, 0x33, 0x9d, 0x76, 0x58, + 0x58, 0xea, 0x5c, 0x96, 0xc2, 0x24, 0xd6, 0x45, 0x66, 0xda, 0x26, 0x71, + 0xa4, 0x45, 0xd5, 0x59, 0xc4, 0x63, 0x7b, 0x65, 0xe8, 0x3b, 0x59, 0x58, + 0x41, 0x4d, 0x16, 0xb0, 0x5d, 0x9b, 0x2a, 0x05, 0x07, 0x59, 0x0a, 0x58, + 0x18, 0x66, 0x9a, 0x4c, 0x01, 0x25, 0x7b, 0x04, 0x3a, 0x5d, 0x3c, 0x04, + 0xe3, 0x17, 0x29, 0x2d, 0x32, 0xd9, 0x91, 0xce, 0xc4, 0xca, 0xd3, 0x86, + 0x7e, 0x36, 0xe8, 0x64, 0xed, 0xdc, 0x3e, 0x1a, 0xd9, 0x24, 0xc2, 0xac, + 0xd2, 0xcf, 0xa7, 0x96, 0x83, 0xef, 0xac, 0x39, 0x55, 0xbd, 0x42, 0xd2, + 0xb3, 0xf2, 0x76, 0x4f, 0xcc, 0x04, 0x6e, 0x27, 0x7a, 0x37, 0x1c, 0x82, + 0xb1, 0xbd, 0x1b, 0xb9, 0x61, 0xf0, 0xcd, 0xb7, 0xcb, 0xc7, 0x7c, 0xca, + 0x96, 0x03, 0x0a, 0x51, 0x83, 0x73, 0xa3, 0x39, 0x52, 0x84, 0x1c, 0x41, + 0x42, 0xc3, 0xf9, 0x21, 0x32, 0x8c, 0xa6, 0x54, 0x51, 0x9c, 0xef, 0x78, + 0x00, 0x1a, 0xcf, 0x0f, 0x5c, 0x3b, 0x42, 0x31, 0x62, 0xd2, 0xe1, 0xab, + 0xe8, 0x2f, 0xf9, 0xdb, 0xd6, 0x0a, 0x6a, 0x5f, 0xb3, 0x07, 0xcc, 0xc1, + 0x8d, 0xe0, 0x24, 0xc7, 0xfe, 0xb8, 0xa8, 0x85, 0x8a, 0xac, 0x8c, 0x3f, + 0xde, 0x83, 0xa8, 0xb8, 0xf1, 0x02, 0xd1, 0xbc, 0x8e, 0x8a, 0x35, 0xf6, + 0xde, 0x9d, 0xdf, 0xbc, 0x7d, 0x1e, 0x28, 0x8e, 0xf0, 0x8f, 0x0a, 0xb4, + 0x7a, 0x26, 0x43, 0xb2, 0x30, 0x57, 0x75, 0xbd, 0xfb, 0x21, 0x91, 0x03, + 0x00, 0x26, 0x55, 0x2d, 0x93, 0xf6, 0x41, 0x10, 0xd1, 0x30, 0x17, 0xa6, + 0xa7, 0x59, 0x94, 0x89, 0x3e, 0x8d, 0x15, 0x7c, 0x76, 0x6a, 0xbe, 0x1a, + 0xfd, 0xb8, 0x31, 0x0a, 0x31, 0x51, 0x59, 0x89, 0xd4, 0x15, 0x38, 0x61, + 0xe0, 0x3e, 0xf2, 0x0f, 0x50, 0x37, 0x9f, 0xde, 0x0a, 0x69, 0xdc, 0x04, + 0xf3, 0xed, 0x60, 0x8a, 0x10, 0xdd, 0x4c, 0x04, 0x57, 0x57, 0x9e, 0xdd, + 0xeb, 0x1a, 0xc5, 0xa3, 0xce, 0x44, 0x7c, 0x31, 0x8b, 0xe2, 0xa9, 0xf8, + 0x39, 0xb2, 0x13, 0xe8, 0x2f, 0xe5, 0x97, 0x5e, 0x19, 0xb7, 0xf9, 0x3d, + 0x95, 0x0b, 0xac, 0xd4, 0xc9, 0x1f, 0x11, 0x97, 0x25, 0x70, 0x3e, 0x9a, + 0xd3, 0x95, 0x87, 0x72, 0xa0, 0x1b, 0xa4, 0x95, 0xff, 0x0b, 0x56, 0x6a, + 0x43, 0x0a, 0x7e, 0x9f, 0x70, 0xa4, 0xe9, 0x49, 0xcf, 0x1d, 0x9a, 0xd2, + 0x17, 0x46, 0x93, 0x46, 0x10, 0x87, 0x4b, 0x17, 0x02, 0xbf, 0x5c, 0xe2, + 0x52, 0x7d, 0xa8, 0x26, 0xf6, 0x7a, 0xd1, 0x45, 0xb7, 0xa0, 0x54, 0x4c, + 0x29, 0x0f, 0x51, 0x18, 0x60, 0x87, 0xac, 0x00, 0xda, 0xc3, 0x62, 0xc0, + 0x05, 0x33, 0x02, 0x9b, 0x36, 0x24, 0x3a, 0x41, 0x27, 0x10, 0x8d, 0x51, + 0x00, 0x31, 0x69, 0xf1, 0x3b, 0x0e, 0x41, 0x98, 0x99, 0x60, 0xdb, 0xfe, + 0xf9, 0xcb, 0x8a, 0x22, 0x86, 0x16, 0x47, 0xea, 0xc6, 0xdb, 0x24, 0x67, + 0x33, 0xda, 0x2c, 0xaa, 0xb9, 0xe7, 0x04, 0xb0, 0x6e, 0x70, 0x41, 0xbc, + 0x61, 0x13, 0x61, 0xb3, 0x50, 0xb6, 0xd6, 0xfc, 0x3a, 0xaf, 0x8e, 0x0f, + 0xc9, 0x37, 0xa7, 0x32, 0x3b, 0xfd, 0xa0, 0xab, 0x0b, 0x60, 0xc4, 0x2b, + 0x3f, 0xf3, 0x79, 0x21, 0x1c, 0x90, 0x3a, 0x0e, 0x11, 0xc6, 0xac, 0x3d, + 0x33, 0x18, 0x18, 0x45, 0xa8, 0x40, 0x1e, 0x24, 0x13, 0x9d, 0x1e, 0xe0, + 0x07, 0xf8, 0x36, 0xe4, 0xa9, 0x9d, 0x73, 0xe1, 0x45, 0xc5, 0xb3, 0x14, + 0x98, 0x28, 0x24, 0xd7, 0x21, 0xa4, 0xaa, 0x2b, 0x93, 0xfd, 0x34, 0x5e, + 0x3b, 0xfd, 0xaa, 0xb9, 0x46, 0xfa, 0xaf, 0x6f, 0x3d, 0xc7, 0x1a, 0x71, + 0x76, 0x49, 0xe7, 0x85, 0x45, 0x9b, 0xde, 0x98, 0x5f, 0x8b, 0x91, 0x21, + 0xea, 0xb8, 0x0a, 0x78, 0xfd, 0x83, 0x11, 0x13, 0xef, 0xda, 0x7b, 0xa6, + 0xff, 0x67, 0x78, 0x46, 0xb6, 0xec, 0x77, 0x6c, 0x0a, 0x00, 0xdf, 0xd1, + 0xbf, 0xab, 0xb0, 0xd5, 0xf0, 0xf8, 0xec, 0x46, 0x03, 0x22, 0xc5, 0xf6, + 0x89, 0x1f, 0x14, 0xce, 0xcd, 0xff, 0x32, 0x9f, 0xae, 0xdd, 0x20, 0x5a, + 0xf9, 0x2f, 0x55, 0xa8, 0xa3, 0xac, 0xfd, 0xe2, 0x27, 0x8f, 0xed, 0xb7, + 0x1f, 0x00, 0xd5, 0x3d, 0x95, 0xaf, 0xde, 0xd5, 0x31, 0x0b, 0x53, 0x05, + 0xb2, 0x36, 0x81, 0xd1, 0x27, 0xdc, 0x52, 0x92, 0x22, 0x3b, 0xf3, 0x9d, + 0x1a, 0x4e, 0x58, 0x96, 0x96, 0x39, 0x6b, 0x3f, 0x38, 0x36, 0xd2, 0x0d, + 0xf7, 0x86, 0xc6, 0xa5, 0xd5, 0x93, 0xd6, 0xd2, 0xab, 0x7b, 0x7f, 0x3f, + 0x16, 0xe5, 0x87, 0x6d, 0x3a, 0x8d, 0xee, 0xfc, 0xfb, 0x70, 0x0a, 0x72, + 0x26, 0x7f, 0xd5, 0xd4, 0x67, 0xf7, 0x0f, 0x09, 0xf0, 0xb8, 0xb4, 0xba, + 0x6e, 0xc4, 0xcd, 0x7d, 0x7b, 0x7e, 0x9b, 0xd1, 0xa8, 0x48, 0x55, 0x0f, + 0x2c, 0xcc, 0x46, 0x6e, 0xa7, 0x52, 0x71, 0xd8, 0x01, 0x7a, 0xd0, 0x10, + 0xde, 0x41, 0x7e, 0x62, 0x99, 0x80, 0x97, 0x9e, 0x76, 0x33, 0x80, 0xbe, + 0x45, 0x96, 0xa4, 0xf6, 0xe3, 0x9f, 0xe2, 0x57, 0x44, 0x31, 0xa7, 0xf6, + 0x4b, 0x45, 0xab, 0x1b, 0xc4, 0x6b, 0x9f, 0x69, 0xed, 0xaf, 0xe7, 0x16, + 0x70, 0xc0, 0x40, 0x92, 0xc1, 0x52, 0x07, 0x8f, 0x58, 0x62, 0xb1, 0xb1, + 0x20, 0x81, 0x46, 0x83, 0xd8, 0xd4, 0xc5, 0xe3, 0x97, 0xc4, 0x0f, 0x92, + 0x7e, 0xad, 0x97, 0x5a, 0x1d, 0xd0, 0x34, 0x06, 0x37, 0x15, 0x4b, 0xe0, + 0x71, 0xcf, 0x7e, 0x84, 0x59, 0x8d, 0x82, 0xa7, 0x42, 0x20, 0x65, 0xab, + 0xcc, 0x20, 0x32, 0x83, 0x53, 0x95, 0xff, 0xff, 0x0d, 0x55, 0x52, 0x32, + 0xcf, 0x2b, 0x45, 0xc9, 0x60, 0xa8, 0x43, 0xa1, 0x0d, 0x72, 0xf5, 0xec, + 0x6a, 0x81, 0xd2, 0x88, 0x80, 0x39, 0xea, 0x11, 0xe7, 0xec, 0x15, 0xb1, + 0x1a, 0x9a, 0x10, 0x12, 0xb6, 0x5c, 0x87, 0xaa, 0x82, 0x00, 0x83, 0x83, + 0x8a, 0x8d, 0xc7, 0xd9, 0xb8, 0xa0, 0x21, 0x41, 0x52, 0x7f, 0x1a, 0x24, + 0xcb, 0x52, 0x19, 0xa4, 0x42, 0xdd, 0x65, 0x73, 0x26, 0x09, 0x78, 0xf9, + 0x33, 0x0b, 0xed, 0xe7, 0x78, 0xfc, 0x32, 0xb6, 0xc3, 0xd3, 0x35, 0xcb, + 0x53, 0x94, 0xf2, 0x00, 0x92, 0x73, 0x22, 0xd5, 0xdf, 0x50, 0x9f, 0xa7, + 0xb1, 0xae, 0x60, 0xca, 0x1c, 0x7b, 0xec, 0xf4, 0xf5, 0xaa, 0x7d, 0x20, + 0x7e, 0x1e, 0x57, 0xe7, 0xf8, 0x97, 0x6e, 0x12, 0x32, 0x42, 0x9f, 0xc1, + 0x50, 0x9f, 0xf3, 0x7d, 0xda, 0x88, 0x46, 0xd5, 0x59, 0xa4, 0xcf, 0xdb, + 0xcd, 0xed, 0x05, 0xed, 0x94, 0x55, 0x6c, 0xa1, 0xf3, 0xa0, 0xdd, 0x88, + 0x54, 0x19, 0x35, 0xfc, 0xdb, 0x84, 0x3b, 0x30, 0xc9, 0x3b, 0x1b, 0xf2, + 0xbf, 0x8e, 0x17, 0xfe, 0xf3, 0x1e, 0x48, 0xda, 0x94, 0xba, 0x69, 0xba, + 0x90, 0x8a, 0xfd, 0x0d, 0xa8, 0xf8, 0x3d, 0xfd, 0xa6, 0x77, 0x83, 0x50, + 0x57, 0x1f, 0x26, 0xe9, 0x3f, 0x21, 0x35, 0xbd, 0x8e, 0xaf, 0xeb, 0x26, + 0x0d, 0x53, 0x11, 0xd1, 0x3f, 0x4a, 0xa5, 0x51, 0x95, 0x75, 0x32, 0xf7, + 0x3c, 0xe3, 0xda, 0x76, 0x5b, 0xf9, 0x2e, 0xa9, 0x78, 0x28, 0xe9, 0xb8, + 0x8a, 0xa3, 0xdc, 0xf1, 0x50, 0x0c, 0xe2, 0x4b, 0x1d, 0x5a, 0x4b, 0x5c, + 0xa0, 0x8a, 0xd0, 0xf1, 0x6e, 0xd4, 0x0e, 0xe5, 0x3b, 0x39, 0x56, 0xe5, + 0x15, 0xe5, 0x22, 0xd6, 0xc6, 0x16, 0x5e, 0x1d, 0x36, 0x47, 0xb6, 0x2c, + 0xd1, 0x14, 0x72, 0x2e, 0x79, 0x0d, 0x09, 0x4f, 0xd1, 0xd0, 0x7a, 0xc9, + 0xdc, 0xd0, 0xd8, 0x0e, 0x45, 0x1c, 0xee, 0x8a, 0x63, 0x32, 0x9b, 0x68, + 0xeb, 0xa2, 0x5a, 0x3d, 0xeb, 0x25, 0x7f, 0x1c, 0x57, 0xb4, 0x5c, 0x7d, + 0x08, 0x15, 0x62, 0x6b, 0xeb, 0xea, 0xb7, 0x09, 0xf5, 0xa9, 0x25, 0x20, + 0x06, 0xf5, 0xd5, 0x27, 0x7c, 0xaf, 0xd7, 0x34, 0xac, 0x6f, 0x7c, 0x94, + 0x66, 0x10, 0x86, 0x49, 0xf1, 0x16, 0x34, 0xaa, 0xb0, 0x64, 0x4b, 0x22, + 0x21, 0x6b, 0x11, 0x54, 0x32, 0x72, 0xeb, 0x58, 0x54, 0xb5, 0xdf, 0xd1, + 0x91, 0x21, 0xfa, 0x85, 0xfb, 0x1b, 0x25, 0xde, 0xef, 0x1f, 0x7f, 0x0e, + 0x28, 0x2f, 0x8e, 0xdf, 0xce, 0x5e, 0xea, 0xbf, 0x8d, 0x49, 0xca, 0x66, + 0x74, 0xf6, 0xb0, 0x2a, 0x7b, 0x9a, 0xfc, 0xc3, 0x78, 0xb0, 0x3b, 0xbd, + 0xc2, 0x24, 0x12, 0xe9, 0x27, 0x86, 0x03, 0xe6, 0x83, 0x32, 0x26, 0xe8, + 0xd8, 0x83, 0xf4, 0xc9, 0x76, 0xc5, 0xd5, 0x9a, 0xe8, 0x24, 0xab, 0x95, + 0x63, 0xfc, 0x57, 0xd9, 0x62, 0x82, 0xc7, 0x3e, 0x74, 0x14, 0x43, 0x69, + 0x4e, 0xcb, 0x89, 0xf4, 0x3e, 0x7b, 0x5d, 0x6d, 0xa0, 0x85, 0x1c, 0x01, + 0x4c, 0xf1, 0x18, 0xfc, 0xee, 0x69, 0xf2, 0x15, 0x2b, 0x6a, 0x66, 0x54, + 0x78, 0xf3, 0xa0, 0xe8, 0xfa, 0x3c, 0x50, 0x96, 0x72, 0x7d, 0xc2, 0x4b, + 0x85, 0x8c, 0x45, 0x3a, 0xd3, 0x31, 0xf4, 0xe6, 0xa2, 0xdc, 0xad, 0xad, + 0xc8, 0x03, 0xf8, 0xa5, 0xf5, 0x15, 0x03, 0x55, 0xc6, 0x16, 0x42, 0x0e, + 0x6e, 0x84, 0x70, 0xdc, 0x45, 0x0e, 0x97, 0xd2, 0x36, 0x5f, 0x40, 0x7a, + 0xe6, 0x42, 0x7e, 0xe6, 0x75, 0x96, 0x0c, 0xe0, 0x7a, 0xf1, 0x6c, 0x3f, + 0x80, 0x07, 0x76, 0xf4, 0x61, 0xa5, 0x25, 0xe9, 0x5f, 0x3b, 0x8a, 0xe8, + 0x4d, 0x57, 0x67, 0x9c, 0x6e, 0xc3, 0x57, 0x96, 0x95, 0x67, 0x62, 0xf2, + 0x13, 0x7f, 0x8c, 0x0c, 0x15, 0xd9, 0xae, 0x49, 0x58, 0xcd, 0xcb, 0x09, + 0xaa, 0xfa, 0xaa, 0x54, 0xc9, 0x85, 0x69, 0x99, 0xc4, 0xf1, 0x77, 0xce, + 0xa3, 0x69, 0x70, 0x0c, 0x9f, 0xad, 0x80, 0x57, 0x4b, 0xe5, 0x8b, 0xd3, + 0x6a, 0x6d, 0x3c, 0xe7, 0x3d, 0xb3, 0x25, 0xd2, 0x1b, 0x37, 0x53, 0x9d, + 0x40, 0x14, 0xaf, 0xe8, 0x51, 0x4f, 0x30, 0x75, 0xd3, 0x7a, 0xa1, 0xb4, + 0x24, 0xe7, 0x6c, 0x91, 0xcf, 0xe6, 0xac, 0x89, 0x9f, 0x79, 0xb9, 0x74, + 0x51, 0xbf, 0x4c, 0x59, 0x8b, 0xe8, 0xb8, 0x71, 0x32, 0xa5, 0xad, 0x68, + 0x2d, 0x65, 0x8d, 0x51, 0x89, 0xd9, 0xc4, 0xf2, 0x38, 0xe0, 0x07, 0xec, + 0x73, 0x65, 0xbb, 0xbf, 0xe9, 0x2b, 0xc8, 0xa7, 0xc5, 0xba, 0xca, 0x7c, + 0xb0, 0x52, 0x94, 0x18, 0x56, 0xbb, 0x8d, 0x0f, 0xf6, 0xdb, 0x16, 0xa6, + 0x1f, 0x08, 0xdf, 0x67, 0x6a, 0xff, 0x2a, 0x6e, 0xda, 0x43, 0x56, 0xbd, + 0x36, 0x74, 0xdf, 0x2f, 0x79, 0x32, 0xed, 0x0f, 0xec, 0xbf, 0x9a, 0xb2, + 0x24, 0xf5, 0x28, 0xf6, 0xd9, 0x71, 0x73, 0xeb, 0xbe, 0x0a, 0xcc, 0xc7, + 0x98, 0xdd, 0x8b, 0x14, 0x7f, 0x7c, 0xef, 0xce, 0x18, 0xaf, 0x86, 0x92, + 0xf6, 0x43, 0xc1, 0x26, 0x91, 0x65, 0x19, 0x5a, 0x1d, 0x5c, 0x96, 0x6e, + 0x41, 0xe6, 0x06, 0x02, 0xb9, 0xc8, 0xb5, 0x92, 0x1f, 0x9d, 0x10, 0x92, + 0xe9, 0xf2, 0xfb, 0xd1, 0x21, 0xca, 0x84, 0xdf, 0xbc, 0xc4, 0x82, 0x12, + 0x97, 0xf2, 0x32, 0xab, 0x35, 0x82, 0xd1, 0xbc, 0x23, 0xd4, 0x49, 0x23, + 0xef, 0xa3, 0x0e, 0xb5, 0xee, 0x09, 0x24, 0xb2, 0x90, 0x0c, 0xf3, 0xd5, + 0x9f, 0xdb, 0xb7, 0x51, 0xda, 0x89, 0x93, 0xf2, 0x7a, 0x1e, 0x46, 0xf3, + 0xa6, 0x39, 0x64, 0x49, 0x45, 0x1a, 0xab, 0xe7, 0xa3, 0x2d, 0x04, 0xe4, + 0x5a, 0x92, 0x0f, 0x4e, 0x97, 0x75, 0xe8, 0x76, 0xb9, 0x1a, 0xfb, 0xa7, + 0x20, 0x9a, 0x7f, 0x67, 0x19, 0xed, 0xad, 0x39, 0xa3, 0x72, 0xf8, 0x4b, + 0xca, 0xf4, 0x66, 0xe4, 0xf6, 0xd9, 0x3d, 0x09, 0x41, 0x6e, 0x10, 0x48, + 0x78, 0xa9, 0xcc, 0xed, 0x86, 0xe4, 0x5f, 0x7f, 0x5e, 0x6c, 0x08, 0x9b, + 0xbe, 0x0c, 0x7d, 0x97, 0xff, 0xfa, 0xbe, 0xe2, 0x0f, 0x8e, 0xe8, 0xef, + 0x3b, 0xce, 0xbd, 0xfe, 0x16, 0x39, 0x1c, 0x76, 0x7c, 0x64, 0x04, 0xaf, + 0xa6, 0x6b, 0x75, 0xa8, 0xa8, 0x13, 0x9d, 0x3e, 0xda, 0x78, 0x8a, 0x39, + 0x44, 0xcb, 0x56, 0x07, 0xbe, 0xd8, 0xd2, 0x2a, 0x2f, 0x11, 0xb1, 0xc8, + 0xed, 0xe1, 0x99, 0xd4, 0x6e, 0x87, 0x87, 0xb7, 0xf4, 0xfb, 0xa8, 0xaa, + 0x71, 0xfe, 0xc1, 0x5d, 0x60, 0x2d, 0xf4, 0x01, 0xa7, 0x3c, 0x16, 0xa8, + 0xf1, 0x57, 0x5e, 0xa3, 0x44, 0x3b, 0xc5, 0xd6, 0x4f, 0x27, 0x72, 0x85, + 0xbd, 0xcb, 0x44, 0x3c, 0xf9, 0xb3, 0x59, 0x7d, 0xb0, 0x24, 0x49, 0x4d, + 0x0a, 0x5f, 0x46, 0xfb, 0x37, 0x49, 0x9c, 0x48, 0x12, 0xad, 0x47, 0xd4, + 0x3b, 0x18, 0x8c, 0x1f, 0x95, 0xc4, 0xea, 0x3b, 0x68, 0x48, 0xf0, 0xb9, + 0x66, 0x26, 0xd5, 0x10, 0xaa, 0xdf, 0xc8, 0x68, 0xab, 0xab, 0x29, 0x64, + 0xf2, 0xbd, 0xe0, 0xb0, 0x4b, 0x7b, 0x86, 0x98, 0x34, 0xb9, 0x3e, 0xc2, + 0x99, 0xf6, 0x81, 0x12, 0x29, 0x7d, 0xa6, 0x27, 0xa7, 0x61, 0xda, 0xc1, + 0x2a, 0x08, 0xe8, 0x08, 0x09, 0x47, 0x38, 0x18, 0xba, 0x2d, 0x17, 0x7a, + 0x59, 0x6d, 0x86, 0x15, 0x49, 0x19, 0x90, 0x0e, 0x2e, 0x78, 0x2c, 0xe7, + 0x90, 0x94, 0x00, 0x78, 0x92, 0x87, 0x2b, 0xc2, 0x53, 0x4e, 0x27, 0x4f, + 0x47, 0xa9, 0xdd, 0xc1, 0x46, 0x91, 0x44, 0xad, 0x85, 0xb8, 0x0d, 0xa2, + 0xc4, 0x07, 0x53, 0x30, 0x84, 0x63, 0xe2, 0x89, 0xba, 0x5d, 0xc1, 0x4e, + 0x2d, 0x16, 0x52, 0xbb, 0xfc, 0x96, 0xf2, 0xa2, 0xf1, 0xb7, 0x0d, 0x8f, + 0x18, 0xc0, 0xbd, 0x0e, 0x26, 0xb5, 0x4a, 0x03, 0x29, 0xef, 0x8b, 0xce, + 0x89, 0x86, 0x02, 0xf1, 0x2b, 0xfc, 0x0c, 0x20, 0x3b, 0x58, 0x13, 0x69, + 0x5a, 0x05, 0xc3, 0x5a, 0x5f, 0x42, 0x06, 0xfa, 0x32, 0xec, 0x95, 0x2a, + 0x3c, 0xa3, 0x76, 0x91, 0xa7, 0x6a, 0x43, 0x7a, 0xca, 0x5c, 0x1a, 0xf3, + 0xff, 0x7a, 0xc0, 0x9b, 0xb6, 0xed, 0xb7, 0x58, 0x0c, 0x41, 0xa0, 0x1c, + 0xe1, 0x69, 0x5a, 0xb5, 0xe3, 0x23, 0x67, 0x1b, 0x0a, 0x89, 0x4c, 0x56, + 0x9e, 0x82, 0xee, 0x8a, 0xff, 0xaf, 0x2d, 0x63, 0x41, 0xd0, 0xb0, 0x5d, + 0x37, 0x76, 0x41, 0x25, 0x24, 0x52, 0x80, 0x95, 0xb3, 0xd5, 0x2d, 0x8c, + 0x04, 0x54, 0xb7, 0x80, 0xa3, 0x9b, 0xbc, 0xc7, 0xf9, 0x4c, 0x25, 0xfe, + 0x2d, 0x0a, 0x2c, 0x1d, 0xd7, 0x6a, 0x35, 0xac, 0xee, 0x38, 0x2d, 0x4b, + 0x22, 0x77, 0x2c, 0x97, 0xe9, 0x13, 0xc9, 0x2a, 0x85, 0x15, 0xff, 0x53, + 0x89, 0x81, 0x21, 0xad, 0x7e, 0xe4, 0x0d, 0xfd, 0x15, 0xdb, 0xa9, 0xa3, + 0x52, 0x50, 0x55, 0x7e, 0x25, 0x70, 0x64, 0xc3, 0x48, 0x06, 0xe2, 0x91, + 0x18, 0x97, 0x0d, 0x5b, 0x00, 0x74, 0x3e, 0xf0, 0x62, 0x30, 0xd6, 0xad, + 0x57, 0x24, 0x60, 0x76, 0x79, 0xd5, 0xcd, 0x85, 0x83, 0x4b, 0x72, 0x71, + 0x65, 0xaa, 0x4f, 0xbd, 0x88, 0x12, 0xf7, 0x49, 0xe3, 0x02, 0x67, 0xaa, + 0x64, 0xed, 0xd7, 0xb9, 0x9e, 0x52, 0xab, 0x23, 0x8c, 0x09, 0x98, 0x95, + 0xac, 0x8c, 0x5d, 0x0a, 0x25, 0x06, 0x5b, 0xcf, 0x24, 0x46, 0x45, 0x12, + 0x5b, 0x56, 0xda, 0xb0, 0xd1, 0x2e, 0xff, 0xf1, 0x12, 0x4c, 0x5a, 0x8f, + 0x88, 0xe3, 0xe3, 0x79, 0x50, 0x64, 0x25, 0xa1, 0x02, 0x94, 0x72, 0x98, + 0x51, 0x0c, 0xa9, 0x1f, 0xaf, 0x34, 0xfb, 0xed, 0x08, 0x33, 0x45, 0x6d, + 0x55, 0x69, 0x22, 0x89, 0x8f, 0x57, 0x76, 0xd7, 0x8c, 0x14, 0x9e, 0xd4, + 0x0b, 0xd3, 0xc3, 0xfb, 0xa7, 0x9e, 0x92, 0xb8, 0x30, 0x45, 0xba, 0xb5, + 0xc0, 0x88, 0x7e, 0xef, 0x0b, 0x5a, 0x6c, 0x47, 0x23, 0x83, 0x63, 0x47, + 0x0f, 0xe0, 0xff, 0x91, 0x40, 0xd7, 0xb3, 0x61, 0xd9, 0x2b, 0x14, 0x63, + 0x6a, 0x55, 0xd3, 0x04, 0x4c, 0xda, 0x5b, 0x43}; + +/* Same key over a WH_TEST_SLHDSA_LONG_MSG_LEN message, byte i = (byte)i */ +#define WH_TEST_SLHDSA_LONG_MSG_LEN 1024 + +static const byte whTestSlhDsaVerifyLongSig[WH_TEST_SLHDSA_KAT_SIG_LEN] = { + 0x45, 0xa4, 0x02, 0x96, 0x29, 0x30, 0xcc, 0x4c, 0x55, 0x97, 0x6f, 0x73, + 0x37, 0xb9, 0x15, 0x9d, 0xa9, 0xd1, 0x07, 0x30, 0x2f, 0x37, 0xa4, 0x8d, + 0x1c, 0xbb, 0xf8, 0xc1, 0xd6, 0xd6, 0x4e, 0xc0, 0x1b, 0x81, 0x5f, 0x62, + 0x0a, 0xd2, 0xb3, 0x6b, 0x1a, 0x33, 0x48, 0x8e, 0x81, 0xc5, 0xe9, 0x4b, + 0x6d, 0x56, 0x02, 0xc0, 0x62, 0x84, 0x5d, 0xb8, 0x1b, 0x6a, 0xbf, 0xfc, + 0xd0, 0xcf, 0x46, 0x67, 0x6b, 0x1a, 0x83, 0x76, 0x10, 0xf2, 0x9d, 0xf9, + 0x0a, 0xdf, 0xf4, 0x36, 0x95, 0xbc, 0xf8, 0x56, 0xd8, 0x22, 0xd5, 0xbf, + 0xdc, 0xd7, 0xea, 0x73, 0x86, 0x31, 0x51, 0xb1, 0xe0, 0xcc, 0x53, 0x64, + 0xbb, 0x64, 0x51, 0x98, 0x5d, 0x15, 0xdf, 0x28, 0x38, 0xc2, 0x5b, 0x9d, + 0xe2, 0x63, 0x44, 0xee, 0x11, 0xa0, 0xfa, 0xc5, 0x00, 0x99, 0x33, 0x30, + 0xe5, 0xab, 0xd3, 0x54, 0x6a, 0x88, 0x80, 0xc1, 0x22, 0x5d, 0xb2, 0x5d, + 0xa7, 0xce, 0xc2, 0x59, 0x1b, 0xdf, 0x33, 0x0a, 0x64, 0xa6, 0x8d, 0x30, + 0xf1, 0x7d, 0x02, 0x19, 0x3d, 0xe5, 0xd2, 0x90, 0x14, 0x8c, 0x1b, 0x3f, + 0xc6, 0xf7, 0xf5, 0xec, 0xa1, 0xda, 0xdb, 0x23, 0xbb, 0xcd, 0x31, 0x9d, + 0x13, 0x01, 0xfa, 0x41, 0xde, 0x07, 0xaa, 0xa3, 0xc9, 0xec, 0x74, 0x62, + 0x86, 0x16, 0xf7, 0x02, 0x61, 0xd9, 0x68, 0x7d, 0x61, 0x50, 0xe3, 0xad, + 0x33, 0x57, 0x9a, 0xd6, 0x7a, 0xa3, 0x93, 0x29, 0xc6, 0x98, 0x8d, 0x70, + 0x12, 0x89, 0x43, 0x96, 0xf1, 0x98, 0x47, 0x38, 0x0f, 0xca, 0x34, 0x81, + 0x52, 0x9e, 0x8c, 0x13, 0x5c, 0x05, 0x09, 0xb6, 0x69, 0xf9, 0x5d, 0xcb, + 0xd0, 0x15, 0x52, 0x23, 0xc9, 0x20, 0x94, 0x50, 0xee, 0xe7, 0x1a, 0x36, + 0xa4, 0x42, 0x0a, 0xea, 0xfb, 0x7e, 0x72, 0xe1, 0xc8, 0xa3, 0x3c, 0xcb, + 0xaa, 0xed, 0x72, 0x38, 0xdd, 0x8c, 0xe8, 0x23, 0x55, 0x64, 0x8e, 0xf7, + 0xc9, 0x6e, 0xcc, 0xbf, 0x70, 0x2f, 0xc3, 0xa3, 0x74, 0x95, 0x05, 0x59, + 0x87, 0x16, 0x08, 0x15, 0xfb, 0xef, 0xe7, 0x99, 0x82, 0x3a, 0xf1, 0xbe, + 0x10, 0x6f, 0x48, 0x2a, 0xd3, 0x86, 0x24, 0xb0, 0x7b, 0x66, 0xeb, 0x66, + 0x71, 0xe7, 0x62, 0x90, 0x80, 0xe9, 0xcb, 0x1a, 0x22, 0x65, 0x18, 0x4e, + 0x3b, 0xb2, 0x65, 0xab, 0xcc, 0xa0, 0x10, 0xd4, 0x5b, 0xd5, 0xda, 0x53, + 0x64, 0x0d, 0x42, 0x64, 0xe4, 0x3e, 0x12, 0x31, 0x9d, 0x49, 0x60, 0x5a, + 0xcf, 0x3c, 0xff, 0x4e, 0x43, 0x6c, 0x7c, 0x43, 0xed, 0xb1, 0xbd, 0x24, + 0xf5, 0x5b, 0x06, 0xd5, 0x90, 0x09, 0x75, 0x9a, 0xeb, 0x75, 0xea, 0xfe, + 0x6b, 0x97, 0x5a, 0x49, 0x64, 0xe5, 0xe3, 0x8b, 0x43, 0x42, 0x66, 0x6c, + 0x17, 0xd6, 0xbe, 0xf3, 0xef, 0x32, 0x66, 0x10, 0x97, 0xa1, 0x88, 0x11, + 0x52, 0x3a, 0x3c, 0x27, 0xc3, 0xe7, 0x29, 0x5e, 0xc3, 0x90, 0x5f, 0xbb, + 0xad, 0xa1, 0xba, 0x92, 0xdf, 0xa9, 0x25, 0x87, 0x56, 0xbd, 0x98, 0xa1, + 0xa8, 0xa9, 0xdb, 0x1e, 0x4c, 0x16, 0xb5, 0x75, 0x71, 0xb8, 0x0a, 0x54, + 0x0b, 0x0b, 0x0d, 0xc8, 0xcc, 0x4f, 0xfa, 0x8b, 0xc9, 0xda, 0x02, 0xf4, + 0x4f, 0xab, 0x60, 0xb3, 0xae, 0x0a, 0xe0, 0x79, 0xcc, 0x2f, 0x35, 0x5b, + 0x6d, 0x33, 0x13, 0xb7, 0x63, 0x1b, 0x37, 0x0c, 0x1a, 0x50, 0xe3, 0x43, + 0xe1, 0x64, 0x74, 0x86, 0x66, 0xc0, 0xe1, 0x5d, 0xff, 0x30, 0x2f, 0x2f, + 0x2e, 0x49, 0x90, 0xe9, 0x4e, 0x79, 0x1d, 0x2b, 0xd7, 0x3b, 0xc3, 0x54, + 0xdd, 0x68, 0xbe, 0xc1, 0xc2, 0x6c, 0x40, 0x66, 0xd9, 0x93, 0xe6, 0x27, + 0xc0, 0x11, 0xd9, 0x04, 0xd5, 0x0b, 0x16, 0x70, 0x91, 0x5e, 0x2d, 0xc8, + 0xd9, 0x5b, 0x86, 0x7c, 0x76, 0x54, 0xb5, 0x2a, 0xf1, 0xd3, 0x23, 0x11, + 0x3b, 0xef, 0x9b, 0x4c, 0x60, 0x83, 0xb4, 0x92, 0x8b, 0x50, 0x0b, 0xca, + 0xf7, 0x16, 0x83, 0xa4, 0x95, 0x63, 0x3d, 0x5d, 0x98, 0xbf, 0xea, 0xac, + 0x9a, 0x96, 0x6e, 0x93, 0x8c, 0x4c, 0x7b, 0x4e, 0x67, 0xab, 0x17, 0x85, + 0x9a, 0xff, 0x88, 0x4f, 0x2d, 0xbb, 0x97, 0xd2, 0xab, 0x70, 0x10, 0x88, + 0x37, 0xa5, 0xca, 0x10, 0x4b, 0x1f, 0xb5, 0xda, 0x85, 0xa0, 0x28, 0xdc, + 0x21, 0x81, 0xf2, 0xfe, 0x17, 0x58, 0x5a, 0x11, 0x3c, 0xf9, 0x1c, 0x06, + 0x91, 0xb9, 0x8b, 0x12, 0xff, 0xba, 0x6b, 0x04, 0xbf, 0xd4, 0x78, 0x32, + 0x5b, 0x15, 0x85, 0xfe, 0x97, 0x5b, 0x5d, 0xdc, 0x83, 0x7a, 0x37, 0x0a, + 0x35, 0x1d, 0xdc, 0x40, 0x95, 0x3a, 0x72, 0xb5, 0x66, 0x67, 0xf4, 0xa5, + 0xed, 0x32, 0x5f, 0x42, 0xeb, 0x81, 0x41, 0xf1, 0x3c, 0x44, 0xfe, 0x1b, + 0x85, 0xea, 0xef, 0x67, 0x1a, 0x77, 0xba, 0xab, 0x97, 0xd6, 0x4a, 0x3a, + 0x4d, 0xbb, 0x67, 0xda, 0x9f, 0x4e, 0x9c, 0x5e, 0xb9, 0x54, 0x79, 0xa5, + 0xfb, 0xf7, 0xd1, 0xfd, 0x7a, 0xaf, 0x4d, 0xd2, 0x78, 0x65, 0xc0, 0x3e, + 0x8f, 0x31, 0x98, 0xd5, 0x02, 0x6e, 0x3a, 0x05, 0x63, 0xdf, 0x4a, 0x08, + 0x27, 0xaa, 0xee, 0xba, 0x02, 0x11, 0xa6, 0x22, 0xfb, 0xc8, 0x44, 0x55, + 0x62, 0x1a, 0x39, 0xbc, 0xa8, 0x07, 0x9a, 0x63, 0x4f, 0xfc, 0xda, 0x10, + 0xfe, 0x7b, 0x71, 0x4a, 0x50, 0x7e, 0xf3, 0xef, 0xbc, 0x14, 0x21, 0x84, + 0x3d, 0x55, 0x75, 0x1c, 0x9b, 0x37, 0x6e, 0x9d, 0x0e, 0x20, 0x9a, 0xc0, + 0x8d, 0x6c, 0x78, 0xc4, 0xd7, 0xee, 0xa4, 0xbb, 0xf7, 0x64, 0x82, 0x6c, + 0x36, 0x8c, 0x70, 0x1d, 0x5e, 0x85, 0xba, 0x3a, 0x8b, 0x81, 0xc0, 0x5f, + 0xf3, 0x29, 0x97, 0x63, 0xfb, 0xf9, 0x03, 0xac, 0x87, 0x13, 0xa2, 0x19, + 0x88, 0xa1, 0x7e, 0x0c, 0x4f, 0x60, 0x8a, 0x78, 0xda, 0x6a, 0xd0, 0x39, + 0x77, 0x75, 0xa5, 0xdd, 0x9a, 0x29, 0xf2, 0xb5, 0xab, 0x38, 0x6c, 0x37, + 0xa2, 0x96, 0x3c, 0xd9, 0x29, 0x38, 0xf4, 0xf5, 0x9e, 0x87, 0xf2, 0x62, + 0xd9, 0xaa, 0x2b, 0x78, 0xf4, 0x49, 0x13, 0x16, 0x74, 0xeb, 0xfc, 0xe6, + 0xbe, 0xf1, 0x0f, 0x56, 0x8d, 0xb2, 0x3f, 0xcc, 0xd8, 0xed, 0xbc, 0x5e, + 0xee, 0x9e, 0x27, 0x73, 0x3f, 0xc9, 0x3c, 0x01, 0xd6, 0xeb, 0x1d, 0xe5, + 0xfe, 0xcc, 0x1b, 0xc6, 0x8c, 0xeb, 0x71, 0xc7, 0xd2, 0xcd, 0x78, 0x6a, + 0xfe, 0x67, 0x76, 0x20, 0xb4, 0x0c, 0xa0, 0x43, 0x10, 0x3d, 0x64, 0x93, + 0xe2, 0xba, 0x38, 0x61, 0x9e, 0x0e, 0x50, 0x1c, 0xf0, 0xda, 0x32, 0xc0, + 0xbf, 0x1e, 0x70, 0x87, 0x4e, 0x81, 0x95, 0x27, 0xd6, 0xa1, 0x0b, 0x73, + 0xfe, 0x12, 0xe3, 0xf1, 0x77, 0x97, 0x53, 0xab, 0x0b, 0x68, 0x31, 0xbd, + 0xe8, 0x01, 0x9b, 0xf2, 0x91, 0xc6, 0x54, 0xa9, 0xa5, 0x57, 0x1b, 0xb4, + 0x2a, 0xaf, 0xd6, 0xf2, 0xef, 0xf4, 0x48, 0x10, 0x99, 0x96, 0xad, 0xac, + 0xca, 0x3c, 0x1f, 0x0c, 0xa3, 0xb1, 0x54, 0xa9, 0x59, 0xe7, 0x29, 0xfd, + 0x06, 0x60, 0xa9, 0x80, 0x28, 0xaf, 0xb9, 0xf6, 0x66, 0x2e, 0x82, 0x47, + 0xcc, 0xb2, 0x3a, 0xd1, 0xbe, 0x83, 0x1f, 0x91, 0xad, 0xaa, 0x1e, 0x72, + 0x1c, 0x4d, 0x75, 0xab, 0xbd, 0x47, 0x23, 0x4d, 0xcc, 0x60, 0x53, 0xea, + 0x66, 0xf5, 0xff, 0x0d, 0x16, 0xf8, 0x1f, 0x02, 0x40, 0x82, 0x1d, 0x72, + 0x16, 0x05, 0x08, 0x6a, 0xbc, 0x72, 0x1d, 0xf7, 0x6c, 0xe2, 0xee, 0x9f, + 0x41, 0x02, 0x73, 0x6b, 0x29, 0x22, 0xf8, 0x4f, 0x3c, 0xbb, 0x49, 0x80, + 0xf9, 0x63, 0xa9, 0x34, 0x35, 0x18, 0xbe, 0x4a, 0xec, 0x36, 0x4e, 0x1c, + 0x22, 0x62, 0x4d, 0xeb, 0xeb, 0xce, 0x38, 0xf8, 0x32, 0x38, 0xb0, 0xe1, + 0x39, 0x08, 0x1c, 0x5f, 0x09, 0x80, 0x12, 0xd0, 0xa8, 0x66, 0xd0, 0x72, + 0x2f, 0x98, 0x6f, 0x93, 0x7c, 0xd7, 0xc2, 0x4c, 0x20, 0x18, 0x19, 0x20, + 0x77, 0xd7, 0xdb, 0x13, 0x00, 0x49, 0x49, 0x3d, 0x3c, 0xeb, 0xe4, 0x45, + 0x5f, 0xbf, 0xd0, 0x68, 0xad, 0xd8, 0x9f, 0xac, 0x02, 0x6c, 0x13, 0x55, + 0xd8, 0x31, 0x91, 0xf5, 0x37, 0x37, 0x82, 0x4e, 0x10, 0xa7, 0xd8, 0x8d, + 0xa0, 0x55, 0x85, 0xfb, 0xeb, 0x90, 0x2a, 0xcb, 0x8d, 0x12, 0x2f, 0xc8, + 0x8d, 0x8d, 0x23, 0x56, 0x95, 0x44, 0x74, 0x34, 0x4f, 0x3f, 0xac, 0x12, + 0x2c, 0x1d, 0x0c, 0x7f, 0x0f, 0x01, 0x04, 0xf2, 0x15, 0x3a, 0xd2, 0xb0, + 0xcd, 0x7a, 0x10, 0x7a, 0x26, 0x91, 0x8f, 0xd4, 0x19, 0xdc, 0xf9, 0x69, + 0xe0, 0xc3, 0x42, 0x39, 0x87, 0x78, 0xf9, 0x2d, 0xae, 0x1a, 0xb8, 0x5e, + 0x83, 0x3d, 0x69, 0xfa, 0x59, 0x07, 0xdd, 0xf3, 0x59, 0x05, 0x27, 0xaf, + 0x29, 0x9a, 0x5b, 0x64, 0xdd, 0xc6, 0x82, 0xcc, 0x3a, 0x4c, 0x83, 0x92, + 0xc6, 0x39, 0x2a, 0xe8, 0x3a, 0xef, 0xde, 0x6d, 0x17, 0x01, 0x0c, 0x59, + 0x6f, 0x39, 0x94, 0xa0, 0xe5, 0x77, 0xfe, 0x5d, 0x8d, 0x8e, 0x29, 0x66, + 0x7a, 0x8f, 0x90, 0x79, 0x6a, 0x1e, 0x9e, 0x10, 0x79, 0xfa, 0x18, 0xdb, + 0x62, 0xe3, 0xe2, 0xc2, 0x4e, 0x22, 0x9c, 0x44, 0x6d, 0x3c, 0xf0, 0xd3, + 0xf4, 0x5a, 0xbd, 0x76, 0x97, 0x90, 0x3b, 0x3a, 0x2a, 0x8c, 0x35, 0x15, + 0xc1, 0xed, 0x86, 0x91, 0xf8, 0x18, 0x31, 0xee, 0x5b, 0x66, 0xfa, 0x05, + 0x69, 0xb3, 0x9f, 0xea, 0xf2, 0x83, 0xe9, 0x5b, 0xa0, 0x83, 0xad, 0x0c, + 0x66, 0x38, 0x56, 0x22, 0x11, 0x1e, 0x0c, 0xf1, 0x8d, 0x5a, 0xd9, 0xa7, + 0xf8, 0x96, 0x79, 0x14, 0x37, 0xeb, 0xbb, 0x15, 0x34, 0xb4, 0x68, 0x27, + 0x42, 0x2f, 0xf1, 0xfe, 0xa6, 0xc9, 0xf0, 0xa8, 0xe2, 0x00, 0xc0, 0x33, + 0x1d, 0x88, 0x89, 0xac, 0x0c, 0x20, 0x72, 0x44, 0xb0, 0x49, 0xc4, 0x65, + 0x71, 0x08, 0xe0, 0x0c, 0x26, 0xff, 0xf8, 0x52, 0xf3, 0x1f, 0x2e, 0x1a, + 0xa5, 0x1c, 0xac, 0x66, 0x70, 0xa2, 0xcd, 0x4e, 0x14, 0x22, 0x3f, 0xf0, + 0x39, 0x5b, 0x89, 0x1c, 0xa2, 0x80, 0xa5, 0xb9, 0x86, 0xdc, 0x91, 0x76, + 0x54, 0x4d, 0x0a, 0x04, 0x6d, 0x8b, 0xe9, 0x6e, 0xcd, 0x96, 0xc2, 0x97, + 0xee, 0x3d, 0x5f, 0xb3, 0xc7, 0x8f, 0x3b, 0xe3, 0x1b, 0x82, 0xbb, 0x77, + 0xf4, 0x78, 0x4f, 0x4a, 0xed, 0x78, 0x19, 0x61, 0x6f, 0x63, 0x71, 0x6c, + 0x3c, 0xa0, 0x65, 0x3e, 0x69, 0xc6, 0x78, 0x7e, 0x2b, 0x50, 0x2a, 0x1a, + 0xf4, 0x42, 0x25, 0x39, 0x93, 0x77, 0x8b, 0x63, 0x3b, 0xca, 0x23, 0x65, + 0x06, 0x33, 0x25, 0x95, 0x08, 0xcb, 0x39, 0xf1, 0x06, 0xb3, 0xab, 0xf3, + 0xf0, 0xe4, 0x75, 0x7c, 0x54, 0x8a, 0x03, 0x40, 0x35, 0xf5, 0x6a, 0x27, + 0xc5, 0x03, 0xad, 0xa5, 0x80, 0xe4, 0xcc, 0x58, 0xa7, 0xee, 0x04, 0xaf, + 0x3d, 0x07, 0x2e, 0x8c, 0x08, 0xa3, 0x69, 0x68, 0xc7, 0x1b, 0x2a, 0x36, + 0x5d, 0xac, 0xfa, 0xc1, 0x0b, 0x93, 0x23, 0xef, 0x39, 0xc9, 0xcf, 0x90, + 0x58, 0x16, 0x7e, 0x62, 0x37, 0x3f, 0x1a, 0xc1, 0xdf, 0x81, 0x35, 0x1f, + 0x88, 0x84, 0x53, 0xf4, 0xfd, 0xe5, 0xd3, 0x30, 0xea, 0x41, 0xca, 0xd8, + 0x47, 0x82, 0x93, 0x18, 0x8e, 0x33, 0xce, 0xe6, 0x47, 0xe5, 0x8a, 0x17, + 0x4a, 0xe0, 0x0e, 0xae, 0xec, 0x0c, 0x95, 0xb7, 0x29, 0x65, 0x02, 0x70, + 0x4d, 0x6d, 0x64, 0x53, 0xf6, 0xce, 0x0d, 0x19, 0xa2, 0xc1, 0x0f, 0xa8, + 0x58, 0x4a, 0x33, 0x6f, 0xfd, 0x0c, 0x03, 0xac, 0xb9, 0x9b, 0x64, 0xf0, + 0xcc, 0xa8, 0x45, 0xaf, 0x2c, 0x2f, 0x60, 0x8a, 0x7d, 0x3c, 0x4e, 0xab, + 0x81, 0x18, 0x60, 0x85, 0x5c, 0xa1, 0x95, 0xb7, 0xcf, 0xb6, 0x3c, 0x54, + 0xdf, 0xff, 0x31, 0xee, 0x3f, 0x6a, 0x78, 0x86, 0x9e, 0x81, 0xda, 0x33, + 0x4c, 0xfc, 0x22, 0x8a, 0xac, 0xd6, 0x62, 0x41, 0x21, 0x1b, 0xbf, 0xe8, + 0x75, 0x3f, 0x92, 0x92, 0xce, 0xc0, 0xcc, 0xee, 0xe7, 0xab, 0xcf, 0xf4, + 0xc3, 0x5e, 0xe0, 0x71, 0x06, 0xd0, 0x4b, 0x5f, 0x17, 0x41, 0x02, 0xd9, + 0xb1, 0xb0, 0x93, 0x5c, 0x72, 0x92, 0x98, 0x69, 0x80, 0xd1, 0xac, 0xda, + 0xbc, 0x4d, 0xe6, 0xab, 0x3e, 0xa8, 0x99, 0xb0, 0x80, 0x32, 0x9a, 0x3f, + 0xc4, 0x93, 0xf9, 0x1f, 0xa7, 0x9a, 0x7c, 0x5d, 0x7a, 0x17, 0x96, 0x2c, + 0x23, 0x00, 0x3f, 0x41, 0x99, 0xf1, 0x6a, 0x89, 0xe9, 0x81, 0xa2, 0xd5, + 0x27, 0x8e, 0xab, 0xac, 0xd0, 0x36, 0x92, 0x57, 0xce, 0x59, 0x5e, 0xa0, + 0x67, 0x96, 0x1c, 0xf1, 0x7e, 0x30, 0xdd, 0xc8, 0x8d, 0x41, 0xe8, 0x30, + 0x5c, 0x34, 0x67, 0xbf, 0xed, 0x4c, 0x65, 0xc0, 0x96, 0x1b, 0xa8, 0x06, + 0xf2, 0x96, 0xe7, 0xe8, 0xea, 0x9e, 0xff, 0xd8, 0xe7, 0xde, 0x2b, 0x8c, + 0x17, 0x0b, 0x50, 0xc7, 0xcf, 0xe6, 0x74, 0x7c, 0xa3, 0xd2, 0x95, 0x9c, + 0x0a, 0xc9, 0x22, 0x79, 0xcf, 0xf6, 0xa4, 0x6f, 0xd5, 0x4b, 0x1a, 0xfa, + 0x9e, 0x9e, 0x7c, 0x95, 0x2f, 0xe2, 0xc2, 0xbb, 0xb6, 0xc4, 0x07, 0x6a, + 0x19, 0x9e, 0xbd, 0xcf, 0x52, 0x6e, 0x36, 0x4b, 0x23, 0xd2, 0xf7, 0x9b, + 0xc4, 0xac, 0x6e, 0xea, 0x10, 0x35, 0xce, 0xf2, 0x61, 0xea, 0xe6, 0x91, + 0x7f, 0xfe, 0x66, 0x6d, 0x56, 0x49, 0x14, 0x68, 0x75, 0xc0, 0x2f, 0xc8, + 0x9c, 0x89, 0xc8, 0x4d, 0x98, 0x27, 0x18, 0x28, 0xff, 0x7f, 0x7e, 0x72, + 0x07, 0xd7, 0xce, 0xf6, 0xd8, 0x2d, 0xd9, 0x46, 0x46, 0xe5, 0xe0, 0x59, + 0xc6, 0x3c, 0x3c, 0x39, 0xc1, 0xf1, 0x39, 0x3e, 0x6a, 0x61, 0x8b, 0x75, + 0xe3, 0xcc, 0x53, 0x2f, 0xfa, 0x43, 0x4e, 0x34, 0x33, 0x33, 0x7f, 0x82, + 0xf7, 0x7f, 0xbf, 0x43, 0x0f, 0xe3, 0xad, 0xfd, 0x0a, 0xc8, 0xc6, 0x93, + 0xec, 0xbd, 0x7c, 0x51, 0xcb, 0xd9, 0x5c, 0x78, 0xe3, 0xcf, 0x2d, 0xb9, + 0x21, 0x41, 0xd0, 0xb5, 0x8b, 0x97, 0xd4, 0xd1, 0xc1, 0x42, 0x2e, 0x64, + 0x60, 0xd8, 0xd4, 0x03, 0xba, 0x41, 0x99, 0x33, 0x2f, 0x4f, 0xff, 0x2a, + 0x60, 0x7a, 0x1d, 0x28, 0xeb, 0x23, 0x59, 0x3f, 0xbd, 0xa9, 0x37, 0x09, + 0xab, 0x05, 0x7d, 0x73, 0x35, 0x85, 0x02, 0xd7, 0x69, 0x2b, 0x88, 0x9c, + 0x42, 0xe7, 0x65, 0xe5, 0x56, 0xe0, 0x0b, 0xe6, 0xdf, 0xdd, 0xa1, 0x6d, + 0x3d, 0xfe, 0xa2, 0x55, 0xff, 0x25, 0x8a, 0x0b, 0x9b, 0x7c, 0x0a, 0xaa, + 0x15, 0x87, 0xf7, 0xa1, 0x3d, 0x9d, 0x53, 0x78, 0x14, 0xd2, 0x3a, 0x8c, + 0x99, 0xfc, 0x1f, 0x54, 0xf0, 0x84, 0x86, 0x8b, 0x3e, 0x6b, 0xef, 0xfd, + 0x6a, 0x09, 0x54, 0xf3, 0xd4, 0xd0, 0x5b, 0x0e, 0xd2, 0xb1, 0xb5, 0x23, + 0xbb, 0x79, 0xf3, 0x49, 0x19, 0xb0, 0xe3, 0xd6, 0xdc, 0x2c, 0xe2, 0x01, + 0x5a, 0xe6, 0x58, 0x5f, 0xf6, 0x11, 0x91, 0x61, 0x88, 0xcf, 0x1c, 0x92, + 0xf1, 0xbf, 0xdf, 0x5e, 0xab, 0xca, 0x48, 0x87, 0x1d, 0x31, 0x7d, 0x1d, + 0x04, 0xee, 0x45, 0xad, 0xdd, 0xce, 0xd5, 0xeb, 0x9d, 0x57, 0x00, 0x08, + 0x56, 0x55, 0x6f, 0xfa, 0xd0, 0xc5, 0x96, 0x65, 0x90, 0xe4, 0xde, 0x2a, + 0x80, 0xd2, 0xd2, 0xe6, 0x46, 0xdb, 0x73, 0xba, 0x8a, 0x7c, 0xa0, 0x1e, + 0x04, 0x12, 0x99, 0xdd, 0xbf, 0x04, 0x1e, 0x5b, 0x4e, 0xf2, 0x9d, 0xc0, + 0x58, 0xc8, 0x5b, 0x30, 0x55, 0x09, 0xba, 0xf3, 0xf1, 0x3d, 0x0b, 0x88, + 0x8c, 0xa2, 0xd4, 0xf1, 0xf7, 0xca, 0xb2, 0x7d, 0xa4, 0x7d, 0x53, 0x76, + 0x46, 0x3f, 0x5b, 0x24, 0x07, 0xdb, 0xb8, 0x8f, 0xab, 0xe4, 0x3f, 0x5d, + 0x2e, 0x3d, 0x73, 0xcc, 0xe7, 0x24, 0xe7, 0x89, 0x0c, 0xb5, 0x60, 0xcb, + 0x57, 0x7c, 0x15, 0xb3, 0x8a, 0x88, 0x1f, 0xeb, 0x3b, 0x98, 0x62, 0xb9, + 0x15, 0xbe, 0xad, 0x67, 0x90, 0xc0, 0xeb, 0x36, 0x00, 0x37, 0xfd, 0x8a, + 0x98, 0x0a, 0x5a, 0x7e, 0xa9, 0xfc, 0x83, 0x96, 0xdb, 0xc9, 0x5e, 0xaa, + 0xbb, 0x16, 0x63, 0x4f, 0x4f, 0xab, 0x8f, 0xd1, 0x9d, 0xdd, 0xa1, 0xae, + 0x09, 0xa0, 0xb7, 0x1c, 0x87, 0x26, 0x54, 0x38, 0x57, 0xde, 0x61, 0xf9, + 0x3e, 0xcd, 0x97, 0xc7, 0xa0, 0xfe, 0x58, 0xae, 0x6a, 0x19, 0x4e, 0xda, + 0x46, 0x6c, 0x72, 0x9d, 0xe5, 0x78, 0x07, 0x01, 0x2a, 0x82, 0x83, 0x02, + 0xd2, 0x57, 0xaf, 0x6b, 0x3a, 0xdb, 0xe9, 0xe2, 0xc1, 0x1a, 0xb3, 0x15, + 0xe5, 0x19, 0xa1, 0x8b, 0xa1, 0x54, 0xe2, 0x58, 0xee, 0xff, 0x73, 0xb1, + 0x78, 0xeb, 0xa5, 0x7d, 0x06, 0xa4, 0xed, 0xb2, 0xc9, 0x62, 0x41, 0xdd, + 0x20, 0xbe, 0x3d, 0x1c, 0x13, 0xa7, 0xc5, 0x30, 0xa3, 0xe0, 0x42, 0x00, + 0x20, 0x02, 0x26, 0x85, 0x9d, 0xb0, 0x44, 0xcc, 0x91, 0xb7, 0xf0, 0xb6, + 0x5c, 0x76, 0xd0, 0x48, 0x06, 0x60, 0xb6, 0x8a, 0x77, 0x61, 0x9f, 0x8f, + 0x1b, 0xf4, 0x66, 0xb2, 0xdb, 0xa9, 0xf9, 0x37, 0xae, 0x60, 0xb8, 0xd3, + 0x25, 0xa1, 0x2d, 0xc5, 0xb3, 0x3f, 0x0a, 0x95, 0x83, 0x20, 0xb8, 0xef, + 0xf0, 0x7b, 0xe8, 0x2a, 0xbd, 0x9c, 0x7f, 0x9d, 0x7d, 0x8e, 0x29, 0x31, + 0xdd, 0x86, 0x40, 0x59, 0xa9, 0xd6, 0x58, 0x90, 0xec, 0xd2, 0xac, 0x13, + 0x7f, 0xdf, 0x57, 0x49, 0x4a, 0xa8, 0x66, 0x31, 0xe8, 0x0a, 0x87, 0x88, + 0xee, 0xec, 0x1e, 0x3a, 0x2f, 0x50, 0x64, 0xbf, 0x0c, 0x08, 0x61, 0x36, + 0xf4, 0x94, 0xa7, 0x8c, 0x92, 0x92, 0x21, 0xec, 0x7d, 0x9c, 0xed, 0x8f, + 0x81, 0x55, 0x89, 0x70, 0x6a, 0x6b, 0xf0, 0xf1, 0x39, 0x4e, 0xd7, 0xc4, + 0x4b, 0x8e, 0xb5, 0x19, 0xca, 0x81, 0xd8, 0x3e, 0x32, 0x8e, 0x8b, 0x53, + 0xcd, 0xbd, 0x71, 0x80, 0xa4, 0x3c, 0x10, 0x15, 0x47, 0x33, 0xe0, 0xac, + 0x45, 0x67, 0x30, 0xc8, 0x70, 0x1c, 0x37, 0x8d, 0x93, 0x8a, 0x07, 0x32, + 0x4b, 0x26, 0xe1, 0x2e, 0xa6, 0xf9, 0x78, 0xa6, 0x48, 0xd8, 0xcc, 0xfa, + 0x9e, 0x85, 0x8c, 0x82, 0x7d, 0xea, 0x58, 0x62, 0xc3, 0x53, 0x8d, 0x52, + 0xad, 0x8d, 0x27, 0x44, 0x34, 0x16, 0x4a, 0x0f, 0x41, 0x80, 0xda, 0x57, + 0x29, 0xca, 0x88, 0xba, 0xb2, 0x63, 0x95, 0x5b, 0x0e, 0x8f, 0xac, 0x99, + 0xc4, 0x95, 0x12, 0xca, 0xc2, 0x3b, 0x96, 0xaa, 0xb3, 0x03, 0x57, 0x23, + 0x90, 0xb2, 0xe3, 0x05, 0xe1, 0x94, 0x9e, 0x11, 0x3e, 0xfe, 0x13, 0x60, + 0x2d, 0x0e, 0x55, 0x89, 0xa8, 0xa7, 0xe7, 0xbe, 0x57, 0x86, 0x2c, 0x69, + 0x5e, 0xf7, 0x0d, 0x86, 0x43, 0xc3, 0x56, 0x27, 0x45, 0x3c, 0xb9, 0xd7, + 0x11, 0xf7, 0xd9, 0x7f, 0x78, 0x7c, 0xb2, 0x54, 0xad, 0xdd, 0xdd, 0x9a, + 0xbe, 0xb1, 0x8a, 0x9e, 0x9d, 0xb2, 0x71, 0x35, 0x97, 0x60, 0x71, 0x21, + 0xe0, 0x5b, 0xa2, 0x51, 0x66, 0x12, 0x12, 0x2a, 0x25, 0x71, 0xb6, 0xf6, + 0x49, 0xc0, 0xd6, 0xb1, 0xb1, 0x46, 0x36, 0x16, 0x9d, 0x18, 0x91, 0xae, + 0x8f, 0x40, 0x09, 0xf7, 0x87, 0xb2, 0xd6, 0xa4, 0xb2, 0x7d, 0xb0, 0x76, + 0x36, 0xf3, 0x13, 0xa8, 0x0b, 0x9a, 0xd2, 0x32, 0xcb, 0xec, 0x86, 0x55, + 0x33, 0xe4, 0x37, 0x88, 0x3e, 0x01, 0xc1, 0x88, 0x11, 0x04, 0x75, 0xf6, + 0xc2, 0x95, 0xe1, 0x00, 0xae, 0xd9, 0x35, 0xdc, 0xbd, 0xcc, 0x47, 0xb4, + 0x3b, 0xa7, 0xe6, 0x35, 0x8a, 0x05, 0xd9, 0x69, 0x32, 0x8f, 0xc3, 0x5c, + 0xc1, 0x51, 0xaa, 0x44, 0x51, 0xcf, 0xad, 0x57, 0x29, 0xb0, 0x17, 0x48, + 0x92, 0xf1, 0x3a, 0xfc, 0xfb, 0x74, 0x60, 0xc9, 0xe2, 0x30, 0xb1, 0x27, + 0x8e, 0x1e, 0x0c, 0x2e, 0x19, 0x50, 0xdd, 0x34, 0xeb, 0x2a, 0x02, 0x02, + 0xb2, 0xcf, 0xa0, 0x7e, 0x8c, 0x39, 0x9a, 0x1d, 0xd3, 0x3e, 0x93, 0xc7, + 0xfc, 0xb2, 0xbc, 0xdf, 0xbe, 0x04, 0xfe, 0xb1, 0x10, 0xa4, 0x46, 0xd9, + 0xcb, 0xb1, 0xfc, 0x8e, 0x54, 0x0b, 0x88, 0xbb, 0x2e, 0x0a, 0xb0, 0x56, + 0xb7, 0x85, 0x2e, 0x32, 0xf6, 0x79, 0x84, 0x6f, 0x8f, 0x83, 0x27, 0x35, + 0xff, 0x17, 0x7d, 0xa3, 0x1f, 0x0d, 0x70, 0x12, 0xdf, 0x64, 0x2d, 0x8e, + 0x92, 0xf7, 0x56, 0x9c, 0x9f, 0xa3, 0xaf, 0x2e, 0x32, 0xe1, 0x24, 0xc0, + 0x3d, 0x1d, 0x2d, 0x7a, 0x0b, 0xb4, 0xf8, 0xb0, 0x74, 0xed, 0xc9, 0xb0, + 0xe7, 0x29, 0x61, 0xc6, 0x0e, 0x9c, 0x0d, 0x30, 0xbc, 0x95, 0xb9, 0x51, + 0xa9, 0x06, 0xc2, 0x17, 0xaf, 0x81, 0x11, 0x22, 0x1a, 0x4d, 0x18, 0xd5, + 0x53, 0xc0, 0x92, 0xd5, 0x44, 0x50, 0x99, 0x6d, 0x4a, 0x8e, 0x4f, 0xde, + 0x1b, 0x39, 0x52, 0x4b, 0x2d, 0xf6, 0xa0, 0xfd, 0xf3, 0x69, 0xcc, 0x75, + 0xd6, 0xf4, 0x0f, 0x14, 0xf8, 0x3c, 0x51, 0xe7, 0x04, 0x69, 0x1b, 0x5f, + 0x88, 0xd6, 0xdd, 0xe4, 0x50, 0x8d, 0x61, 0x0f, 0x34, 0x74, 0x0b, 0xfa, + 0xdd, 0xb0, 0x26, 0x19, 0x31, 0xb1, 0x3d, 0x2d, 0xa7, 0x1b, 0xda, 0x2a, + 0x32, 0x21, 0x6c, 0xd8, 0xca, 0xf7, 0x60, 0xb8, 0x0f, 0xca, 0x04, 0x50, + 0x61, 0x1f, 0xb4, 0x05, 0xd0, 0x2f, 0x5a, 0x2f, 0xf0, 0x2d, 0xfc, 0xf9, + 0xdf, 0x62, 0x5c, 0x99, 0x9a, 0xec, 0x26, 0xf7, 0x11, 0x22, 0x19, 0x72, + 0x51, 0x7a, 0x7e, 0x33, 0xb7, 0x1f, 0x4e, 0x7f, 0xb7, 0xde, 0xfd, 0xdf, + 0x9e, 0xbf, 0x1d, 0x3e, 0x9d, 0x5a, 0xcd, 0x42, 0xde, 0xab, 0x3b, 0x4c, + 0x7e, 0x66, 0xb3, 0x53, 0x2c, 0x31, 0x31, 0x5c, 0xaf, 0x3a, 0xe4, 0x80, + 0x03, 0xca, 0xa0, 0x13, 0x68, 0x76, 0x61, 0x57, 0x4f, 0x50, 0x0a, 0x47, + 0x17, 0x64, 0x2d, 0x02, 0x9d, 0x3e, 0x47, 0x33, 0x46, 0x4c, 0x77, 0xee, + 0x2c, 0x31, 0x86, 0xd6, 0xd9, 0x42, 0xf1, 0x93, 0xfa, 0x66, 0x95, 0x5c, + 0x59, 0x47, 0xda, 0x46, 0x2e, 0xd2, 0x14, 0x13, 0x0e, 0xb8, 0x1f, 0xb2, + 0x02, 0xd0, 0xdc, 0xab, 0xb8, 0x31, 0xc4, 0xd3, 0xfe, 0x46, 0x3f, 0x2b, + 0xea, 0xf9, 0xc7, 0x71, 0xe4, 0x1e, 0x83, 0x47, 0x3f, 0xe3, 0x9d, 0xea, + 0xb9, 0x97, 0x74, 0x70, 0x5e, 0x13, 0x09, 0x10, 0xef, 0x50, 0x6a, 0x5d, + 0x6e, 0x02, 0xa9, 0xf1, 0x09, 0xd2, 0xfd, 0x45, 0x6d, 0x5e, 0xc4, 0x94, + 0xca, 0xc2, 0xef, 0x49, 0x78, 0x0f, 0x2b, 0x48, 0x6d, 0x65, 0x0f, 0xa7, + 0x4c, 0x41, 0x3e, 0x00, 0x6e, 0x19, 0x5a, 0x45, 0xdd, 0x6a, 0x1d, 0xa4, + 0x71, 0x2d, 0x6b, 0x35, 0xd2, 0x46, 0xba, 0x33, 0x8b, 0xd2, 0x4e, 0x9c, + 0xb0, 0x3b, 0x46, 0x28, 0xe3, 0xa8, 0x55, 0x97, 0x90, 0x39, 0xf5, 0x69, + 0x36, 0xa6, 0x42, 0x25, 0x36, 0x7f, 0x60, 0xb1, 0x20, 0xa7, 0x8f, 0x4e, + 0xc5, 0x21, 0x82, 0xaa, 0x55, 0xae, 0xed, 0xef, 0xe3, 0xd0, 0xa8, 0x5e, + 0xa1, 0x14, 0x76, 0x35, 0x61, 0xab, 0xd1, 0x3e, 0x12, 0x97, 0x68, 0x8e, + 0x53, 0x7b, 0x49, 0xb0, 0x31, 0x2b, 0x36, 0x95, 0xcc, 0x84, 0x1a, 0x36, + 0xce, 0x16, 0x4a, 0x82, 0x1b, 0x51, 0x4a, 0xec, 0x79, 0xf6, 0x37, 0x2a, + 0x1f, 0x87, 0xc2, 0x83, 0x0f, 0x97, 0xce, 0xf4, 0xaf, 0xc6, 0x46, 0x3b, + 0xf1, 0x96, 0x3f, 0x69, 0xbc, 0xc8, 0xfc, 0x93, 0x7f, 0x46, 0x7b, 0xfd, + 0x72, 0x05, 0x13, 0x91, 0x55, 0xad, 0x71, 0x6b, 0x9d, 0xe0, 0x78, 0x3f, + 0xd6, 0x11, 0x84, 0xc7, 0xf4, 0x40, 0x89, 0xcc, 0xae, 0x79, 0x8c, 0x67, + 0xb6, 0xad, 0xe8, 0x9e, 0x11, 0x1a, 0xa7, 0x61, 0x92, 0x15, 0xe2, 0xc3, + 0xa1, 0x19, 0xb5, 0xfe, 0x1b, 0xe7, 0x21, 0xaf, 0x3f, 0xc4, 0xf8, 0x85, + 0xf4, 0x2c, 0xa1, 0xe8, 0x2d, 0xff, 0x15, 0x53, 0x0b, 0x7f, 0xc2, 0x64, + 0x38, 0xef, 0xb8, 0x10, 0xae, 0x2d, 0x46, 0x61, 0xf1, 0x31, 0x17, 0xeb, + 0x88, 0x7f, 0x6d, 0x33, 0x50, 0x70, 0x1d, 0xeb, 0xff, 0x27, 0x25, 0xc4, + 0xf2, 0x37, 0xc5, 0x15, 0xbf, 0x6d, 0x3d, 0xa4, 0x95, 0x01, 0x60, 0xb0, + 0xca, 0xe1, 0x7b, 0xe9, 0x60, 0xc7, 0xc9, 0x13, 0xce, 0x6d, 0x0a, 0xe4, + 0xc1, 0xda, 0x08, 0xf3, 0x18, 0x38, 0x2d, 0x0b, 0x51, 0x86, 0x67, 0x84, + 0x81, 0xcf, 0xab, 0x6c, 0xbc, 0x4b, 0x0f, 0x33, 0x72, 0x03, 0xaa, 0x81, + 0x3f, 0x6e, 0x71, 0x5d, 0xea, 0xac, 0x25, 0x03, 0x93, 0x7b, 0xa4, 0x5f, + 0xba, 0xc1, 0x50, 0x4a, 0x5c, 0xdc, 0xb2, 0xdd, 0xab, 0x6b, 0x8e, 0xbf, + 0x65, 0xc3, 0x74, 0xf4, 0xb2, 0x7c, 0x65, 0x3c, 0xf0, 0xc8, 0x93, 0x38, + 0xc2, 0xaa, 0x94, 0x2a, 0x9d, 0x86, 0x4a, 0x50, 0xf7, 0x63, 0xad, 0xa6, + 0xdd, 0x06, 0xd5, 0xad, 0xc0, 0x95, 0xed, 0x22, 0x4c, 0x43, 0x7f, 0xe7, + 0xb5, 0x55, 0x18, 0xd9, 0x19, 0xa9, 0xbd, 0x30, 0x01, 0x67, 0xe4, 0x13, + 0x19, 0xf4, 0x1f, 0xb4, 0xe2, 0x81, 0xdf, 0x0d, 0x76, 0xca, 0x9b, 0xc0, + 0xfe, 0xc7, 0x50, 0x9e, 0x84, 0x14, 0x16, 0x6b, 0x42, 0xdf, 0xf5, 0x06, + 0x72, 0xac, 0x67, 0x6e, 0x00, 0x25, 0xfc, 0xae, 0xda, 0x4d, 0xa3, 0xdc, + 0x38, 0x74, 0x4e, 0xd7, 0x8a, 0x1b, 0x50, 0xd6, 0xbc, 0xb7, 0xef, 0x84, + 0x0f, 0x7d, 0x24, 0x6a, 0x87, 0xb8, 0xe8, 0x7b, 0x97, 0xa7, 0x89, 0x8d, + 0x58, 0x9a, 0xcc, 0x2c, 0x07, 0x1b, 0x0c, 0xf0, 0xaf, 0xc3, 0xf5, 0x2e, + 0x94, 0x46, 0x67, 0xac, 0xe4, 0x65, 0xb1, 0x79, 0x0b, 0x95, 0xf8, 0x29, + 0x59, 0x05, 0x1a, 0x00, 0x7b, 0xb7, 0x0a, 0x94, 0xa8, 0x5d, 0xc1, 0x37, + 0x10, 0x32, 0x65, 0x19, 0x43, 0x2f, 0xf9, 0xf3, 0xf6, 0x0c, 0x5b, 0x2a, + 0xfc, 0xb8, 0xa0, 0xd4, 0x38, 0xed, 0x25, 0x49, 0xd1, 0xae, 0x68, 0x77, + 0x97, 0x7e, 0xa5, 0xaf, 0xbb, 0x76, 0xa6, 0xe5, 0xd9, 0x00, 0xe3, 0x3a, + 0xf0, 0xf1, 0xd2, 0x4f, 0x01, 0xd9, 0x26, 0xba, 0xa2, 0x1d, 0xe2, 0x0f, + 0x57, 0xd8, 0x48, 0x7f, 0x5e, 0xd6, 0x8b, 0x1b, 0xf2, 0x79, 0x76, 0xbc, + 0xc9, 0xa5, 0x8c, 0x36, 0x26, 0x46, 0x5c, 0xc5, 0x10, 0x51, 0x19, 0xeb, + 0x91, 0xac, 0xc5, 0x9a, 0xd4, 0x86, 0xed, 0x60, 0x31, 0x25, 0xaa, 0x17, + 0x5e, 0x2a, 0x8d, 0x0d, 0x99, 0x41, 0xc9, 0xd8, 0xf0, 0x60, 0xec, 0x64, + 0x39, 0xbd, 0xbb, 0x41, 0x1f, 0xa3, 0xbb, 0x3a, 0xf1, 0x85, 0xb0, 0xc3, + 0x26, 0xe4, 0xfa, 0x86, 0x81, 0x01, 0x05, 0xf0, 0xe2, 0xbf, 0xd1, 0x9f, + 0x17, 0xe8, 0xdd, 0xa1, 0xa0, 0xfd, 0x7f, 0x8e, 0xc8, 0x52, 0xbc, 0x82, + 0x43, 0x80, 0x4a, 0xce, 0x50, 0xce, 0x10, 0x7c, 0x1a, 0xfa, 0x8e, 0xcb, + 0xf8, 0x1e, 0x27, 0x46, 0x09, 0x32, 0xee, 0x71, 0x68, 0x0e, 0x74, 0x8f, + 0xf8, 0x41, 0x84, 0x69, 0x2a, 0x6c, 0x0f, 0x25, 0x00, 0x7f, 0x16, 0xb0, + 0x06, 0xf6, 0x41, 0x28, 0xa3, 0x30, 0x53, 0x9a, 0xd0, 0xd8, 0xd0, 0xfa, + 0x7d, 0xcb, 0xd2, 0x42, 0x2c, 0x73, 0xcf, 0x2c, 0xf7, 0xe6, 0xa2, 0x35, + 0x9d, 0x84, 0x5b, 0x8d, 0x4f, 0x0f, 0xb3, 0xa9, 0x61, 0x4e, 0x70, 0xa6, + 0xcc, 0xd0, 0xb6, 0x16, 0x0d, 0x2b, 0x2f, 0x13, 0x68, 0x61, 0xb4, 0x73, + 0x64, 0xd7, 0x45, 0xff, 0xb9, 0xf1, 0xe7, 0xa5, 0xd6, 0x9c, 0x7f, 0x0b, + 0x7e, 0x17, 0xf4, 0x73, 0x71, 0xf3, 0x8b, 0x95, 0x2e, 0xb1, 0xcf, 0xc7, + 0xb1, 0x5e, 0xda, 0xe5, 0x57, 0x7a, 0xf8, 0xf4, 0x2b, 0x52, 0xbb, 0x8a, + 0xed, 0xd0, 0xc8, 0x15, 0x63, 0xa1, 0xeb, 0x79, 0x06, 0x89, 0x3c, 0xd8, + 0x75, 0x69, 0xc2, 0xce, 0xe6, 0xbe, 0xcf, 0x15, 0x7e, 0xac, 0xb2, 0xa6, + 0xb6, 0x72, 0x33, 0xad, 0xfd, 0x06, 0x6a, 0xaf, 0x4b, 0xf5, 0x1a, 0x29, + 0x38, 0xde, 0x75, 0xdf, 0x4b, 0x5b, 0x11, 0xf4, 0x48, 0xb1, 0x18, 0x3c, + 0xeb, 0x79, 0x1f, 0xa2, 0xcd, 0xa7, 0x31, 0x78, 0x5e, 0x37, 0xd0, 0x4b, + 0x7f, 0x6b, 0xf4, 0xca, 0xaf, 0x43, 0xc9, 0xc8, 0x96, 0x02, 0x0e, 0xd1, + 0x51, 0xb0, 0x8c, 0xb6, 0xc5, 0xc4, 0xdd, 0xa5, 0x43, 0xd2, 0x11, 0x6f, + 0xd8, 0xb0, 0x76, 0xef, 0xdd, 0xd0, 0x39, 0x9b, 0x23, 0x39, 0x8f, 0x3a, + 0x19, 0x90, 0xdd, 0x57, 0xf0, 0x95, 0xc3, 0xc5, 0x1c, 0x52, 0xe2, 0xbd, + 0x5f, 0xb3, 0xbb, 0xfb, 0x90, 0xf9, 0x47, 0xad, 0x2c, 0x65, 0x67, 0x91, + 0xb3, 0xd4, 0x67, 0x9f, 0xb7, 0xf1, 0x2b, 0x82, 0xdc, 0x42, 0x2a, 0x1e, + 0x55, 0x25, 0xbe, 0x7b, 0x1b, 0x6c, 0x05, 0xd1, 0xfa, 0x9f, 0x45, 0xbb, + 0x4c, 0x66, 0x77, 0x42, 0x3a, 0xdf, 0xbe, 0x45, 0xcf, 0xd7, 0x32, 0x26, + 0x21, 0x05, 0xce, 0x2b, 0x06, 0x5e, 0xf5, 0x38, 0xde, 0x92, 0x49, 0xda, + 0x75, 0xe6, 0x6e, 0x55, 0x45, 0x38, 0x4b, 0xfd, 0x48, 0x1c, 0x08, 0x32, + 0xf6, 0x3e, 0x06, 0xfb, 0x0c, 0xbc, 0xdc, 0x07, 0xac, 0x55, 0x29, 0x40, + 0xd3, 0xd4, 0xe2, 0x44, 0x7c, 0x94, 0x1a, 0xe6, 0xc2, 0x7f, 0x84, 0x5c, + 0x29, 0xbd, 0x64, 0x5e, 0x73, 0x13, 0xcc, 0x69, 0x63, 0xc6, 0x99, 0x55, + 0x65, 0x56, 0x41, 0xb1, 0x7a, 0xb8, 0x5f, 0x57, 0x73, 0x7d, 0x89, 0x17, + 0x09, 0x9c, 0x52, 0x45, 0x18, 0xd6, 0x51, 0x17, 0xc5, 0xee, 0xf9, 0x1a, + 0x8b, 0x13, 0x37, 0xb7, 0xe5, 0xb8, 0x48, 0x70, 0x70, 0x52, 0x1b, 0x14, + 0xb1, 0xa7, 0x23, 0xf2, 0x49, 0x6f, 0xc7, 0xd7, 0x5c, 0x84, 0xce, 0x30, + 0xf7, 0x92, 0xf6, 0xd3, 0x6e, 0xe4, 0x86, 0xcb, 0x08, 0x7c, 0xd6, 0xbc, + 0x8c, 0xe9, 0xa6, 0x1e, 0x48, 0x09, 0x99, 0x18, 0x99, 0xe1, 0x93, 0xab, + 0x3b, 0xbb, 0xd2, 0xa7, 0x0a, 0x88, 0xfe, 0x39, 0x73, 0xe5, 0x69, 0x3a, + 0xb9, 0xbb, 0x2e, 0x99, 0x68, 0x68, 0xbf, 0x63, 0x11, 0x82, 0x8b, 0xf8, + 0x86, 0xd0, 0xe2, 0x67, 0x32, 0x47, 0xa3, 0xd6, 0xee, 0x0c, 0xae, 0xd0, + 0xc1, 0x06, 0xdc, 0x16, 0xd8, 0x2f, 0xa4, 0x56, 0x40, 0xc9, 0x8d, 0xb5, + 0xca, 0x69, 0x75, 0x98, 0xe9, 0x80, 0xf4, 0x9d, 0x08, 0x0c, 0x76, 0xba, + 0x92, 0x64, 0x53, 0xc7, 0xc1, 0x6c, 0x14, 0x46, 0xa5, 0x88, 0x7c, 0x4f, + 0xe0, 0x5b, 0x9d, 0xa8, 0xad, 0xd1, 0x10, 0xf0, 0x48, 0x6c, 0x2f, 0xe9, + 0x81, 0xb7, 0x26, 0x58, 0x6c, 0x77, 0x01, 0x52, 0x61, 0x56, 0x00, 0x77, + 0xb0, 0xfe, 0xbc, 0x6c, 0xda, 0x87, 0xa2, 0x83, 0x5c, 0x96, 0x44, 0x95, + 0xe4, 0x7d, 0xae, 0x0f, 0x6d, 0xeb, 0xa3, 0xe5, 0x38, 0x8d, 0x28, 0xb9, + 0x0c, 0x9d, 0x76, 0x5a, 0xcc, 0x58, 0xea, 0x15, 0x3f, 0x76, 0xbc, 0x16, + 0xc7, 0x37, 0x79, 0x5a, 0x2e, 0xb2, 0x2e, 0x01, 0xcd, 0xd5, 0x08, 0x0f, + 0x70, 0xa5, 0x9a, 0x4c, 0xa2, 0x74, 0x79, 0x2c, 0xc8, 0x7f, 0x63, 0xc0, + 0xe3, 0x31, 0xb6, 0x3f, 0x44, 0x0e, 0xa2, 0x82, 0x7f, 0x2c, 0x5c, 0xd7, + 0x17, 0x3e, 0x33, 0x95, 0xfe, 0x32, 0xfb, 0x1a, 0x28, 0x09, 0xcf, 0xac, + 0x9c, 0x97, 0x60, 0xa3, 0xeb, 0x6d, 0xb5, 0x96, 0x4e, 0x2e, 0xf2, 0x43, + 0x13, 0xc2, 0x8e, 0xe3, 0xb6, 0x3c, 0x7f, 0xb4, 0xb8, 0xfe, 0x99, 0xdb, + 0x40, 0xb4, 0x25, 0x3a, 0x04, 0xfa, 0x24, 0x42, 0x59, 0xeb, 0x78, 0xcd, + 0x9c, 0x34, 0x94, 0x31, 0xea, 0x18, 0xc4, 0x1e, 0xb8, 0x52, 0xdc, 0x7b, + 0x8a, 0x94, 0xb0, 0x4c, 0x09, 0x1d, 0xb0, 0x35, 0xd4, 0xb2, 0xa4, 0xd2, + 0xd2, 0xc8, 0x71, 0xeb, 0xf0, 0x12, 0x6d, 0xf8, 0x0e, 0x68, 0xe2, 0xc2, + 0x86, 0xf7, 0x17, 0xf3, 0x1c, 0xa7, 0x8d, 0x1f, 0xc1, 0xb1, 0x14, 0xec, + 0xa1, 0x73, 0x33, 0x55, 0x68, 0xc5, 0x2d, 0x22, 0x1a, 0x16, 0xa5, 0x08, + 0xce, 0xac, 0x44, 0xf2, 0xae, 0xed, 0x75, 0xa4, 0x3f, 0x33, 0xfa, 0x49, + 0x15, 0xf5, 0xfe, 0xe7, 0xcf, 0x97, 0x55, 0x1f, 0x58, 0x01, 0x3a, 0xe1, + 0xcb, 0xba, 0xc8, 0xde, 0xda, 0xe6, 0x2d, 0x0a, 0xac, 0xcc, 0xcb, 0x1e, + 0xcd, 0x63, 0xdf, 0xa7, 0x47, 0xc4, 0x2d, 0xc3, 0x82, 0xfd, 0x22, 0x87, + 0xd4, 0x4b, 0xda, 0x28, 0x8e, 0x2f, 0x72, 0x95, 0x3e, 0x4f, 0xed, 0x00, + 0xe3, 0xf9, 0x42, 0xb4, 0x78, 0xd1, 0xc0, 0x39, 0xb5, 0x6e, 0x53, 0xfc, + 0xed, 0xf3, 0x8f, 0x4f, 0x96, 0x9a, 0x59, 0x47, 0xee, 0x5f, 0x60, 0xd0, + 0xa4, 0xcb, 0x5c, 0x60, 0x0b, 0x49, 0x6c, 0x12, 0xca, 0x99, 0xac, 0xdd, + 0xfb, 0x56, 0x2c, 0x3e, 0x5e, 0x07, 0xd4, 0x3e, 0x0e, 0x98, 0x5e, 0x17, + 0x58, 0x79, 0x41, 0x5f, 0x8e, 0xa0, 0x18, 0xa1, 0x14, 0x66, 0x92, 0x99, + 0x81, 0xdb, 0x23, 0xa8, 0x86, 0x21, 0x6a, 0x4d, 0x16, 0x6f, 0xa6, 0xf1, + 0xfe, 0x7c, 0xd4, 0xc4, 0x5e, 0x28, 0xb3, 0x96, 0x1e, 0x80, 0xab, 0x75, + 0x66, 0x9e, 0xdb, 0x0a, 0x2b, 0x8c, 0x32, 0x7d, 0xfb, 0x70, 0x0d, 0x9a, + 0x56, 0xfb, 0xc6, 0x89, 0x21, 0x7b, 0x60, 0xda, 0x1d, 0x1c, 0x19, 0xa9, + 0x3b, 0x6f, 0x78, 0xe7, 0xc0, 0x5d, 0xe0, 0x12, 0xd4, 0x35, 0x0f, 0x8f, + 0x01, 0xac, 0x7f, 0xb5, 0x70, 0x4e, 0x6d, 0xc5, 0xac, 0x96, 0x8f, 0xb9, + 0xc1, 0xe2, 0x4e, 0x58, 0xb2, 0xc6, 0x57, 0x6b, 0x16, 0xbc, 0x07, 0xde, + 0x61, 0x48, 0x6f, 0x64, 0x00, 0x32, 0xc0, 0xdb, 0xb3, 0xcc, 0x8d, 0xfd, + 0xc1, 0xfb, 0x17, 0xf8, 0x46, 0xab, 0x52, 0x0a, 0x90, 0x19, 0x95, 0x9f, + 0xdf, 0xc4, 0x4c, 0x83, 0xd5, 0x8c, 0x8a, 0xa5, 0xae, 0xd6, 0x8e, 0x89, + 0x3b, 0xcc, 0x94, 0xd6, 0xaa, 0xa8, 0x8f, 0x0d, 0xc0, 0xbb, 0x2d, 0xe6, + 0x75, 0xf2, 0xb4, 0x59, 0x89, 0xd3, 0xaa, 0x27, 0x1e, 0x74, 0x96, 0xdc, + 0xdb, 0x78, 0xb4, 0x12, 0x64, 0xc6, 0x8d, 0x1a, 0xef, 0x23, 0x5e, 0x0f, + 0xfa, 0xed, 0xc1, 0x9a, 0x99, 0xe6, 0x48, 0x14, 0x52, 0x5c, 0xa7, 0x72, + 0x48, 0xb0, 0x15, 0x43, 0xd5, 0x8d, 0x98, 0x53, 0x32, 0xe3, 0x5d, 0x88, + 0x89, 0x5b, 0x9a, 0x81, 0x4f, 0xdb, 0xdc, 0x6b, 0x9f, 0x21, 0x5a, 0x26, + 0xe2, 0x72, 0xaa, 0xbf, 0xdf, 0xf6, 0xa5, 0x73, 0x1f, 0x5e, 0x08, 0xf5, + 0x52, 0xf8, 0x73, 0xb1, 0x0b, 0xe8, 0x01, 0x9a, 0xf5, 0x4f, 0x88, 0x2f, + 0x4f, 0xe4, 0x1d, 0x4f, 0xa7, 0xdb, 0x97, 0x9a, 0x75, 0x9f, 0x45, 0x01, + 0x8b, 0x15, 0xed, 0xbc, 0x38, 0x7a, 0x16, 0x63, 0x0e, 0x28, 0x26, 0xfc, + 0x36, 0xce, 0x9c, 0xf7, 0xe4, 0x84, 0xc1, 0x54, 0x12, 0x6d, 0xe2, 0xba, + 0x22, 0xe5, 0x25, 0x3f, 0xf0, 0xdf, 0x52, 0xef, 0xe7, 0x92, 0x90, 0x01, + 0x00, 0x68, 0x2b, 0xe7, 0xab, 0x03, 0xd7, 0x46, 0x0b, 0xff, 0x91, 0x24, + 0xeb, 0xcb, 0xc8, 0x42, 0xdb, 0x60, 0xd1, 0x58, 0x3a, 0x42, 0x9e, 0x62, + 0x7c, 0xea, 0xe0, 0x64, 0x2d, 0xb3, 0x68, 0xa8, 0xff, 0x20, 0x9c, 0x86, + 0xb2, 0x07, 0xac, 0xcb, 0xa3, 0x7e, 0x02, 0x9d, 0x40, 0xfc, 0x11, 0x57, + 0xdc, 0x22, 0xd4, 0x84, 0x1b, 0xbb, 0x1c, 0xb8, 0xfd, 0xe3, 0xf5, 0xe8, + 0x44, 0x85, 0x84, 0x30, 0xb9, 0xc7, 0x18, 0xb2, 0xf3, 0xd4, 0xc0, 0xc5, + 0x97, 0x92, 0x8c, 0xb8, 0x46, 0xa2, 0x1a, 0x74, 0x6f, 0x58, 0xd2, 0xf4, + 0x57, 0xaf, 0xa8, 0x53, 0xd6, 0x0a, 0xc5, 0xfc, 0xc4, 0x8a, 0xa6, 0x74, + 0xed, 0x0c, 0xd6, 0x5f, 0x8a, 0x56, 0x0b, 0xf5, 0xa7, 0xc0, 0x5a, 0x5f, + 0x80, 0x74, 0x39, 0x57, 0x9f, 0x90, 0x99, 0x8e, 0x8d, 0x38, 0x87, 0xa7, + 0x53, 0xc5, 0x49, 0xf0, 0xe2, 0x82, 0xd2, 0xfa, 0xe1, 0x83, 0x03, 0xaf, + 0x31, 0x38, 0xea, 0x86, 0xe6, 0xdf, 0xda, 0x6e, 0x0f, 0xdf, 0xa1, 0x5a, + 0xdd, 0x15, 0x3b, 0x8d, 0xb7, 0xb8, 0xcb, 0x8c, 0x26, 0x15, 0x9c, 0xd3, + 0xe7, 0x25, 0xcf, 0xfb, 0x36, 0x3a, 0xea, 0x24, 0xfe, 0xaf, 0x3c, 0xaf, + 0xd6, 0xc1, 0xd1, 0xca, 0xa8, 0xc2, 0xa9, 0x20, 0x04, 0x49, 0xc7, 0xb9, + 0x76, 0x0e, 0xc3, 0xea, 0x32, 0x86, 0xda, 0xbc, 0x49, 0x4c, 0xd9, 0x3f, + 0x37, 0xb0, 0x51, 0xa6, 0x9d, 0x4a, 0x74, 0xb9, 0xc4, 0xff, 0x8a, 0xb6, + 0xc3, 0xae, 0x56, 0x6b, 0x8a, 0x16, 0x1f, 0xd9, 0xff, 0x6f, 0xd2, 0x5b, + 0x9c, 0x65, 0xa9, 0x74, 0xc3, 0xfb, 0xa9, 0xfe, 0x0b, 0xec, 0xd3, 0xc3, + 0x11, 0x51, 0x02, 0x3d, 0xbf, 0x34, 0xc7, 0xd4, 0x97, 0xc0, 0x28, 0x83, + 0xa3, 0x36, 0x15, 0xeb, 0xff, 0x1b, 0xb3, 0x37, 0x48, 0x60, 0x3d, 0xe4, + 0x94, 0xbd, 0xee, 0x3b, 0xc9, 0x06, 0x34, 0x22, 0x57, 0xdb, 0x59, 0xd1, + 0xe5, 0x46, 0x02, 0x8d, 0x4e, 0xfd, 0x2f, 0x9a, 0xa7, 0x45, 0x0e, 0x1a, + 0x40, 0x84, 0x64, 0x9d, 0xa9, 0x1b, 0x52, 0xd4, 0x98, 0x02, 0x01, 0x0b, + 0x23, 0xcf, 0xdf, 0x5c, 0x58, 0xe7, 0xb5, 0xa0, 0xc7, 0x35, 0x56, 0xa8, + 0x2b, 0x0d, 0x32, 0x67, 0x51, 0xfc, 0x62, 0x40, 0x17, 0xf3, 0x86, 0x22, + 0xf6, 0x99, 0x0d, 0x5b, 0x5c, 0x35, 0x31, 0x8e, 0x2e, 0x17, 0x94, 0xde, + 0xef, 0xbe, 0x83, 0x43, 0xa1, 0x7d, 0x7c, 0x64, 0x95, 0x4b, 0xd2, 0x40, + 0x80, 0xba, 0x8a, 0x53, 0x45, 0x17, 0x6f, 0x60, 0x99, 0x8d, 0xd6, 0x13, + 0x75, 0xe2, 0x53, 0x53, 0x45, 0x22, 0xc8, 0xdb, 0x17, 0x08, 0x0a, 0x21, + 0xb1, 0xa4, 0xb4, 0x00, 0xb8, 0xea, 0x37, 0x83, 0xa3, 0x6c, 0x23, 0x53, + 0xee, 0x8c, 0x2e, 0xef, 0xc5, 0xfc, 0xc7, 0xf1, 0xb2, 0x50, 0x48, 0xaa, + 0x4a, 0xf9, 0x73, 0x70, 0x52, 0xc4, 0xca, 0x43, 0xc3, 0x7a, 0x4a, 0x08, + 0xd6, 0x30, 0xe9, 0xbe, 0x68, 0x2c, 0x93, 0x08, 0x17, 0x98, 0xbd, 0x09, + 0x46, 0xb2, 0x0e, 0x23, 0xc2, 0x7b, 0x94, 0xc9, 0x13, 0xfe, 0x96, 0x21, + 0xcc, 0x6b, 0x77, 0xf7, 0xbd, 0xa1, 0x9f, 0xa5, 0x79, 0x73, 0xc4, 0xc0, + 0x26, 0x08, 0xc6, 0xbf, 0xdd, 0x5a, 0x2c, 0x89, 0x76, 0x5c, 0xf2, 0xfe, + 0x28, 0x6f, 0x31, 0xf7, 0x59, 0xaa, 0x90, 0xe0, 0xe2, 0x4a, 0x44, 0x3e, + 0x44, 0xd4, 0x5b, 0x6f, 0xee, 0xf2, 0x34, 0x58, 0x62, 0x62, 0xc8, 0xfd, + 0x8f, 0x1b, 0xba, 0xd4, 0xa2, 0xe1, 0x7a, 0x41, 0x90, 0x4a, 0x0a, 0x66, + 0x1e, 0x6f, 0x5e, 0x81, 0x8d, 0x3b, 0xcb, 0x4a, 0xa2, 0x83, 0x4f, 0x8b, + 0xb0, 0xd4, 0x73, 0x7f, 0xdf, 0x17, 0xe5, 0x16, 0xff, 0xea, 0x28, 0x93, + 0x34, 0xeb, 0xc8, 0xe2, 0xe5, 0x3d, 0xc0, 0xbc, 0x35, 0xaa, 0x2e, 0xaa, + 0xc4, 0x52, 0xfa, 0xee, 0x6e, 0x97, 0xc9, 0x51, 0xf1, 0xe5, 0xbd, 0x81, + 0x95, 0x8f, 0x73, 0x12, 0x6f, 0x40, 0xd6, 0x95, 0x81, 0xbc, 0xa9, 0xff, + 0xf2, 0x2d, 0xcb, 0x27, 0x13, 0x79, 0x7c, 0x5f, 0xd0, 0x8f, 0x72, 0x6a, + 0x32, 0x75, 0xfc, 0xca, 0x0f, 0x36, 0x01, 0x91, 0xd4, 0x57, 0xda, 0x02, + 0xf7, 0x8c, 0x3c, 0x77, 0xbe, 0x78, 0x1d, 0x25, 0x9c, 0x5b, 0x55, 0x7e, + 0x71, 0x18, 0x9b, 0x79, 0x67, 0x8e, 0x66, 0x8b, 0x20, 0xef, 0x3e, 0x80, + 0xa5, 0xcf, 0x0d, 0xf6, 0x2d, 0xa9, 0x7b, 0x20, 0xc2, 0x35, 0xf1, 0xf3, + 0x70, 0x50, 0x0f, 0xec, 0x7f, 0xab, 0x50, 0xd4, 0xdb, 0x7f, 0x09, 0x9e, + 0x71, 0x2e, 0xa7, 0x92, 0xa0, 0x3f, 0x90, 0xf3, 0x49, 0x78, 0x18, 0x5f, + 0xc5, 0xa5, 0x1e, 0xcf, 0x25, 0x36, 0x05, 0x21, 0x7f, 0xc8, 0x7f, 0x62, + 0xda, 0x80, 0x9c, 0x76, 0x72, 0x39, 0x96, 0x68, 0xfe, 0xc3, 0x07, 0xe7, + 0xd1, 0x20, 0x58, 0xe6, 0xe2, 0xa9, 0x3a, 0x06, 0xb3, 0x9b, 0x10, 0x5c, + 0x94, 0xa7, 0x7a, 0x63, 0x96, 0x3a, 0xea, 0xc2, 0x0c, 0x72, 0x03, 0xc2, + 0x48, 0x26, 0x00, 0x8d, 0x01, 0x8c, 0x6b, 0x3d, 0xa3, 0xa9, 0xcb, 0xd4, + 0x8d, 0x77, 0x63, 0x8c, 0x20, 0xe3, 0xf7, 0xdd, 0xee, 0x6e, 0x24, 0xd2, + 0x14, 0xbc, 0xc0, 0x7d, 0xa9, 0x6d, 0xa5, 0xed, 0x98, 0x99, 0x62, 0xa8, + 0x1e, 0x9b, 0xe9, 0xef, 0x8d, 0xf5, 0xf5, 0xb2, 0xf9, 0xa5, 0xa9, 0x5b, + 0x0b, 0x3b, 0x29, 0x3a, 0x22, 0x8d, 0x56, 0xd3, 0xcc, 0xd6, 0x75, 0x0a, + 0x20, 0x0e, 0xd0, 0x9e, 0x71, 0x15, 0x64, 0x26, 0x72, 0x5b, 0xe4, 0xa8, + 0x3f, 0x55, 0xd1, 0x6e, 0xa9, 0xf8, 0x85, 0x54, 0x13, 0x23, 0xd5, 0xd2, + 0xd7, 0x09, 0xcc, 0xf0, 0x53, 0x2a, 0xea, 0xcd, 0x4d, 0x4d, 0xd2, 0x8b, + 0xee, 0x01, 0xc4, 0xb5, 0x0c, 0x9b, 0xa6, 0x00, 0x57, 0x2f, 0xd3, 0x51, + 0x08, 0xf0, 0x08, 0x18, 0x48, 0xae, 0x81, 0xdb, 0x95, 0x8c, 0xdf, 0xab, + 0xa5, 0xa4, 0x6a, 0x21, 0x7e, 0x26, 0xb0, 0xb7, 0x7f, 0xd0, 0x1a, 0xc8, + 0xd8, 0xb5, 0x76, 0xa0, 0x87, 0x6e, 0x37, 0xb1, 0xea, 0x33, 0x84, 0xde, + 0x5b, 0x01, 0x64, 0xf2, 0xf4, 0xc5, 0xe2, 0xe0, 0xe5, 0x58, 0x6f, 0x5f, + 0xda, 0xa0, 0xe1, 0x00, 0x2f, 0xe1, 0xde, 0x6d, 0xef, 0x73, 0x48, 0x67, + 0xcc, 0x12, 0x7f, 0xa2, 0x12, 0x65, 0x72, 0xb4, 0x8b, 0x6b, 0x2c, 0x0f, + 0x5b, 0x85, 0xa3, 0x78, 0x39, 0x26, 0xbe, 0x3c, 0x76, 0x5f, 0xbb, 0x07, + 0x9a, 0xd6, 0x67, 0x06, 0xdc, 0x92, 0x83, 0x18, 0xa0, 0x68, 0xfd, 0xcb, + 0xab, 0x4b, 0x1a, 0xc7, 0x8e, 0x59, 0xfc, 0x6d, 0x3c, 0xdf, 0x22, 0xbc, + 0xad, 0xaa, 0x2b, 0x1e, 0xc3, 0xe6, 0x26, 0xe4, 0xfe, 0xda, 0x21, 0xfa, + 0x65, 0xb6, 0xc5, 0xdb, 0x01, 0x05, 0x97, 0x9c, 0x8e, 0xa4, 0x51, 0xfb, + 0x9a, 0x81, 0xfc, 0xcc, 0x6b, 0x3f, 0xd0, 0xaa, 0x6d, 0x72, 0x24, 0x75, + 0x17, 0x72, 0x8a, 0x75, 0x2e, 0xc1, 0x55, 0x8a, 0x95, 0x3b, 0x28, 0x0c, + 0x87, 0xcc, 0xe4, 0x5a, 0xb7, 0xd0, 0x3a, 0x05, 0x5d, 0x18, 0x82, 0xee, + 0x5d, 0x0c, 0x96, 0x8c, 0x94, 0x05, 0x54, 0xfe, 0xbd, 0x23, 0x3d, 0x75, + 0x5c, 0x3d, 0xc7, 0x97, 0x0e, 0xf2, 0xbd, 0x91, 0x0b, 0x0b, 0xe6, 0x2c, + 0x44, 0xb2, 0x0b, 0xac, 0xdf, 0xfc, 0x91, 0x8d, 0xd5, 0x2f, 0x6b, 0xde, + 0x41, 0x94, 0x69, 0x1f, 0xe0, 0x56, 0x6b, 0x05, 0x9a, 0xd8, 0x0a, 0xa9, + 0xf2, 0x7e, 0x4e, 0xa2, 0xca, 0xeb, 0xb7, 0x57, 0x02, 0xca, 0x52, 0xf7, + 0x2d, 0x72, 0xa9, 0x55, 0xe5, 0x2c, 0xb6, 0xf5, 0xe3, 0xe4, 0x7a, 0x48, + 0xe0, 0xc0, 0xb9, 0x73, 0xb9, 0x0b, 0x8e, 0xb5, 0x80, 0x9b, 0x58, 0x7d, + 0xd4, 0xf0, 0x88, 0x95, 0x86, 0xe8, 0x01, 0xb6, 0xb9, 0x27, 0x51, 0x94, + 0xc8, 0x94, 0xcb, 0xad, 0x90, 0xa7, 0x1f, 0x61, 0x45, 0xce, 0x49, 0xe9, + 0x67, 0xb4, 0x53, 0x32, 0xc0, 0x9b, 0xd3, 0x78, 0xbe, 0x02, 0xb2, 0x39, + 0x0a, 0xc5, 0xce, 0x45, 0x5b, 0xba, 0x52, 0x6b, 0x4c, 0xc2, 0x05, 0xbe, + 0x82, 0x54, 0xdb, 0x46, 0x20, 0x88, 0xe1, 0xec, 0xa7, 0x39, 0x7d, 0x17, + 0x39, 0xae, 0xe2, 0xf9, 0x9a, 0xbe, 0xf6, 0xc2, 0x0d, 0xc2, 0x85, 0x7a, + 0xfb, 0xf6, 0x82, 0xce, 0xfd, 0xf2, 0xf0, 0x7a, 0xfe, 0x2b, 0x61, 0xe7, + 0x64, 0xee, 0x5e, 0x78, 0xe3, 0xff, 0xfe, 0x6e, 0x2a, 0xb4, 0xff, 0x00, + 0x5e, 0x6e, 0x89, 0x02, 0xb4, 0x5b, 0xf4, 0x67, 0x84, 0x8e, 0x39, 0x71, + 0x9d, 0xb1, 0xe3, 0x1b, 0xaf, 0x1d, 0xdf, 0x39, 0x57, 0x2d, 0xa4, 0xa7, + 0x8b, 0x1b, 0xb2, 0xcf, 0xdd, 0xdf, 0xae, 0xd3, 0xa7, 0x5d, 0xf5, 0x55, + 0xf9, 0xd3, 0xaa, 0x7b, 0xc2, 0x9d, 0xbc, 0x4d, 0x9a, 0xe3, 0xaf, 0xbc, + 0xd8, 0x88, 0xed, 0xb6, 0x85, 0x08, 0xed, 0x91, 0x1b, 0x98, 0xd6, 0x89, + 0xdd, 0xd1, 0xd4, 0xdf, 0xe4, 0x7b, 0x73, 0x51, 0x73, 0xda, 0x31, 0x03, + 0x5e, 0x52, 0x20, 0x13, 0xaf, 0x36, 0x0c, 0x90, 0x53, 0x2a, 0x5e, 0x0e, + 0x5a, 0x29, 0x40, 0xf7, 0xbe, 0xd1, 0x09, 0x18, 0x38, 0xd3, 0xa5, 0x8a, + 0xf1, 0xd6, 0xb7, 0x3f, 0x35, 0x1d, 0xe9, 0xf0, 0x25, 0x3b, 0xd7, 0x39, + 0x1f, 0x17, 0xd7, 0x63, 0x1c, 0xe7, 0x55, 0x11, 0xb5, 0x01, 0xcb, 0x98, + 0xe2, 0x52, 0xe1, 0x69, 0x61, 0xa9, 0xb4, 0x44, 0x75, 0xdd, 0x28, 0xc0, + 0xc5, 0x5c, 0x67, 0xa4, 0xe4, 0x96, 0x3a, 0xb1, 0x87, 0xa1, 0xa0, 0xff, + 0x7a, 0x15, 0x36, 0x40, 0x54, 0xff, 0x8c, 0xa3, 0x48, 0x07, 0x1f, 0x10, + 0x10, 0x70, 0xcb, 0x6e, 0x0e, 0x7d, 0x01, 0x32, 0x4d, 0x93, 0x39, 0xec, + 0x51, 0x19, 0x38, 0xf1, 0x06, 0x3e, 0x7b, 0xc2, 0x40, 0x31, 0x91, 0xcc, + 0x33, 0x8d, 0xde, 0x14, 0xb6, 0x48, 0x4e, 0xc6, 0x2e, 0xe6, 0x41, 0xb1, + 0x4a, 0xac, 0x98, 0xc0, 0x94, 0xfe, 0xfb, 0xf7, 0xe2, 0x9c, 0x3d, 0xa2, + 0xf1, 0x5c, 0x81, 0xf5, 0xf0, 0xd8, 0x9c, 0x08, 0x21, 0x71, 0x34, 0xaa, + 0xad, 0xca, 0x9e, 0x24, 0x7e, 0x28, 0x45, 0x93, 0x66, 0x76, 0x00, 0x55, + 0x33, 0xff, 0x21, 0x41, 0x86, 0x4b, 0xf6, 0x0c, 0x54, 0xc4, 0xa8, 0x52, + 0xf5, 0xa0, 0xcd, 0x5e, 0xee, 0x46, 0x86, 0x71, 0xf5, 0xf9, 0xb2, 0xd8, + 0x49, 0xb2, 0x12, 0x2e, 0xe7, 0x08, 0x13, 0x30, 0xe0, 0x59, 0xe9, 0xae, + 0x99, 0xfb, 0x6c, 0x79, 0x55, 0x49, 0xea, 0xdc, 0xea, 0x35, 0x0f, 0x53, + 0xd8, 0xd6, 0x41, 0x0e, 0x66, 0x9b, 0x07, 0xe2, 0xdb, 0xcb, 0x2f, 0xf3, + 0xd0, 0x94, 0x2d, 0x8a, 0x60, 0x71, 0x92, 0xef, 0x53, 0x16, 0xa4, 0x9f, + 0xbe, 0x9f, 0xae, 0xcc, 0x4d, 0xe0, 0x6a, 0x34, 0xbc, 0x82, 0xf2, 0x43, + 0xde, 0x6e, 0xb6, 0xc1, 0xeb, 0xc0, 0x08, 0xc9, 0x47, 0xa9, 0xdf, 0x86, + 0x60, 0x3e, 0x53, 0x20, 0x12, 0x9e, 0xe3, 0x69, 0xd4, 0x56, 0x8c, 0x4c, + 0xa9, 0x2d, 0x41, 0xd0, 0x78, 0xb2, 0x50, 0xee, 0x7d, 0x6e, 0x82, 0x4b, + 0x6d, 0x93, 0xfa, 0x1c, 0xbc, 0xad, 0x76, 0xdd, 0xef, 0x14, 0x72, 0x15, + 0xb3, 0xaa, 0x1f, 0xd1, 0xf9, 0x3e, 0x24, 0x92, 0xee, 0xfc, 0xae, 0x2a, + 0x43, 0xf5, 0x5c, 0x21, 0xc3, 0xd7, 0xed, 0xc9, 0x01, 0x99, 0x23, 0x8a, + 0x9f, 0xe6, 0x34, 0x8c, 0xb7, 0x89, 0xd7, 0x0e, 0xd1, 0x73, 0x06, 0x19, + 0xfa, 0x17, 0xc9, 0xc9, 0xfa, 0xc4, 0xf8, 0x2e, 0x17, 0xb8, 0x8c, 0xe4, + 0xb5, 0xd6, 0xd4, 0xdd, 0x8d, 0x62, 0x57, 0x34, 0xc6, 0xb8, 0x78, 0x41, + 0xc7, 0x62, 0xfb, 0x2e, 0xc3, 0x1d, 0xa8, 0xa3, 0x5b, 0x93, 0x85, 0x5c, + 0x4a, 0xf7, 0xb3, 0x10, 0xcc, 0xc4, 0x64, 0x08, 0x11, 0x57, 0xaf, 0x5a, + 0xa5, 0x9c, 0x31, 0xee, 0x1d, 0x90, 0xf2, 0x96, 0x86, 0xfb, 0x38, 0xb4, + 0xe9, 0x3b, 0x6e, 0x4c, 0xff, 0x6f, 0x2a, 0x20, 0x0c, 0x91, 0x04, 0x8a, + 0xa7, 0xe6, 0xcf, 0x45, 0x4f, 0xad, 0xb7, 0x63, 0x34, 0x01, 0xa9, 0x33, + 0x6e, 0x21, 0xae, 0xa1, 0xff, 0xde, 0x77, 0x52, 0x96, 0x62, 0x27, 0x84, + 0xac, 0x49, 0x25, 0xef, 0x4c, 0xd5, 0xec, 0xf9, 0xd4, 0x71, 0x17, 0x8a, + 0xc5, 0x2b, 0xed, 0x96, 0xe4, 0x3a, 0xb4, 0xe9, 0x18, 0x19, 0xd4, 0x3b, + 0x4b, 0x5c, 0x76, 0x94, 0x49, 0x92, 0xd9, 0x21, 0x37, 0x1a, 0x0e, 0xf7, + 0x36, 0x70, 0x11, 0x82, 0x3a, 0x1f, 0x80, 0x14, 0x80, 0x98, 0x68, 0x53, + 0x0a, 0x55, 0xf5, 0xe7, 0x7f, 0x04, 0xd6, 0x21, 0x47, 0xa5, 0x8d, 0xa1, + 0xeb, 0xc3, 0x81, 0xa7, 0x36, 0xbd, 0x6f, 0x47, 0x22, 0x26, 0x14, 0x5d, + 0xc5, 0x06, 0xb4, 0x58, 0x37, 0xe4, 0x60, 0xf9, 0xc7, 0x2e, 0xa5, 0xa9, + 0xe4, 0x58, 0xc0, 0x1a, 0x45, 0x8f, 0xaa, 0xcd, 0xf6, 0x43, 0x06, 0x11, + 0x59, 0x74, 0x90, 0xd0, 0x1c, 0x38, 0xad, 0xf9, 0x27, 0xe2, 0x08, 0x96, + 0x08, 0x96, 0x73, 0x27, 0x94, 0x79, 0xd1, 0xf5, 0x3a, 0x2e, 0xb2, 0x25, + 0x6d, 0x8a, 0x55, 0x56, 0x83, 0xd5, 0x34, 0x5f, 0x69, 0xa3, 0xf5, 0xe3, + 0x8a, 0x6e, 0x35, 0x91, 0x51, 0x06, 0xda, 0x83, 0x21, 0x3c, 0x3b, 0x87, + 0xb0, 0x48, 0x7c, 0xd7, 0xdd, 0x56, 0x1a, 0xe4, 0xa3, 0xa9, 0xfc, 0x6a, + 0x59, 0xb5, 0xc8, 0xca, 0x7a, 0x8b, 0x34, 0x78, 0xc9, 0x20, 0x94, 0xdb, + 0x7f, 0xc1, 0x69, 0x4d, 0x2a, 0x44, 0xd6, 0x6d, 0x9b, 0x23, 0x41, 0x99, + 0x65, 0x9f, 0xda, 0xcd, 0x19, 0xed, 0xc5, 0xf2, 0x60, 0x72, 0x9d, 0xc5, + 0x6e, 0x1b, 0x28, 0x34, 0xea, 0xa4, 0xd0, 0x29, 0xe8, 0x66, 0xee, 0x1e, + 0x55, 0xe6, 0xbc, 0x93, 0xae, 0x65, 0x23, 0x97, 0xe1, 0xc0, 0x15, 0x07, + 0x1b, 0x04, 0xb5, 0x60, 0x1e, 0xb5, 0x1e, 0xa7, 0x39, 0xd8, 0xd4, 0xfd, + 0xf6, 0xe3, 0x44, 0x9e, 0xdb, 0x1b, 0x46, 0x54, 0x92, 0xbf, 0x3c, 0x25, + 0xbd, 0x56, 0xdd, 0xd9, 0x9a, 0x0d, 0x1c, 0x0f, 0x30, 0x90, 0x0d, 0x62, + 0xfd, 0x31, 0x8f, 0xc8, 0xa4, 0xbb, 0x32, 0xb3, 0x97, 0x4f, 0x47, 0x50, + 0x06, 0x96, 0xbd, 0x49, 0x44, 0x8d, 0x8b, 0x12, 0xff, 0x6c, 0xb8, 0xdd, + 0x37, 0x30, 0x45, 0xa3, 0x8e, 0x2a, 0x06, 0x89, 0x5e, 0x9a, 0x6b, 0xfe, + 0xb8, 0x2e, 0x1a, 0x6f, 0x4e, 0xd3, 0xdf, 0x1c, 0x05, 0x4d, 0x7e, 0x54, + 0xdf, 0xea, 0x7a, 0x3a, 0x60, 0xcb, 0xf9, 0x3c, 0xa0, 0xd1, 0x9b, 0x5c, + 0x50, 0x08, 0xe3, 0x7f, 0x8b, 0xb6, 0xe0, 0x8c, 0x2e, 0xba, 0xba, 0xda, + 0x97, 0x66, 0x14, 0x4c, 0xcb, 0x17, 0x62, 0xf6, 0x08, 0x2b, 0xbc, 0x48, + 0x23, 0x38, 0x16, 0xcb, 0xbf, 0x91, 0x44, 0x94, 0x68, 0x2e, 0x09, 0x31, + 0x29, 0xf0, 0x38, 0xc5, 0xc7, 0xef, 0xc8, 0xab, 0x46, 0xe3, 0xee, 0xaa, + 0xb9, 0x15, 0x89, 0xf8, 0xa5, 0xbd, 0xb9, 0xef, 0x39, 0xff, 0x3f, 0x2d, + 0x02, 0x21, 0xed, 0xfd, 0xe7, 0x1f, 0x84, 0xce, 0xfc, 0x37, 0x21, 0x85, + 0xa5, 0x40, 0x30, 0x94, 0x04, 0x3c, 0xa8, 0x7c, 0x0b, 0xcf, 0x1f, 0xa5, + 0xde, 0x88, 0xb6, 0x52, 0x90, 0x0b, 0xe1, 0x31, 0x7a, 0x2c, 0x41, 0x77, + 0xda, 0x94, 0x1b, 0x36, 0xf1, 0x6b, 0x73, 0xf3, 0xa9, 0x59, 0x4e, 0x81, + 0xdd, 0x6c, 0xd1, 0x7a, 0x53, 0x28, 0xa1, 0xcc, 0xe9, 0x47, 0x8d, 0x80, + 0x5c, 0xfe, 0xb0, 0xb9, 0x6e, 0xd4, 0x61, 0x41, 0x0e, 0x28, 0x3b, 0xa4, + 0xb1, 0x6a, 0xb6, 0xc0, 0xd7, 0x47, 0x50, 0xe4, 0x5a, 0x30, 0xd8, 0x8c, + 0x06, 0x34, 0xf1, 0xd1, 0x79, 0x4c, 0xeb, 0xf9, 0x17, 0xf0, 0x6c, 0x75, + 0xe8, 0x8d, 0x20, 0xe4, 0xcd, 0x45, 0x9c, 0x85, 0xc2, 0x00, 0xa4, 0x6a, + 0x22, 0x36, 0x48, 0x7e, 0x44, 0x4c, 0xc9, 0x08, 0x48, 0xa9, 0xa2, 0x29, + 0x1b, 0xa5, 0x17, 0x48, 0xfb, 0xf4, 0x42, 0xca, 0x3c, 0x8a, 0xc9, 0x8c, + 0x9d, 0x76, 0xfd, 0xbd, 0x99, 0x1e, 0x40, 0x7c, 0x09, 0xcd, 0xb3, 0xce, + 0xcd, 0x3c, 0x80, 0x0c, 0x2b, 0x99, 0xee, 0x14, 0x82, 0xb6, 0x31, 0xa7, + 0x90, 0xdb, 0x5e, 0x4a, 0x0b, 0x11, 0x97, 0xf1, 0x5f, 0x9c, 0xc1, 0xa5, + 0xdb, 0x40, 0x70, 0x11, 0x9f, 0x89, 0x23, 0xc3, 0x8b, 0xe6, 0x64, 0xbe, + 0x24, 0x42, 0xcd, 0x97, 0x27, 0x1e, 0x01, 0x6b, 0x55, 0x33, 0x67, 0x6a, + 0xac, 0xf3, 0x10, 0x2b, 0xb6, 0x92, 0x88, 0x84, 0x48, 0xe3, 0x06, 0xe9, + 0x91, 0xfa, 0x61, 0x0a, 0x3e, 0xe5, 0x84, 0xc5, 0xd6, 0x3b, 0xa9, 0x49, + 0xeb, 0x3d, 0x4f, 0xe2, 0xc8, 0xc0, 0x92, 0x42, 0xf7, 0x04, 0x00, 0x1a, + 0xd7, 0xd3, 0x01, 0x54, 0xdc, 0x14, 0x6a, 0x6f, 0x9f, 0x86, 0xd6, 0x66, + 0x11, 0x77, 0x4b, 0x8d, 0xd4, 0xf7, 0xaf, 0x54, 0x89, 0xf9, 0xcb, 0xe7, + 0xb0, 0x21, 0xc8, 0x2e, 0x54, 0x7f, 0x77, 0x80, 0x7c, 0xbf, 0xdd, 0x6a, + 0xa9, 0x4a, 0xc9, 0x9f, 0x18, 0xa7, 0xf9, 0xb1, 0x6a, 0x4c, 0x9b, 0xe3, + 0xf8, 0x95, 0xd5, 0xe5, 0x42, 0xc2, 0x43, 0x7c, 0x14, 0x25, 0x9e, 0xc9, + 0xb8, 0x07, 0x70, 0x97, 0x40, 0x50, 0x7e, 0xbb, 0x7c, 0x00, 0xac, 0x28, + 0x28, 0xf8, 0x7b, 0x43, 0x20, 0xf2, 0xb1, 0xda, 0xa6, 0x44, 0xaa, 0x6d, + 0xe5, 0x99, 0x2c, 0xa0, 0x09, 0x8e, 0xcb, 0x71, 0xfe, 0x76, 0xc1, 0xae, + 0xab, 0x34, 0xb4, 0xb2, 0xf6, 0xdd, 0xda, 0xd7, 0x7c, 0xf8, 0x8a, 0xfd, + 0x3f, 0xf4, 0x32, 0xfa, 0x47, 0xbf, 0xc1, 0x8f, 0x26, 0x6a, 0xb9, 0x2c, + 0x00, 0x35, 0x0c, 0xdb, 0x97, 0xef, 0xa7, 0xa5, 0x71, 0x23, 0x42, 0x8d, + 0x1e, 0x70, 0xe6, 0x71, 0xc6, 0xbe, 0x4d, 0x84, 0x8f, 0x10, 0x3b, 0x83, + 0x81, 0xf9, 0x77, 0xf7, 0xdc, 0x0c, 0x91, 0x56, 0x4d, 0x4e, 0xbc, 0x55, + 0x6c, 0xa2, 0xf9, 0xd1, 0xc9, 0x2a, 0x80, 0x7e, 0x1c, 0xd7, 0xed, 0xd0, + 0x67, 0x53, 0xa6, 0xe8, 0x0d, 0x2f, 0x35, 0x4b, 0x93, 0x21, 0x84, 0xfe, + 0xa4, 0xfe, 0x99, 0x78, 0x43, 0x2f, 0x1c, 0xef, 0x55, 0x12, 0x0c, 0xa0, + 0x54, 0xbf, 0xd6, 0xd5, 0x2d, 0x9d, 0xeb, 0x30, 0x43, 0x63, 0xaf, 0x72, + 0x69, 0xc7, 0xae, 0x76, 0x00, 0x9f, 0x49, 0x06, 0x69, 0xe7, 0x03, 0x6e, + 0x7b, 0xee, 0x54, 0xc9, 0xe8, 0x19, 0xa4, 0x30, 0x5e, 0xea, 0x89, 0x1d, + 0xdf, 0xe8, 0xde, 0x30, 0xb5, 0x8c, 0x1f, 0x5f, 0x35, 0xbb, 0xf3, 0x02, + 0xcf, 0xec, 0xcb, 0x5c, 0xde, 0x11, 0x9d, 0xbc, 0xe6, 0x0f, 0x6f, 0x83, + 0xe6, 0xb3, 0x99, 0xa4, 0xd4, 0x6c, 0x66, 0x48, 0xbb, 0xfc, 0x7f, 0xbe, + 0x2a, 0x15, 0x58, 0xee, 0xdb, 0xcb, 0x92, 0x9e, 0x2c, 0x5f, 0x3e, 0xdf, + 0x3a, 0xa1, 0x0f, 0x21, 0x88, 0xdf, 0x85, 0xae, 0xd0, 0x9d, 0xc5, 0x77, + 0x50, 0x86, 0x65, 0x81, 0x8b, 0x16, 0x09, 0x7e, 0x9e, 0x9b, 0x2c, 0x83, + 0x46, 0xed, 0x02, 0xe2, 0x31, 0xe6, 0x5b, 0x33, 0x50, 0x09, 0x0e, 0xf1, + 0x5e, 0x32, 0xf4, 0xf2, 0xd9, 0xe6, 0x0a, 0x88, 0x9e, 0xf1, 0x28, 0xff, + 0x27, 0x11, 0x68, 0x6c, 0xcd, 0x1d, 0xc1, 0xf2, 0x36, 0xca, 0xbf, 0x14, + 0x28, 0x73, 0x8a, 0x0e, 0x8e, 0x5c, 0x9c, 0x0b, 0x69, 0xba, 0x8b, 0x04, + 0x47, 0x35, 0xd1, 0x09, 0x5e, 0x8d, 0x3a, 0x1b, 0xe7, 0x52, 0xae, 0xb6, + 0x8f, 0xc9, 0x13, 0xb0, 0x72, 0x38, 0x3e, 0xaa, 0xba, 0xf5, 0x50, 0xad, + 0x28, 0x3a, 0x98, 0x30, 0x8b, 0x33, 0x9a, 0xd7, 0x77, 0x63, 0x1d, 0xc9, + 0x70, 0x32, 0x62, 0x66, 0x0f, 0xc8, 0xcf, 0x53, 0x2e, 0x94, 0x20, 0xdd, + 0x3a, 0xe4, 0xa9, 0x18, 0xc4, 0x4f, 0xb9, 0x29, 0x03, 0x9e, 0x1c, 0xa4, + 0x89, 0xb0, 0xc4, 0x6d, 0xf6, 0x1c, 0x7e, 0x9c, 0xf8, 0xdb, 0x0b, 0xd4, + 0x49, 0x4d, 0x78, 0xa4, 0xb4, 0x6f, 0xcb, 0x9a, 0x9d, 0x6e, 0x23, 0xe8, + 0x1b, 0x45, 0x7e, 0xb8, 0xac, 0x28, 0xcd, 0x86, 0xcd, 0x59, 0xf0, 0xe7, + 0x64, 0x57, 0x2d, 0xdd, 0xbf, 0xee, 0xc5, 0x1a, 0x04, 0x2b, 0xd1, 0xb0, + 0x57, 0x52, 0x90, 0x1f, 0x59, 0x0c, 0x2d, 0x78, 0xdb, 0x4d, 0x28, 0xa4, + 0xcf, 0x2c, 0x86, 0xe6, 0xc5, 0x7c, 0x7d, 0x53, 0xf5, 0x86, 0x5a, 0x89, + 0x47, 0x8b, 0xcb, 0xce, 0xb0, 0x51, 0x62, 0x21, 0xd7, 0x5f, 0xb7, 0x60, + 0x2e, 0x0d, 0x53, 0x2b, 0xdf, 0x18, 0x2a, 0x6e, 0x1a, 0xd3, 0xab, 0x8e, + 0x82, 0xcc, 0x8b, 0xc8, 0x57, 0xa2, 0x74, 0xc1, 0x67, 0x1d, 0x2c, 0xc9, + 0x59, 0xc0, 0xb9, 0xf2, 0xe2, 0x4c, 0xa7, 0x2f, 0xde, 0x03, 0xfb, 0x15, + 0x2e, 0x82, 0x8a, 0x43, 0x19, 0x69, 0x0d, 0x76, 0x97, 0x71, 0xaf, 0x08, + 0x8d, 0xb0, 0xe3, 0xa0, 0xa9, 0x5d, 0xce, 0x14, 0xe9, 0x9a, 0x2a, 0xe5, + 0xf2, 0x2b, 0x0d, 0x30, 0xcc, 0xea, 0x8f, 0x21, 0x8d, 0x0e, 0x43, 0xb8, + 0x63, 0x93, 0xa8, 0x8f, 0x29, 0x01, 0xd9, 0xa8, 0xd8, 0xa8, 0x58, 0x8d, + 0x2d, 0xa2, 0xbe, 0xdc, 0xbe, 0xe5, 0xc3, 0xe2, 0xb0, 0x05, 0x0c, 0x11, + 0x20, 0x00, 0xb0, 0xb4, 0xf7, 0x63, 0x6c, 0x46, 0x9e, 0x17, 0x49, 0x63, + 0xa6, 0xbb, 0x1a, 0x0e, 0xe5, 0x12, 0xca, 0xc6, 0x91, 0xb4, 0x1e, 0xe7, + 0x9c, 0x1c, 0x61, 0x92, 0x69, 0xa0, 0x71, 0x5f, 0x5f, 0xba, 0x68, 0x1c, + 0x1c, 0x31, 0x9d, 0x98, 0x91, 0x62, 0xdd, 0x68, 0xa3, 0xbc, 0x97, 0x8e, + 0xcf, 0xec, 0x96, 0xcf, 0xbb, 0x60, 0x14, 0x24, 0xa5, 0x4d, 0x81, 0x4b, + 0x59, 0x08, 0xa0, 0xef, 0x47, 0x7c, 0xbf, 0xe5, 0x87, 0x66, 0x58, 0x1b, + 0x9a, 0xf0, 0xce, 0x0d, 0x1d, 0x0c, 0x84, 0xe8, 0x01, 0x78, 0xf7, 0xb6, + 0xc4, 0x23, 0x7d, 0x13, 0xa8, 0x7e, 0xce, 0x8c, 0xf5, 0x03, 0xa1, 0xdc, + 0x2b, 0x6e, 0x90, 0x66, 0x0d, 0x5b, 0x04, 0xc5, 0xa4, 0xcf, 0x0c, 0x6d, + 0xe5, 0xfe, 0x7c, 0x34, 0xf3, 0xd8, 0x42, 0xa3, 0xa8, 0x98, 0xf7, 0xf1, + 0x3f, 0x90, 0xb0, 0x5a, 0x88, 0x70, 0x59, 0xfd, 0xfb, 0x9f, 0xde, 0xe9, + 0x1c, 0xad, 0xed, 0x28, 0xcf, 0x7d, 0xfc, 0x7d, 0xd5, 0xbe, 0xb8, 0x58, + 0x08, 0x75, 0x2c, 0x3b, 0xbd, 0x02, 0x35, 0x56, 0x3c, 0xb9, 0xfb, 0x4d, + 0x6f, 0x5e, 0xf5, 0xda, 0xa1, 0xc1, 0x67, 0x31, 0x47, 0x25, 0x6f, 0x00, + 0x9f, 0xdb, 0x62, 0x07, 0x79, 0xed, 0xcd, 0x24, 0x25, 0x0a, 0x07, 0x48, + 0xe9, 0x3d, 0x5f, 0x59, 0x78, 0xe2, 0xb2, 0x74, 0xb2, 0x36, 0x9f, 0xed, + 0xc6, 0xfb, 0x27, 0xd0, 0x46, 0xfb, 0x25, 0x85, 0x46, 0xd5, 0x72, 0x2d, + 0xc2, 0xf6, 0xea, 0xda, 0xb0, 0xaa, 0x9a, 0xba, 0xf4, 0x0c, 0x27, 0xe5, + 0x5b, 0x72, 0x5c, 0x43, 0xc8, 0x23, 0x44, 0xdb, 0xe2, 0x2f, 0x40, 0x8e, + 0xe3, 0xb7, 0xea, 0x97, 0x68, 0xab, 0xa3, 0x92, 0xcb, 0x7b, 0xd5, 0xa2, + 0x45, 0x5e, 0x9e, 0xae, 0x63, 0x8f, 0x27, 0xdf, 0x8d, 0xe7, 0xd2, 0x72, + 0xfb, 0x3c, 0x2b, 0xab, 0xe1, 0xc9, 0xfc, 0x51, 0xb5, 0x76, 0xb2, 0xea, + 0xcf, 0x86, 0x81, 0x27, 0xfd, 0xb3, 0x9a, 0xf0, 0xd4, 0x99, 0x94, 0x41, + 0x43, 0xb2, 0x1f, 0xf8, 0x58, 0x41, 0xe2, 0x75, 0x0f, 0xac, 0x45, 0x5a, + 0x0c, 0xeb, 0x82, 0xc7, 0x98, 0x74, 0xee, 0xc5, 0x0d, 0xd3, 0xea, 0x24, + 0xdc, 0xf0, 0xc7, 0x8d, 0x15, 0xb4, 0xd7, 0x61, 0xdb, 0x5b, 0x30, 0x4c, + 0xcd, 0xbd, 0xa1, 0x48, 0x04, 0x12, 0x02, 0x92, 0xea, 0x33, 0x07, 0x49, + 0x71, 0x0c, 0x07, 0xb9, 0xef, 0x70, 0x58, 0x3f, 0x1c, 0x0e, 0xd2, 0x7c, + 0xd5, 0xff, 0x70, 0x01, 0xc6, 0x3f, 0x17, 0x95, 0x3b, 0x87, 0x3e, 0x47, + 0xc8, 0xc0, 0xe9, 0x38, 0x5a, 0xcd, 0x70, 0xd6, 0x6d, 0x4c, 0x19, 0xa2, + 0x99, 0xc5, 0x0c, 0xb3, 0x15, 0xdd, 0x09, 0xbe, 0x18, 0xc5, 0xdf, 0xdc, + 0x34, 0x27, 0x8d, 0x1f, 0xd6, 0xed, 0xcf, 0x77, 0xa6, 0x89, 0xe0, 0xc9, + 0xd2, 0x89, 0x40, 0x2c, 0x23, 0x6f, 0x76, 0xce, 0x4f, 0xf5, 0x6d, 0xa9, + 0x7c, 0x61, 0xe3, 0x33, 0x1b, 0x7c, 0x7e, 0x17, 0x08, 0xd7, 0xa1, 0x9a, + 0x6a, 0xa5, 0x84, 0x83, 0x24, 0x41, 0x02, 0x2f, 0x85, 0x7b, 0x8f, 0x7f, + 0x5d, 0x41, 0x3a, 0xa9, 0x8d, 0xe8, 0x25, 0x14, 0xe1, 0x11, 0xaf, 0x5a, + 0xd6, 0x76, 0xff, 0x9f, 0xa6, 0xae, 0x8e, 0xc5, 0xab, 0x5b, 0xe4, 0x60, + 0xad, 0xd9, 0x45, 0x78, 0x5c, 0xce, 0x02, 0xcb, 0xd9, 0xfb, 0x50, 0xae, + 0x12, 0x89, 0x1a, 0x1d, 0x98, 0xbc, 0xfd, 0xf4}; + +#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY +/* Seed of the verify-only vector: SK.seed || SK.prf || PK.seed */ +static const byte whTestSlhDsaKeyGenSeed[] = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x50, 0x51, 0x52, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f}; + +/* Rebuild the vector's key from its seed and check seed length handling */ +static int _whTest_CryptoSlhDsaSeededKeyGen(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + SlhDsaKey key[1]; + byte pub[WH_TEST_SLHDSA_KAT_PUB_LEN]; + word32 pubSz = sizeof(pub); + byte badSeed[sizeof(whTestSlhDsaKeyGenSeed) + 3]; + + memset(badSeed, 0x5a, sizeof(badSeed)); + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_KAT_PARAM, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to initialize SLH-DSA key: %d\n", ret); + return ret; + } + + /* A 3n-byte seed for another parameter set must be refused */ + ret = wh_Client_SlhDsaMakeExportKeyFromSeed(ctx, WH_TEST_SLHDSA_KAT_PARAM, + badSeed, sizeof(badSeed), key); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("Mismatched SLH-DSA seed length returned %d\n", ret); + ret = WH_TEST_FAIL; + } + else { + ret = 0; + } + + if (ret == 0) { + ret = wh_Client_SlhDsaMakeExportKeyFromSeed( + ctx, WH_TEST_SLHDSA_KAT_PARAM, badSeed, sizeof(badSeed) - 1, key); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("Ragged SLH-DSA seed length returned %d\n", ret); + ret = WH_TEST_FAIL; + } + else { + ret = 0; + } + } + + if (ret == 0) { + ret = wh_Client_SlhDsaMakeExportKeyFromSeed( + ctx, WH_TEST_SLHDSA_KAT_PARAM, whTestSlhDsaKeyGenSeed, + sizeof(whTestSlhDsaKeyGenSeed), key); + if (ret != 0) { + WH_ERROR_PRINT("Failed seeded SLH-DSA keygen: %d\n", ret); + } + } + + if (ret == 0) { + ret = wc_SlhDsaKey_ExportPublic(key, pub, &pubSz); + if (ret != 0) { + WH_ERROR_PRINT("Failed to export the seeded public key: %d\n", ret); + } + } + + if (ret == 0) { + if ((pubSz != sizeof(whTestSlhDsaVerifyPub)) || + (memcmp(pub, whTestSlhDsaVerifyPub, pubSz) != 0)) { + WH_ERROR_PRINT("Seeded SLH-DSA key does not match the vector\n"); + ret = WH_TEST_FAIL; + } + } + + /* Verifying the vector's signature shows the seed arrived intact */ + if (ret == 0) { + ret = wc_SlhDsaKey_Verify(key, NULL, 0, whTestSlhDsaVerifyMsg, + (word32)sizeof(whTestSlhDsaVerifyMsg), + whTestSlhDsaVerifySig, + (word32)sizeof(whTestSlhDsaVerifySig)); + if (ret != 0) { + WH_ERROR_PRINT("Seeded SLH-DSA key did not verify: %d\n", ret); + } + } + +#ifdef WOLFHSM_CFG_DMA + /* The DMA handler applies the same seed length rules. */ + if (ret == 0) { + ret = wh_Client_SlhDsaMakeExportKeyFromSeedDma( + ctx, WH_TEST_SLHDSA_KAT_PARAM, badSeed, sizeof(badSeed), key); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("Mismatched SLH-DSA DMA seed returned %d\n", ret); + ret = WH_TEST_FAIL; + } + else { + ret = 0; + } + } + + if (ret == 0) { + ret = wh_Client_SlhDsaMakeExportKeyFromSeedDma( + ctx, WH_TEST_SLHDSA_KAT_PARAM, whTestSlhDsaKeyGenSeed, + sizeof(whTestSlhDsaKeyGenSeed), key); + if (ret != 0) { + WH_ERROR_PRINT("Failed seeded SLH-DSA DMA keygen: %d\n", ret); + } + } + + if (ret == 0) { + pubSz = sizeof(pub); + ret = wc_SlhDsaKey_ExportPublic(key, pub, &pubSz); + if (ret != 0) { + WH_ERROR_PRINT("Failed to export the DMA seeded key: %d\n", ret); + } + } + + if (ret == 0) { + if ((pubSz != sizeof(whTestSlhDsaVerifyPub)) || + (memcmp(pub, whTestSlhDsaVerifyPub, pubSz) != 0)) { + WH_ERROR_PRINT("DMA seeded SLH-DSA key does not match\n"); + ret = WH_TEST_FAIL; + } + } +#endif /* WOLFHSM_CFG_DMA */ + + if (ret == 0) { + WH_TEST_PRINT("SLH-DSA SEEDED KEYGEN DEVID=0x%X SUCCESS\n", devId); + } + + wc_SlhDsaKey_Free(key); + return ret; +} + +/* EPHEMERAL returns the private key, so cache-key wrappers must refuse it */ +static int _whTest_CryptoSlhDsaCacheKeyEphemeral(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + whKeyId keyId = WH_KEYID_ERASED; + SlhDsaKey pub[1]; + const int param = WH_TEST_SLHDSA_KAT_PARAM; + const whNvmFlags flags = WH_NVM_FLAGS_EPHEMERAL; + + ret = wc_SlhDsaKey_Init(pub, param, NULL, devId); + if (ret != 0) { + return ret; + } + + if (wh_Client_SlhDsaMakeCacheKey(ctx, param, &keyId, flags, 0, NULL) != + WH_ERROR_BADARGS) { + WH_ERROR_PRINT("SLH-DSA cache keygen accepted EPHEMERAL\n"); + ret = WH_TEST_FAIL; + } + /* A valid seed, so the flag is the only reason to refuse */ + if (wh_Client_SlhDsaMakeCacheKeyFromSeed( + ctx, param, whTestSlhDsaKeyGenSeed, sizeof(whTestSlhDsaKeyGenSeed), + &keyId, flags, 0, NULL) != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("SLH-DSA seeded cache keygen accepted EPHEMERAL\n"); + ret = WH_TEST_FAIL; + } + if (wh_Client_SlhDsaMakeCacheKeyAndExportPublic( + ctx, param, &keyId, flags, 0, NULL, pub) != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("SLH-DSA cache and export accepted EPHEMERAL\n"); + ret = WH_TEST_FAIL; + } +#ifdef WOLFHSM_CFG_DMA + if (wh_Client_SlhDsaMakeCacheKeyDma(ctx, param, &keyId, flags, 0, NULL, + pub) != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("SLH-DSA DMA cache keygen accepted EPHEMERAL\n"); + ret = WH_TEST_FAIL; + } +#endif /* WOLFHSM_CFG_DMA */ + + if (ret == 0) { + WH_TEST_PRINT("SLH-DSA CACHE EPHEMERAL DEVID=0x%X SUCCESS\n", devId); + } + + wc_SlhDsaKey_Free(pub); + return ret; +} + +/* Importing a private key must not leave its DER in the comm buffer */ +static int _whTest_CryptoSlhDsaImportClearsComm(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + whKeyId keyId = WH_KEYID_ERASED; + SlhDsaKey key[1]; + const uint8_t* comm = (const uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + /* The seed starts with SK.seed, which the private key DER contains */ + const size_t skSz = sizeof(whTestSlhDsaKeyGenSeed) / 3; + size_t i; + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_KAT_PARAM, NULL, devId); + if (ret != 0) { + return ret; + } + + ret = wh_Client_SlhDsaMakeExportKeyFromSeed( + ctx, WH_TEST_SLHDSA_KAT_PARAM, whTestSlhDsaKeyGenSeed, + sizeof(whTestSlhDsaKeyGenSeed), key); + if (ret == 0) { + ret = wh_Client_SlhDsaImportKey(ctx, key, &keyId, + WH_NVM_FLAGS_USAGE_SIGN, 0, NULL); + } + if (ret != 0) { + WH_ERROR_PRINT("Failed to import an SLH-DSA private key: %d\n", ret); + } + + for (i = 0; (ret == 0) && (i + skSz <= WOLFHSM_CFG_COMM_DATA_LEN); i++) { + if (memcmp(comm + i, whTestSlhDsaKeyGenSeed, skSz) == 0) { + WH_ERROR_PRINT("SLH-DSA private key left in the comm buffer\n"); + ret = WH_TEST_FAIL; + } + } + + if (ret == 0) { + WH_TEST_PRINT("SLH-DSA IMPORT CLEARS COMM DEVID=0x%X SUCCESS\n", devId); + } + + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + wc_SlhDsaKey_Free(key); + return ret; +} +#endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */ + +static void _whTest_SlhDsaLongMsg(byte* msg) +{ + word32 i; + + for (i = 0; i < WH_TEST_SLHDSA_LONG_MSG_LEN; i++) { + msg[i] = (byte)i; + } +} + +/* Verify a vector through the wolfCrypt API; msg and sig are restored */ +static int _whTest_SlhDsaVerifyVector(int devId, byte* msg, word32 msgSz, + byte* sig) +{ + int ret; + SlhDsaKey key[1]; + word32 sigSz = WH_TEST_SLHDSA_KAT_SIG_LEN; + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_KAT_PARAM, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to initialize SLH-DSA key: %d\n", ret); + return ret; + } + + ret = wc_SlhDsaKey_ImportPublic(key, whTestSlhDsaVerifyPub, + (word32)sizeof(whTestSlhDsaVerifyPub)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to import SLH-DSA public key: %d\n", ret); + } + + if (ret == 0) { + ret = wc_SlhDsaKey_Verify(key, NULL, 0, msg, msgSz, sig, sigSz); + if (ret != 0) { + WH_ERROR_PRINT("SLH-DSA vector did not verify: %d\n", ret); + } + } + + /* A changed message must not verify against the vector's signature. */ + if (ret == 0) { + msg[0] ^= 1; + if (wc_SlhDsaKey_Verify(key, NULL, 0, msg, msgSz, sig, sigSz) == 0) { + WH_ERROR_PRINT("SLH-DSA verified a modified message\n"); + ret = WH_TEST_FAIL; + } + msg[0] ^= 1; + } + + if (ret == 0) { + sig[0] ^= 1; + if (wc_SlhDsaKey_Verify(key, NULL, 0, msg, msgSz, sig, sigSz) == 0) { + WH_ERROR_PRINT("SLH-DSA verified a modified signature\n"); + ret = WH_TEST_FAIL; + } + sig[0] ^= 1; + } + + if (ret == 0) { + WH_TEST_PRINT("SLH-DSA VERIFY KAT LEN=%u DEVID=0x%X SUCCESS\n", + (unsigned)msgSz, devId); + } + + wc_SlhDsaKey_Free(key); + return ret; +} + +static int _whTest_CryptoSlhDsaVerifyKat(int devId) +{ + byte msg[sizeof(whTestSlhDsaVerifyMsg)]; + byte sig[WH_TEST_SLHDSA_KAT_SIG_LEN]; + + memcpy(msg, whTestSlhDsaVerifyMsg, sizeof(msg)); + memcpy(sig, whTestSlhDsaVerifySig, sizeof(sig)); + return _whTest_SlhDsaVerifyVector(devId, msg, (word32)sizeof(msg), sig); +} + +/* Same vector with the public key cached on the server */ +static int _whTest_CryptoSlhDsaVerifyKatCached(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + whKeyId keyId = WH_KEYID_ERASED; + SlhDsaKey key[1]; + SlhDsaKey handle[1]; + SlhDsaKey readback[1]; + int keyInit = 0; + int handleInit = 0; + int readbackInit = 0; + uint8_t label[] = "SlhDsaVerifyKat"; + byte pub[WH_TEST_SLHDSA_KAT_PUB_LEN]; + word32 pubSz = sizeof(pub); + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_KAT_PARAM, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to initialize SLH-DSA key: %d\n", ret); + return ret; + } + keyInit = 1; + + ret = wc_SlhDsaKey_ImportPublic(key, whTestSlhDsaVerifyPub, + (word32)sizeof(whTestSlhDsaVerifyPub)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to import SLH-DSA public key: %d\n", ret); + goto done; + } + + ret = wh_Client_SlhDsaImportKey(ctx, key, &keyId, WH_NVM_FLAGS_USAGE_VERIFY, + sizeof(label), label); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache the SLH-DSA public key: %d\n", ret); + goto done; + } + + ret = wc_SlhDsaKey_Init(readback, WH_TEST_SLHDSA_KAT_PARAM, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to initialize SLH-DSA readback: %d\n", ret); + goto done; + } + readbackInit = 1; + + ret = wh_Client_SlhDsaExportPublicKey(ctx, keyId, readback, 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT("Failed to export the cached public key: %d\n", ret); + goto done; + } + + ret = wc_SlhDsaKey_ExportPublic(readback, pub, &pubSz); + if (ret != 0) { + WH_ERROR_PRINT("Failed to read back the public key: %d\n", ret); + goto done; + } + if ((pubSz != sizeof(whTestSlhDsaVerifyPub)) || + (memcmp(pub, whTestSlhDsaVerifyPub, pubSz) != 0)) { + WH_ERROR_PRINT("Cached SLH-DSA public key changed in transit\n"); + ret = WH_TEST_FAIL; + goto done; + } + + /* A bare handle: no key material, only the server key ID */ + ret = wc_SlhDsaKey_Init(handle, WH_TEST_SLHDSA_KAT_PARAM, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to initialize SLH-DSA handle: %d\n", ret); + goto done; + } + handleInit = 1; + + ret = wh_Client_SlhDsaSetKeyId(handle, keyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to set SLH-DSA key id: %d\n", ret); + goto done; + } + + ret = wc_SlhDsaKey_Verify(handle, NULL, 0, whTestSlhDsaVerifyMsg, + (word32)sizeof(whTestSlhDsaVerifyMsg), + whTestSlhDsaVerifySig, + (word32)sizeof(whTestSlhDsaVerifySig)); + if (ret != 0) { + WH_ERROR_PRINT("Cached-key SLH-DSA verify failed: %d\n", ret); + goto done; + } + + { + byte msg[sizeof(whTestSlhDsaVerifyMsg)]; + + memcpy(msg, whTestSlhDsaVerifyMsg, sizeof(msg)); + msg[0] ^= 1; + if (wc_SlhDsaKey_Verify(handle, NULL, 0, msg, (word32)sizeof(msg), + whTestSlhDsaVerifySig, + (word32)sizeof(whTestSlhDsaVerifySig)) == 0) { + WH_ERROR_PRINT("Cached key verified a modified message\n"); + ret = WH_TEST_FAIL; + goto done; + } + } + +#if (WH_TEST_SLHDSA_KAT_SIG_LEN + WH_TEST_SLHDSA_LONG_MSG_LEN) > \ + WOLFHSM_CFG_COMM_DATA_LEN + /* Too big without DMA, even with a local public key to verify with */ + { + SlhDsaKey* keys[] = {handle, key}; + byte msg[WH_TEST_SLHDSA_LONG_MSG_LEN]; + int useDma = 0; + int expect = BAD_FUNC_ARG; + size_t k; + + (void)wh_Client_GetDmaMode(ctx, &useDma); + if (useDma != 0) { + expect = 0; + } + _whTest_SlhDsaLongMsg(msg); + for (k = 0; k < sizeof(keys) / sizeof(keys[0]); k++) { + ret = + wc_SlhDsaKey_Verify(keys[k], NULL, 0, msg, (word32)sizeof(msg), + whTestSlhDsaVerifyLongSig, + (word32)sizeof(whTestSlhDsaVerifyLongSig)); + if (ret != expect) { + WH_ERROR_PRINT("Long message verify with key %u returned %d, " + "expected %d\n", + (unsigned)k, ret, expect); + ret = WH_TEST_FAIL; + goto done; + } + } + } +#endif /* KAT request exceeds WOLFHSM_CFG_COMM_DATA_LEN */ + + WH_TEST_PRINT("SLH-DSA VERIFY KAT CACHED DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + if (handleInit) { + wc_SlhDsaKey_Free(handle); + } + if (readbackInit) { + wc_SlhDsaKey_Free(readback); + } + if (keyInit) { + wc_SlhDsaKey_Free(key); + } + return ret; +} + +#ifdef WOLFSSL_SLHDSA_VERIFY_ONLY +/* Keygen and sign must fail cleanly in a verify-only build */ +static int _whTest_CryptoSlhDsaNoSign(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + whKeyId keyId = WH_KEYID_ERASED; + SlhDsaKey key[1]; + uint8_t label[] = "SlhDsaNoSign"; + byte sig[WH_TEST_SLHDSA_KAT_SIG_LEN]; + word32 sigLen = sizeof(sig); + + /* Use signing usage flags so only the missing keygen can refuse it */ + ret = wh_Client_SlhDsaMakeCacheKey(ctx, WH_TEST_SLHDSA_KAT_PARAM, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | + WH_NVM_FLAGS_USAGE_VERIFY, + sizeof(label), label); + if (ret == 0) { + WH_ERROR_PRINT("Verify-only server generated an SLH-DSA key\n"); + (void)wh_Client_KeyEvict(ctx, keyId); + return WH_TEST_FAIL; + } + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_KAT_PARAM, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to initialize SLH-DSA key: %d\n", ret); + return ret; + } + + ret = wc_SlhDsaKey_ImportPublic(key, whTestSlhDsaVerifyPub, + (word32)sizeof(whTestSlhDsaVerifyPub)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to import SLH-DSA public key: %d\n", ret); + } + + if (ret == 0) { + ret = wh_Client_SlhDsaSign( + ctx, whTestSlhDsaVerifyMsg, (word32)sizeof(whTestSlhDsaVerifyMsg), + sig, &sigLen, key, NULL, 0, WC_HASH_TYPE_NONE, NULL, 0, 1, 0); + if (ret == 0) { + WH_ERROR_PRINT("Verify-only server signed with SLH-DSA\n"); + ret = WH_TEST_FAIL; + } + else { + ret = 0; + } + } + + /* The refused requests must not have left the two sides out of step. */ + if (ret == 0) { + ret = wc_SlhDsaKey_Verify(key, NULL, 0, whTestSlhDsaVerifyMsg, + (word32)sizeof(whTestSlhDsaVerifyMsg), + whTestSlhDsaVerifySig, + (word32)sizeof(whTestSlhDsaVerifySig)); + if (ret != 0) { + WH_ERROR_PRINT("Verify after a refused request failed: %d\n", ret); + } + } + + if (ret == 0) { + WH_TEST_PRINT("SLH-DSA VERIFY ONLY REFUSALS DEVID=0x%X SUCCESS\n", + devId); + } + + wc_SlhDsaKey_Free(key); + return ret; +} +#endif /* WOLFSSL_SLHDSA_VERIFY_ONLY */ + +#endif /* WH_TEST_SLHDSA_KAT_PARAM */ + +#if defined(WH_TEST_SLHDSA_DMA_PARAM) && defined(WOLFHSM_CFG_DMA) +/* Sign and verify end to end over DMA */ +static int _whTest_CryptoSlhDsaDmaClient(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + SlhDsaKey key[1]; + byte msg[] = "Test message for DMA SLH-DSA"; + WH_DMA_ALIGNED byte sig[WH_DMA_PAD_SIZE(WH_TEST_SLHDSA_DMA_SIG_LEN)]; + word32 sigLen = sizeof(sig); + int verified = 0; + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_DMA_PARAM, NULL, devId); + if (ret != 0) { + return ret; + } + + ret = wh_Client_SlhDsaMakeExportKeyDma(ctx, WH_TEST_SLHDSA_DMA_PARAM, key); + if (ret != 0) { + WH_ERROR_PRINT("Failed to generate SLH-DSA key over DMA: %d\n", ret); + goto done; + } + + ret = wh_Client_SlhDsaSignDma(ctx, msg, sizeof(msg), sig, &sigLen, key, + NULL, 0, WC_HASH_TYPE_NONE, NULL, 0, 1, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to sign over DMA: %d\n", ret); + goto done; + } + if (sigLen != WH_TEST_SLHDSA_DMA_SIG_LEN) { + WH_ERROR_PRINT("DMA signature length %u, expected %u\n", + (unsigned)sigLen, (unsigned)WH_TEST_SLHDSA_DMA_SIG_LEN); + ret = WH_TEST_FAIL; + goto done; + } + + ret = + wh_Client_SlhDsaVerifyDma(ctx, sig, sigLen, msg, sizeof(msg), &verified, + key, NULL, 0, WC_HASH_TYPE_NONE, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to verify over DMA: %d\n", ret); + goto done; + } + if (!verified) { + WH_ERROR_PRINT("SLH-DSA DMA verification failed\n"); + ret = WH_TEST_FAIL; + goto done; + } + + sig[0] ^= 0xFF; + ret = + wh_Client_SlhDsaVerifyDma(ctx, sig, sigLen, msg, sizeof(msg), &verified, + key, NULL, 0, WC_HASH_TYPE_NONE, 0); + if (ret != 0) { + WH_ERROR_PRINT("DMA verify with modified sig returned %d\n", ret); + goto done; + } + if (verified) { + WH_ERROR_PRINT("SLH-DSA DMA verified a bad signature\n"); + ret = WH_TEST_FAIL; + goto done; + } + + verified = 1; + /* A failed request must still clear the result */ + ret = wh_Client_SlhDsaVerifyDma(ctx, sig, sigLen - 1, msg, sizeof(msg), + &verified, key, NULL, 0, WC_HASH_TYPE_NONE, + 0); + if ((ret == 0) || verified) { + WH_ERROR_PRINT("SLH-DSA DMA short signature: ret %d, verified %d\n", + ret, verified); + ret = WH_TEST_FAIL; + goto done; + } + + WH_TEST_PRINT("SLH-DSA DMA DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + wc_SlhDsaKey_Free(key); + return ret; +} + +/* Cached key over DMA, plus DMA key import and export round trip */ +static int _whTest_CryptoSlhDsaDmaCachedKey(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + whKeyId keyId = WH_KEYID_ERASED; + SlhDsaKey pub[1]; + SlhDsaKey handle[1]; + int pubInit = 0; + int handleInit = 0; + uint8_t label[] = "SlhDsaDmaCached"; + + ret = wc_SlhDsaKey_Init(pub, WH_TEST_SLHDSA_DMA_PARAM, NULL, devId); + if (ret != 0) { + return ret; + } + pubInit = 1; + + ret = wh_Client_SlhDsaMakeCacheKeyDma(ctx, WH_TEST_SLHDSA_DMA_PARAM, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | + WH_NVM_FLAGS_USAGE_VERIFY, + sizeof(label), label, pub); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache SLH-DSA key over DMA: %d\n", ret); + goto done; + } + + ret = wc_SlhDsaKey_Init(handle, WH_TEST_SLHDSA_DMA_PARAM, NULL, devId); + if (ret != 0) { + goto done; + } + handleInit = 1; + (void)wh_Client_SlhDsaSetKeyId(handle, keyId); + + { + byte msg[] = "DMA signed by a key that never left the HSM"; + WH_DMA_ALIGNED byte sig[WH_DMA_PAD_SIZE(WH_TEST_SLHDSA_DMA_SIG_LEN)]; + word32 sigLen = sizeof(sig); + int verified = 0; + + ret = + wh_Client_SlhDsaSignDma(ctx, msg, sizeof(msg), sig, &sigLen, handle, + NULL, 0, WC_HASH_TYPE_NONE, NULL, 0, 1, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to DMA sign with a cached key: %d\n", ret); + goto done; + } + + ret = wh_Client_SlhDsaVerifyDma(ctx, sig, sigLen, msg, sizeof(msg), + &verified, pub, NULL, 0, + WC_HASH_TYPE_NONE, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to DMA verify a cached-key sig: %d\n", ret); + goto done; + } + if (!verified) { + WH_ERROR_PRINT("Cached-key DMA signature did not verify\n"); + ret = WH_TEST_FAIL; + goto done; + } + } + + /* Export the public key and check it matches what keygen returned */ + { + SlhDsaKey exported[1]; + byte a[WC_SLHDSA_MAX_PUB_LEN]; + byte b[WC_SLHDSA_MAX_PUB_LEN]; + word32 aSz = sizeof(a); + word32 bSz = sizeof(b); + + ret = + wc_SlhDsaKey_Init(exported, WH_TEST_SLHDSA_DMA_PARAM, NULL, devId); + if (ret != 0) { + goto done; + } + ret = wh_Client_SlhDsaExportPublicKeyDma(ctx, keyId, exported, 0, NULL); + if (ret == 0) { + ret = wc_SlhDsaKey_ExportPublic(pub, a, &aSz); + } + if (ret == 0) { + ret = wc_SlhDsaKey_ExportPublic(exported, b, &bSz); + } + if ((ret == 0) && ((aSz != bSz) || (memcmp(a, b, aSz) != 0))) { + WH_ERROR_PRINT("Exported SLH-DSA public key does not match\n"); + ret = WH_TEST_FAIL; + } + wc_SlhDsaKey_Free(exported); + if (ret != 0) { + WH_ERROR_PRINT("SLH-DSA DMA public key export failed: %d\n", ret); + goto done; + } + } + + WH_TEST_PRINT("SLH-DSA DMA CACHED KEY DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + if (handleInit) { + wc_SlhDsaKey_Free(handle); + } + if (pubInit) { + wc_SlhDsaKey_Free(pub); + } + return ret; +} + +/* Fails the client copy-back, which runs after the server has done its work */ +static int _whTest_SlhDsaFailWritePostCb(whClientContext* client, + uintptr_t clientAddr, void** ptr, + size_t len, whDmaOper oper, + whDmaFlags flags) +{ + (void)client; + (void)len; + (void)flags; + *ptr = (void*)clientAddr; + return (oper == WH_DMA_OPER_CLIENT_WRITE_POST) ? WH_ERROR_ABORTED + : WH_ERROR_OK; +} + +/* Evict the cached key on a client failure, even for a caller-chosen id */ +static int _whTest_CryptoSlhDsaDmaCacheKeyRollback(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + whKeyId keyId = WH_KEYID_ERASED; + whKeyId explicitId = WH_KEYID_ERASED; + SlhDsaKey pub[1]; + whClientDmaClientMemCb savedCb = ctx->dma.cb; + + ret = wc_SlhDsaKey_Init(pub, WH_TEST_SLHDSA_DMA_PARAM, NULL, devId); + if (ret != 0) { + return ret; + } + + /* Borrow an id the server knows is free */ + ret = wh_Client_SlhDsaMakeCacheKeyDma(ctx, WH_TEST_SLHDSA_DMA_PARAM, &keyId, + WH_NVM_FLAGS_NONE, 0, NULL, pub); + if (ret == 0) { + explicitId = keyId; + ret = wh_Client_KeyEvict(ctx, explicitId); + } + if (ret != 0) { + WH_ERROR_PRINT("Failed to reserve an SLH-DSA key id: %d\n", ret); + } + + if (ret == 0) { + (void)wh_Client_DmaRegisterCb(ctx, _whTest_SlhDsaFailWritePostCb); + ret = wh_Client_SlhDsaMakeCacheKeyDma(ctx, WH_TEST_SLHDSA_DMA_PARAM, + &keyId, WH_NVM_FLAGS_NONE, 0, + NULL, pub); + (void)wh_Client_DmaRegisterCb(ctx, savedCb); + if ((ret != WH_ERROR_ABORTED) || !WH_KEYID_ISERASED(keyId)) { + WH_ERROR_PRINT("SLH-DSA DMA keygen copy-back failure returned " + "%d keyId 0x%X\n", + ret, keyId); + ret = WH_TEST_FAIL; + } + else { + ret = 0; + } + } + + /* Nothing may stay cached under the caller's id */ + if (ret == 0) { + ret = wh_Client_KeyEvict(ctx, explicitId); + if (ret != WH_ERROR_NOTFOUND) { + WH_ERROR_PRINT("SLH-DSA key left cached after failure: %d\n", ret); + ret = WH_TEST_FAIL; + } + else { + ret = 0; + } + } + + if (ret == 0) { + WH_TEST_PRINT("SLH-DSA DMA CACHE ROLLBACK DEVID=0x%X SUCCESS\n", devId); + } + + wc_SlhDsaKey_Free(pub); + return ret; +} +#endif /* WH_TEST_SLHDSA_DMA_PARAM && WOLFHSM_CFG_DMA */ + +#ifdef WH_TEST_SLHDSA_COMM_PARAM +/* Larger parameter sets must return an error, not a truncated signature */ +#if defined(WOLFSSL_SLHDSA_PARAM_192S) || defined(WOLFSSL_SLHDSA_PARAM_256S) +#if defined(WOLFSSL_SLHDSA_PARAM_192S) +#define WH_TEST_SLHDSA_OVERSIZE_PARAM SLHDSA_SHAKE192S +#else +#define WH_TEST_SLHDSA_OVERSIZE_PARAM SLHDSA_SHAKE256S +#endif /* WOLFSSL_SLHDSA_PARAM_192S */ +static int _whTest_CryptoSlhDsaCommBufferLimit(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + SlhDsaKey key[1]; + byte msg[] = "too big for the comm buffer"; + byte sig[WC_SLHDSA_MAX_SIG_LEN]; + word32 sigLen = sizeof(sig); + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_OVERSIZE_PARAM, NULL, devId); + if (ret != 0) { + return ret; + } + + ret = + wh_Client_SlhDsaMakeExportKey(ctx, WH_TEST_SLHDSA_OVERSIZE_PARAM, key); + if (ret != 0) { + WH_ERROR_PRINT("Failed to generate oversize SLH-DSA key: %d\n", ret); + goto done; + } + + ret = wh_Client_SlhDsaSign(ctx, msg, sizeof(msg), sig, &sigLen, key, NULL, + 0, WC_HASH_TYPE_NONE, NULL, 0, 1, 0); + if (ret != WH_ERROR_BUFFER_SIZE) { + WH_ERROR_PRINT("Oversize sign expected WH_ERROR_BUFFER_SIZE, got %d\n", + ret); + ret = WH_TEST_FAIL; + goto done; + } + + WH_TEST_PRINT("SLH-DSA COMM LIMIT DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + wc_SlhDsaKey_Free(key); + return ret; +} +#endif /* 192S || 256S */ +#endif /* WH_TEST_SLHDSA_COMM_PARAM */ + +#ifdef WOLFSSL_HAVE_MLDSA +/* Seeded keygen for ML-DSA must be declined, not run as a random keygen */ +static int _whTest_CryptoMlDsaSeedKeyGenDeclined(whClientContext* ctx) +{ + int ret; + wc_CryptoInfo info; + MlDsaKey key[1]; + byte seed[32]; + + memset(&info, 0, sizeof(info)); + memset(seed, 0, sizeof(seed)); + + ret = wc_MlDsaKey_Init(key, NULL, INVALID_DEVID); + if (ret != 0) { + WH_ERROR_PRINT("Failed to initialize ML-DSA key: %d\n", ret); + return ret; + } + + info.algo_type = WC_ALGO_TYPE_PK; + info.pk.type = WC_PK_TYPE_PQC_SIG_KEYGEN_SEED; + info.pk.pqc_sig_kg.type = WC_PQC_SIG_TYPE_MLDSA; + info.pk.pqc_sig_kg.key = key; + info.pk.pqc_sig_kg.seed = seed; + info.pk.pqc_sig_kg.seedSz = (word32)sizeof(seed); + + ret = wh_Client_CryptoCb(WH_CLIENT_DEVID(ctx), &info, ctx); + if (ret == CRYPTOCB_UNAVAILABLE) { + ret = 0; + } + else { + WH_ERROR_PRINT("Seeded ML-DSA keygen returned %d, expected %d\n", ret, + CRYPTOCB_UNAVAILABLE); + ret = WH_TEST_FAIL; + } + + wc_MlDsaKey_Free(key); + return ret; +} +#endif /* WOLFSSL_HAVE_MLDSA */ + +int whTest_Crypto_SlhDsa(whClientContext* ctx) +{ +#ifdef WH_TEST_SLHDSA_KAT_PARAM + int i; + + /* Verify the vector in each DMA mode the build offers */ + for (i = 0; i < WH_TEST_DMA_MODE_CNT; i++) { + (void)wh_Client_SetDmaMode(ctx, i); + WH_TEST_RETURN_ON_FAIL( + _whTest_CryptoSlhDsaVerifyKat(WH_CLIENT_DEVID(ctx))); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaVerifyKatCached(ctx)); + } + (void)wh_Client_SetDmaMode(ctx, 0); +#ifdef WOLFSSL_SLHDSA_VERIFY_ONLY + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaNoSign(ctx)); +#else + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaSeededKeyGen(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaCacheKeyEphemeral(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaImportClearsComm(ctx)); +#endif /* WOLFSSL_SLHDSA_VERIFY_ONLY */ +#endif /* WH_TEST_SLHDSA_KAT_PARAM */ + +#ifdef WH_TEST_SLHDSA_COMM_PARAM + /* Run the wolfCrypt API driver in each DMA mode the build offers */ + for (i = 0; i < WH_TEST_DMA_MODE_CNT; i++) { + (void)wh_Client_SetDmaMode(ctx, i); + WH_TEST_RETURN_ON_FAIL( + _whTest_SlhDsaWolfCryptImpl(ctx, WH_CLIENT_DEVID(ctx))); + } + (void)wh_Client_SetDmaMode(ctx, 0); + + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaClient(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaCachedKey(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaMPrime(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaCheckPrivKey(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaBufferTooSmall(ctx)); +#ifndef NO_SHA256 + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaBadDigestLen(ctx)); +#endif /* !NO_SHA256 */ +#ifdef WH_TEST_SLHDSA_OVERSIZE_PARAM + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaCommBufferLimit(ctx)); +#endif /* WH_TEST_SLHDSA_OVERSIZE_PARAM */ +#endif /* WH_TEST_SLHDSA_COMM_PARAM */ + +#ifdef WH_TEST_SLHDSA_FAST_PARAM + /* The KAT vector is SLH-DSA-SHAKE-128f, so it needs that parameter. */ + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaSeededKat(ctx)); +#endif /* WH_TEST_SLHDSA_FAST_PARAM */ +#if defined(WH_TEST_SLHDSA_DMA_PARAM) && defined(WOLFHSM_CFG_DMA) + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaDmaClient(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaDmaCachedKey(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaDmaCacheKeyRollback(ctx)); +#endif /* WH_TEST_SLHDSA_DMA_PARAM && WOLFHSM_CFG_DMA */ + +#ifdef WOLFSSL_HAVE_MLDSA + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaSeedKeyGenDeclined(ctx)); +#endif /* WOLFSSL_HAVE_MLDSA */ + + (void)ctx; +#if !defined(WH_TEST_SLHDSA_COMM_PARAM) && \ + !defined(WH_TEST_SLHDSA_FAST_PARAM) && !defined(WH_TEST_SLHDSA_KAT_PARAM) + /* Nothing above was built, so report that rather than a pass. */ + return WH_TEST_SKIPPED; +#else + return 0; +#endif /* no WH_TEST_SLHDSA_*_PARAM */ +} + +#endif /* WOLFSSL_HAVE_SLHDSA */ + +#endif /* !WOLFHSM_CFG_NO_CRYPTO */ diff --git a/test-refactor/misc/wh_test_check_struct_padding.c b/test-refactor/misc/wh_test_check_struct_padding.c index 699d503ca..19afdd3ac 100644 --- a/test-refactor/misc/wh_test_check_struct_padding.c +++ b/test-refactor/misc/wh_test_check_struct_padding.c @@ -141,6 +141,14 @@ whMessageCrypto_MlDsaSignRequest pkMldsaSignReq; whMessageCrypto_MlDsaSignResponse pkMldsaSignRes; whMessageCrypto_MlDsaVerifyRequest pkMldsaVerifyReq; whMessageCrypto_MlDsaVerifyResponse pkMldsaVerifyRes; +whMessageCrypto_SlhDsaKeyGenRequest pkSlhdsaKeygenReq; +whMessageCrypto_SlhDsaKeyGenResponse pkSlhdsaKeygenRes; +whMessageCrypto_SlhDsaSignRequest pkSlhdsaSignReq; +whMessageCrypto_SlhDsaSignResponse pkSlhdsaSignRes; +whMessageCrypto_SlhDsaVerifyRequest pkSlhdsaVerifyReq; +whMessageCrypto_SlhDsaVerifyResponse pkSlhdsaVerifyRes; +whMessageCrypto_SlhDsaCheckPrivKeyRequest pkSlhdsaCheckReq; +whMessageCrypto_SlhDsaCheckPrivKeyResponse pkSlhdsaCheckRes; whMessageCrypto_MlKemKeyGenRequest pkMlkemKeygenReq; whMessageCrypto_MlKemKeyGenResponse pkMlkemKeygenRes; whMessageCrypto_MlKemEncapsRequest pkMlkemEncapsReq; @@ -163,6 +171,12 @@ whMessageCrypto_MlDsaSignDmaRequest pqMldsaSignDmaReq; whMessageCrypto_MlDsaSignDmaResponse pqMldsaSignDmaRes; whMessageCrypto_MlDsaVerifyDmaRequest pqMldsaVerifyDmaReq; whMessageCrypto_MlDsaVerifyDmaResponse pqMldsaVerifyDmaRes; +whMessageCrypto_SlhDsaKeyGenDmaRequest pqSlhdsaKeygenDmaReq; +whMessageCrypto_SlhDsaKeyGenDmaResponse pqSlhdsaKeygenDmaRes; +whMessageCrypto_SlhDsaSignDmaRequest pqSlhdsaSignDmaReq; +whMessageCrypto_SlhDsaSignDmaResponse pqSlhdsaSignDmaRes; +whMessageCrypto_SlhDsaVerifyDmaRequest pqSlhdsaVerifyDmaReq; +whMessageCrypto_SlhDsaVerifyDmaResponse pqSlhdsaVerifyDmaRes; whMessageCrypto_CmacAesDmaRequest cmacDmaReq; whMessageCrypto_CmacAesDmaResponse cmacDmaRes; whMessageCrypto_MlKemKeyGenDmaRequest pkMlkemKeygenDmaReq; diff --git a/test-refactor/posix/Makefile b/test-refactor/posix/Makefile index f0217de3c..21a1c7ef4 100644 --- a/test-refactor/posix/Makefile +++ b/test-refactor/posix/Makefile @@ -159,6 +159,10 @@ ifeq ($(NOKEYGEN),1) DEF += -DWOLFHSM_CFG_TEST_NO_KEY_GEN endif +ifeq ($(SLHDSA_VERIFY_ONLY),1) + DEF += -DWOLFSSL_SLHDSA_VERIFY_ONLY +endif + ifneq ($(KEYCACHE_BIG_BUFSIZE),) DEF += -DWOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE=$(KEYCACHE_BIG_BUFSIZE) endif diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c index b3c4de2ab..654e080c2 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -84,6 +84,7 @@ WH_TEST_DECL(whTest_Crypto_Rng); WH_TEST_DECL(whTest_Crypto_Rsa); WH_TEST_DECL(whTest_Crypto_Sha); WH_TEST_DECL(whTest_Crypto_Sha3); +WH_TEST_DECL(whTest_Crypto_SlhDsa); WH_TEST_DECL(whTest_Crypto_WcId); WH_TEST_DECL(whTest_Crypto_Xmss); WH_TEST_DECL(whTest_CryptoEcc256); @@ -180,6 +181,7 @@ const whTestCase whTestsClient[] = { {"whTest_Crypto_Rsa", whTest_Crypto_Rsa}, {"whTest_Crypto_Sha", whTest_Crypto_Sha}, {"whTest_Crypto_Sha3", whTest_Crypto_Sha3}, + {"whTest_Crypto_SlhDsa", whTest_Crypto_SlhDsa}, {"whTest_Crypto_WcId", whTest_Crypto_WcId}, {"whTest_Crypto_Xmss", whTest_Crypto_Xmss}, {"whTest_CryptoEcc256", whTest_CryptoEcc256}, diff --git a/test/config/user_settings.h b/test/config/user_settings.h index 669706220..37f3fa480 100644 --- a/test/config/user_settings.h +++ b/test/config/user_settings.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * @@ -144,6 +144,12 @@ * compiled and exercised by the test suite. */ #define WOLFSSL_HASH_FLAGS +/* SLH-DSA: only 128s fits in the default WOLFHSM_CFG_COMM_DATA_LEN */ +#define WOLFSSL_HAVE_SLHDSA +#define WOLFSSL_SLHDSA_PARAM_NO_128F +#define WOLFSSL_SLHDSA_PARAM_NO_192 +#define WOLFSSL_SLHDSA_PARAM_NO_256 + /* ML-KEM Options */ #define WOLFSSL_HAVE_MLKEM /* LMS / HSS Options (RFC 8554, NIST SP 800-208) */ diff --git a/test/wh_test_check_struct_padding.c b/test/wh_test_check_struct_padding.c index f716566b6..db1487b5a 100644 --- a/test/wh_test_check_struct_padding.c +++ b/test/wh_test_check_struct_padding.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * @@ -139,6 +139,14 @@ whMessageCrypto_MlDsaSignRequest pkMldsaSignReq; whMessageCrypto_MlDsaSignResponse pkMldsaSignRes; whMessageCrypto_MlDsaVerifyRequest pkMldsaVerifyReq; whMessageCrypto_MlDsaVerifyResponse pkMldsaVerifyRes; +whMessageCrypto_SlhDsaKeyGenRequest pkSlhdsaKeygenReq; +whMessageCrypto_SlhDsaKeyGenResponse pkSlhdsaKeygenRes; +whMessageCrypto_SlhDsaSignRequest pkSlhdsaSignReq; +whMessageCrypto_SlhDsaSignResponse pkSlhdsaSignRes; +whMessageCrypto_SlhDsaVerifyRequest pkSlhdsaVerifyReq; +whMessageCrypto_SlhDsaVerifyResponse pkSlhdsaVerifyRes; +whMessageCrypto_SlhDsaCheckPrivKeyRequest pkSlhdsaCheckReq; +whMessageCrypto_SlhDsaCheckPrivKeyResponse pkSlhdsaCheckRes; whMessageCrypto_MlKemKeyGenRequest pkMlkemKeygenReq; whMessageCrypto_MlKemKeyGenResponse pkMlkemKeygenRes; whMessageCrypto_MlKemEncapsRequest pkMlkemEncapsReq; @@ -161,6 +169,12 @@ whMessageCrypto_MlDsaSignDmaRequest pqMldsaSignDmaReq; whMessageCrypto_MlDsaSignDmaResponse pqMldsaSignDmaRes; whMessageCrypto_MlDsaVerifyDmaRequest pqMldsaVerifyDmaReq; whMessageCrypto_MlDsaVerifyDmaResponse pqMldsaVerifyDmaRes; +whMessageCrypto_SlhDsaKeyGenDmaRequest pqSlhdsaKeygenDmaReq; +whMessageCrypto_SlhDsaKeyGenDmaResponse pqSlhdsaKeygenDmaRes; +whMessageCrypto_SlhDsaSignDmaRequest pqSlhdsaSignDmaReq; +whMessageCrypto_SlhDsaSignDmaResponse pqSlhdsaSignDmaRes; +whMessageCrypto_SlhDsaVerifyDmaRequest pqSlhdsaVerifyDmaReq; +whMessageCrypto_SlhDsaVerifyDmaResponse pqSlhdsaVerifyDmaRes; whMessageCrypto_CmacAesDmaRequest cmacDmaReq; whMessageCrypto_CmacAesDmaResponse cmacDmaRes; whMessageCrypto_MlKemKeyGenDmaRequest pkMlkemKeygenDmaReq; diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h index 4899a3e80..a38eb0003 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * @@ -52,6 +52,7 @@ #include "wolfssl/wolfcrypt/ed25519.h" #include "wolfssl/wolfcrypt/wc_mldsa.h" #include "wolfssl/wolfcrypt/wc_mlkem.h" +#include "wolfssl/wolfcrypt/wc_slhdsa.h" #include "wolfssl/wolfcrypt/hmac.h" #ifdef WOLFSSL_SHA3 #include "wolfssl/wolfcrypt/sha3.h" @@ -3412,6 +3413,378 @@ int wh_Client_MlDsaCheckPrivKeyDma(whClientContext* ctx, wc_MlDsaKey* key, #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + +/** + * @brief Associates an SLH-DSA key with a specific key ID. + * + * This function sets the device context of an SLH-DSA key to the specified + * key ID. On the server side, this key ID is used to reference the key stored + * in the HSM. + * + * @param[in] key Pointer to the SLH-DSA key structure. + * @param[in] keyId Key ID to be associated with the SLH-DSA key. + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaSetKeyId(SlhDsaKey* key, whKeyId keyId); + +/** + * @brief Gets the wolfHSM keyId being used by the wolfCrypt struct. + * + * @param[in] key Pointer to the SLH-DSA key structure. + * @param[out] outId Pointer to the key ID to return. + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaGetKeyId(SlhDsaKey* key, whKeyId* outId); + +/** + * @brief Import an SLH-DSA key to the server key cache. + * + * @param[in] ctx Pointer to the client context + * @param[in] key Pointer to the key to import + * @param[in,out] inout_keyId Pointer to key ID to use/receive + * @param[in] flags Flags to control key persistence + * @param[in] label_len Length of optional label + * @param[in] label Optional label to associate with key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaImportKey(whClientContext* ctx, SlhDsaKey* key, + whKeyId* inout_keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label); + +/** + * @brief Export an SLH-DSA key from the server. + * + * @param[in] ctx Pointer to the client context + * @param[in] keyId ID of key to export + * @param[out] key Pointer to receive exported key + * @param[in] label_len Length of optional label buffer + * @param[in] label Optional buffer to receive key label + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaExportKey(whClientContext* ctx, whKeyId keyId, + SlhDsaKey* key, uint16_t label_len, + uint8_t* label); + +/** + * @brief Exports only the public part of a cached SLH-DSA key. + * + * The caller must initialize key with wc_SlhDsaKey_Init. The parameter set + * is taken from the exported DER, so any set may be used for init. + * + * @param[in] ctx Pointer to the client context + * @param[in] keyId Server key ID whose public key should be exported + * @param[out] key Pointer to receive the exported public key + * @param[in] label_len Length of optional label buffer + * @param[in] label Optional buffer to receive key label + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaExportPublicKey(whClientContext* ctx, whKeyId keyId, + SlhDsaKey* key, uint16_t label_len, + uint8_t* label); + +/** + * @brief Generate an SLH-DSA key on the server and leave it cached there. + * + * @param[in] ctx Pointer to the client context + * @param[in] param Parameter set to generate (enum SlhDsaParam) + * @param[in,out] inout_key_id Pointer to key ID to use/receive + * @param[in] flags Flags to control key persistence, must not be EPHEMERAL + * @param[in] label_len Length of optional label + * @param[in] label Optional label to associate with key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaMakeCacheKey(whClientContext* ctx, int param, + whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, uint8_t* label); + +/** + * @brief Generate a cached SLH-DSA key and export its public part. + * + * On success, pub can also be used as a handle to the cached private key. + * + * @param[in] ctx Pointer to the client context + * @param[in] param Parameter set to generate (enum SlhDsaParam) + * @param[in,out] inout_key_id Pointer to key ID to use/receive + * @param[in] flags Flags to control key persistence, must not be EPHEMERAL + * @param[in] label_len Length of optional label + * @param[in] label Optional label to associate with key + * @param[out] pub Pointer to receive the public key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaMakeCacheKeyAndExportPublic( + whClientContext* ctx, int param, whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, const uint8_t* label, SlhDsaKey* pub); + +/** + * @brief Generate an ephemeral SLH-DSA key and export it to the caller. + * + * @param[in] ctx Pointer to the client context + * @param[in] param Parameter set to generate (enum SlhDsaParam) + * @param[out] key Pointer to receive the generated key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaMakeExportKey(whClientContext* ctx, int param, + SlhDsaKey* key); + +/** + * @brief Generate an ephemeral SLH-DSA key from a caller-supplied seed. + * + * The seed is SK.seed || SK.prf || PK.seed, 3n bytes for the requested + * parameter set. + * + * @param[in] ctx Pointer to the client context + * @param[in] param Parameter set to generate (enum SlhDsaParam) + * @param[in] seed Pointer to the 3n seed bytes + * @param[in] seedSz Length of seed in bytes + * @param[out] key Pointer to receive the generated key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaMakeExportKeyFromSeed(whClientContext* ctx, int param, + const byte* seed, word32 seedSz, + SlhDsaKey* key); + +/** + * @brief Generate a cached SLH-DSA key from a caller-supplied seed. + * + * @param[in] ctx Pointer to the client context + * @param[in] param Parameter set to generate (enum SlhDsaParam) + * @param[in] seed Pointer to the 3n seed bytes + * @param[in] seedSz Length of seed in bytes + * @param[in,out] inout_key_id Pointer to key ID to use/receive + * @param[in] flags Flags to control key persistence, must not be EPHEMERAL + * @param[in] label_len Length of optional label + * @param[in] label Optional label to associate with key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaMakeCacheKeyFromSeed(whClientContext* ctx, int param, + const byte* seed, word32 seedSz, + whKeyId* inout_key_id, + whNvmFlags flags, uint16_t label_len, + uint8_t* label); + +/** + * @brief Sign a message or digest with an SLH-DSA key on the server. + * + * preHashType selects pure SLH-DSA (WC_HASH_TYPE_NONE) or HashSLH-DSA. + * isMPrime signs a caller-built M' and ignores context and preHashType. + * A non-empty addRnd sets the randomizer. Otherwise randomized selects a + * server-generated randomizer or deterministic signing. + * + * @param[in] ctx Pointer to the client context + * @param[in] in Message, digest, or M' to sign + * @param[in] in_len Length of in in bytes + * @param[out] out Buffer to receive the signature + * @param[in,out] inout_len Capacity of out on entry, signature length on exit + * @param[in] key Key handle, either server-resident or containing key material + * @param[in] context FIPS 205 context string, may be NULL + * @param[in] contextLen Length of context, 0 to 255 + * @param[in] preHashType Pre-hash algorithm (enum wc_HashType) + * @param[in] addRnd Explicit randomizer, may be NULL + * @param[in] addRndSz Length of addRnd in bytes, 0 if none + * @param[in] randomized Non-zero to have the server pick the randomizer + * @param[in] isMPrime Non-zero when in is a caller-built M' + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaSign(whClientContext* ctx, const byte* in, word32 in_len, + byte* out, word32* inout_len, SlhDsaKey* key, + const byte* context, byte contextLen, + word32 preHashType, const byte* addRnd, byte addRndSz, + int randomized, int isMPrime); + +/** + * @brief Verify an SLH-DSA signature with a key on the server. + * + * @param[in] ctx Pointer to the client context + * @param[in] sig Signature to verify + * @param[in] sig_len Length of sig in bytes + * @param[in] msg Message, digest, or M' that was signed + * @param[in] msg_len Length of msg in bytes + * @param[out] out_res Set to 1 when the signature verifies, 0 otherwise + * @param[in] key Key handle, either server-resident or containing key material + * @param[in] context FIPS 205 context string, may be NULL + * @param[in] contextLen Length of context, 0 to 255 + * @param[in] preHashType Pre-hash algorithm (enum wc_HashType) + * @param[in] isMPrime Non-zero when msg is a caller-built M' + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaVerify(whClientContext* ctx, const byte* sig, + word32 sig_len, const byte* msg, word32 msg_len, + int* out_res, SlhDsaKey* key, const byte* context, + byte contextLen, word32 preHashType, int isMPrime); + +/** + * @brief Check that an SLH-DSA private key on the server matches a public + * key. + * + * Passing NULL for pubKey (with pubKeySz 0) has the server check only the + * internal consistency of its private key. + * + * @param[in] ctx Pointer to the client context + * @param[in] key Key handle for the private key + * @param[in] pubKey Public key bytes, PK.seed || PK.root, or NULL + * @param[in] pubKeySz Length of pubKey in bytes, 2n, or 0 when pubKey is NULL + * @return int Returns 0 when they match, WC_KEY_MISMATCH_E when they do not, + * or a negative error code on failure. + */ +int wh_Client_SlhDsaCheckPrivKey(whClientContext* ctx, SlhDsaKey* key, + const byte* pubKey, word32 pubKeySz); + +#ifdef WOLFHSM_CFG_DMA + +/** + * @brief Import an SLH-DSA key to the server key cache using DMA. + * + * @param[in] ctx Pointer to the client context + * @param[in] key Pointer to the key to import + * @param[in,out] inout_keyId Pointer to key ID to use/receive + * @param[in] flags Flags to control key persistence + * @param[in] label_len Length of optional label + * @param[in] label Optional label to associate with key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaImportKeyDma(whClientContext* ctx, SlhDsaKey* key, + whKeyId* inout_keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label); + +/** + * @brief Export an SLH-DSA key from the server using DMA. + * + * @param[in] ctx Pointer to the client context + * @param[in] keyId ID of key to export + * @param[out] key Pointer to receive exported key + * @param[in] label_len Length of optional label buffer + * @param[in] label Optional buffer to receive key label + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaExportKeyDma(whClientContext* ctx, whKeyId keyId, + SlhDsaKey* key, uint16_t label_len, + uint8_t* label); + +/** + * @brief Export only the public part of a cached SLH-DSA key using DMA. + * + * @param[in] ctx Pointer to the client context + * @param[in] keyId Server key ID whose public key should be exported + * @param[out] key Pointer to receive the exported public key + * @param[in] label_len Length of optional label buffer + * @param[in] label Optional buffer to receive key label + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaExportPublicKeyDma(whClientContext* ctx, whKeyId keyId, + SlhDsaKey* key, uint16_t label_len, + uint8_t* label); + +/** + * @brief Generate an ephemeral SLH-DSA key and export it using DMA. + * + * @param[in] ctx Pointer to the client context + * @param[in] param Parameter set to generate (enum SlhDsaParam) + * @param[out] key Pointer to receive the generated key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaMakeExportKeyDma(whClientContext* ctx, int param, + SlhDsaKey* key); + +/** + * @brief Generate an ephemeral SLH-DSA key from a seed using DMA. + * + * @param[in] ctx Pointer to the client context + * @param[in] param Parameter set to generate (enum SlhDsaParam) + * @param[in] seed Pointer to the 3n seed bytes + * @param[in] seedSz Length of seed in bytes + * @param[out] key Pointer to receive the generated key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaMakeExportKeyFromSeedDma(whClientContext* ctx, int param, + const byte* seed, word32 seedSz, + SlhDsaKey* key); + +/** + * @brief Generate a cached SLH-DSA key and export its public part using DMA. + * + * @param[in] ctx Pointer to the client context + * @param[in] param Parameter set to generate (enum SlhDsaParam) + * @param[in,out] inout_key_id Pointer to key ID to use/receive + * @param[in] flags Flags to control key persistence, must not be EPHEMERAL + * @param[in] label_len Length of optional label + * @param[in] label Optional label to associate with key + * @param[out] pub Pointer to receive the public key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaMakeCacheKeyDma(whClientContext* ctx, int param, + whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, const uint8_t* label, + SlhDsaKey* pub); + +/** + * @brief Sign with an SLH-DSA key on the server using DMA. + * + * Arguments match wh_Client_SlhDsaSign. The message and signature are + * passed by DMA, so the larger parameter sets fit. + * + * @param[in] ctx Pointer to the client context + * @param[in] in Message, digest, or M' to sign + * @param[in] in_len Length of in in bytes + * @param[out] out Buffer to receive the signature + * @param[in,out] out_len Capacity of out on entry, signature length on exit + * @param[in] key Key handle, either server-resident or containing key material + * @param[in] context FIPS 205 context string, may be NULL + * @param[in] contextLen Length of context, 0 to 255 + * @param[in] preHashType Pre-hash algorithm (enum wc_HashType) + * @param[in] addRnd Explicit randomizer, may be NULL + * @param[in] addRndSz Length of addRnd in bytes, 0 if none + * @param[in] randomized Non-zero to have the server pick the randomizer + * @param[in] isMPrime Non-zero when in is a caller-built M' + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaSignDma(whClientContext* ctx, const byte* in, word32 in_len, + byte* out, word32* out_len, SlhDsaKey* key, + const byte* context, byte contextLen, + word32 preHashType, const byte* addRnd, + byte addRndSz, int randomized, int isMPrime); + +/** + * @brief Verify an SLH-DSA signature using DMA. + * + * @param[in] ctx Pointer to the client context + * @param[in] sig Signature to verify + * @param[in] sig_len Length of sig in bytes + * @param[in] msg Message, digest, or M' that was signed + * @param[in] msg_len Length of msg in bytes + * @param[out] out_res Set to 1 when the signature verifies, 0 otherwise + * @param[in] key Key handle, either server-resident or containing key material + * @param[in] context FIPS 205 context string, may be NULL + * @param[in] contextLen Length of context, 0 to 255 + * @param[in] preHashType Pre-hash algorithm (enum wc_HashType) + * @param[in] isMPrime Non-zero when msg is a caller-built M' + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaVerifyDma(whClientContext* ctx, const byte* sig, + word32 sig_len, const byte* msg, word32 msg_len, + int* out_res, SlhDsaKey* key, const byte* context, + byte contextLen, word32 preHashType, + int isMPrime); + +/** + * @brief Check an SLH-DSA private key on the server against a public key. + * + * The public key is only 2n bytes, so this forwards to the comm-buffer path. + * + * @param[in] ctx Pointer to the client context + * @param[in] key Key handle for the private key + * @param[in] pubKey Public key bytes, PK.seed || PK.root + * @param[in] pubKeySz Length of pubKey in bytes, 2n + * @return int Returns 0 when they match, WC_KEY_MISMATCH_E when they do not, + * or a negative error code on failure. + */ +int wh_Client_SlhDsaCheckPrivKeyDma(whClientContext* ctx, SlhDsaKey* key, + const byte* pubKey, word32 pubKeySz); + +#endif /* WOLFHSM_CFG_DMA */ + +#endif /* WOLFSSL_HAVE_SLHDSA */ + #ifdef WOLFSSL_HAVE_MLKEM /** diff --git a/wolfhsm/wh_common.h b/wolfhsm/wh_common.h index e3cb98f21..f06cf676a 100644 --- a/wolfhsm/wh_common.h +++ b/wolfhsm/wh_common.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * @@ -184,6 +184,7 @@ enum WH_KEY_ALGO_ENUM { WH_KEY_ALGO_MLKEM = 6, WH_KEY_ALGO_LMS = 7, WH_KEY_ALGO_XMSS = 8, + WH_KEY_ALGO_SLHDSA = 9, }; #endif /* !WOLFHSM_WH_COMMON_H_ */ diff --git a/wolfhsm/wh_crypto.h b/wolfhsm/wh_crypto.h index 608c742e5..5c4e3dd0e 100644 --- a/wolfhsm/wh_crypto.h +++ b/wolfhsm/wh_crypto.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * @@ -44,6 +44,7 @@ #include "wolfssl/wolfcrypt/ed25519.h" #include "wolfssl/wolfcrypt/wc_mldsa.h" #include "wolfssl/wolfcrypt/wc_mlkem.h" +#include "wolfssl/wolfcrypt/wc_slhdsa.h" #include "wolfhsm/wh_message_crypto.h" @@ -121,6 +122,19 @@ int wh_Crypto_MlDsaDeserializeKeyDer(const uint8_t* buffer, uint16_t size, wc_MlDsaKey* key); #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA +#define WH_CRYPTO_SLHDSA_MAX_CTX_LEN (255U) +/* RFC 9909 DER: 4n private and 2n public key plus ASN.1 overhead */ +#define WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE \ + (WC_SLHDSA_MAX_PRIV_LEN + WC_SLHDSA_MAX_PUB_LEN + 128U) +/* Store a SlhDsaKey to a byte sequence */ +int wh_Crypto_SlhDsaSerializeKeyDer(SlhDsaKey* key, uint16_t max_size, + uint8_t* buffer, uint16_t* out_size); +/* Restore a SlhDsaKey from a byte sequence */ +int wh_Crypto_SlhDsaDeserializeKeyDer(const uint8_t* buffer, uint16_t size, + SlhDsaKey* key); +#endif /* WOLFSSL_HAVE_SLHDSA */ + #ifdef WOLFSSL_HAVE_MLKEM /* Store a MlKemKey to a byte sequence */ int wh_Crypto_MlKemSerializeKey(MlKemKey* key, uint16_t max_size, diff --git a/wolfhsm/wh_message_crypto.h b/wolfhsm/wh_message_crypto.h index 8373c06cf..e920665e9 100644 --- a/wolfhsm/wh_message_crypto.h +++ b/wolfhsm/wh_message_crypto.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * @@ -1197,6 +1197,141 @@ int wh_MessageCrypto_TranslateMlDsaVerifyResponse( uint16_t magic, const whMessageCrypto_MlDsaVerifyResponse* src, whMessageCrypto_MlDsaVerifyResponse* dest); + +/* + * SLH-DSA + */ + +/* SLH-DSA Key Generation Request */ +typedef struct { + uint32_t sz; + uint32_t param; /* enum SlhDsaParam parameter set */ + uint32_t keyId; + uint32_t flags; + uint32_t access; + uint32_t seedSz; /* 0 for random keygen, else SK.seed||SK.prf||PK.seed */ + uint8_t label[WH_NVM_LABEL_LEN]; + /* Data follows: + * uint8_t seed[seedSz]; + */ +} whMessageCrypto_SlhDsaKeyGenRequest; + +/* SLH-DSA Key Generation Response */ +typedef struct { + uint32_t keyId; + uint32_t len; + /* Data follows: + * uint8_t out[len]; + */ +} whMessageCrypto_SlhDsaKeyGenResponse; + +int wh_MessageCrypto_TranslateSlhDsaKeyGenRequest( + uint16_t magic, const whMessageCrypto_SlhDsaKeyGenRequest* src, + whMessageCrypto_SlhDsaKeyGenRequest* dest); + +int wh_MessageCrypto_TranslateSlhDsaKeyGenResponse( + uint16_t magic, const whMessageCrypto_SlhDsaKeyGenResponse* src, + whMessageCrypto_SlhDsaKeyGenResponse* dest); + +/* SLH-DSA Sign Request */ +typedef struct { + uint32_t options; +#define WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_EVICT (1 << 0) +/* Input is a caller-built M', so context and pre-hash do not apply */ +#define WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_MPRIME (1 << 1) +/* Caller asked for a hedged signature; the server supplies the randomizer */ +#define WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_RANDOMIZED (1 << 2) + uint32_t param; + uint32_t keyId; + uint32_t sz; + uint32_t contextSz; /* FIPS 205 context length (0-255) */ + uint32_t preHashType; /* enum wc_HashType, 0 for pure SLH-DSA */ + uint32_t addRndSz; /* Caller-supplied randomizer length, 0 if none */ + uint8_t WH_PAD[4]; + /* Data follows: + * uint8_t in[sz]; + * uint8_t context[contextSz]; + * uint8_t addRnd[addRndSz]; + */ +} whMessageCrypto_SlhDsaSignRequest; + +/* SLH-DSA Sign Response */ +typedef struct { + uint32_t sz; + uint8_t WH_PAD[4]; + /* Data follows: + * uint8_t out[sz]; + */ +} whMessageCrypto_SlhDsaSignResponse; + +int wh_MessageCrypto_TranslateSlhDsaSignRequest( + uint16_t magic, const whMessageCrypto_SlhDsaSignRequest* src, + whMessageCrypto_SlhDsaSignRequest* dest); + +int wh_MessageCrypto_TranslateSlhDsaSignResponse( + uint16_t magic, const whMessageCrypto_SlhDsaSignResponse* src, + whMessageCrypto_SlhDsaSignResponse* dest); + +/* SLH-DSA Verify Request */ +typedef struct { + uint32_t options; +#define WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_EVICT (1 << 0) +/* Message is a caller-built M', so context and pre-hash do not apply */ +#define WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_MPRIME (1 << 2) + uint32_t param; + uint32_t keyId; + uint32_t sigSz; + uint32_t hashSz; + uint32_t contextSz; /* FIPS 205 context length (0-255) */ + uint32_t preHashType; /* enum wc_HashType, 0 for pure SLH-DSA */ + uint8_t WH_PAD[4]; + /* Data follows: + * uint8_t sig[sigSz]; + * uint8_t hash[hashSz]; + * uint8_t context[contextSz]; + */ +} whMessageCrypto_SlhDsaVerifyRequest; + +/* SLH-DSA Verify Response */ +typedef struct { + uint32_t res; + uint8_t WH_PAD[4]; +} whMessageCrypto_SlhDsaVerifyResponse; + +int wh_MessageCrypto_TranslateSlhDsaVerifyRequest( + uint16_t magic, const whMessageCrypto_SlhDsaVerifyRequest* src, + whMessageCrypto_SlhDsaVerifyRequest* dest); + +int wh_MessageCrypto_TranslateSlhDsaVerifyResponse( + uint16_t magic, const whMessageCrypto_SlhDsaVerifyResponse* src, + whMessageCrypto_SlhDsaVerifyResponse* dest); + +/* SLH-DSA Check Private Key Request */ +typedef struct { + uint32_t options; +#define WH_MESSAGE_CRYPTO_SLHDSA_CHECKPRIVKEY_OPTIONS_EVICT (1 << 0) + uint32_t param; + uint32_t keyId; + uint32_t pubSz; /* PK.seed||PK.root, 2n bytes */ + /* Data follows: + * uint8_t pub[pubSz]; + */ +} whMessageCrypto_SlhDsaCheckPrivKeyRequest; + +/* SLH-DSA Check Private Key Response */ +typedef struct { + int32_t res; + uint8_t WH_PAD[4]; +} whMessageCrypto_SlhDsaCheckPrivKeyResponse; + +int wh_MessageCrypto_TranslateSlhDsaCheckPrivKeyRequest( + uint16_t magic, const whMessageCrypto_SlhDsaCheckPrivKeyRequest* src, + whMessageCrypto_SlhDsaCheckPrivKeyRequest* dest); + +int wh_MessageCrypto_TranslateSlhDsaCheckPrivKeyResponse( + uint16_t magic, const whMessageCrypto_SlhDsaCheckPrivKeyResponse* src, + whMessageCrypto_SlhDsaCheckPrivKeyResponse* dest); + /* * ML-KEM */ @@ -1695,6 +1830,96 @@ int wh_MessageCrypto_TranslateMlDsaVerifyDmaResponse( uint16_t magic, const whMessageCrypto_MlDsaVerifyDmaResponse* src, whMessageCrypto_MlDsaVerifyDmaResponse* dest); +/* SLH-DSA DMA Key Generation Request */ +typedef struct { + whMessageCrypto_DmaBuffer key; + whMessageCrypto_DmaBuffer seed; /* sz 0 for random keygen */ + uint32_t param; + uint32_t flags; + uint32_t keyId; + uint32_t access; /* Key access permissions */ + uint32_t labelSize; + uint8_t label[WH_NVM_LABEL_LEN]; + uint8_t WH_PAD[4]; +} whMessageCrypto_SlhDsaKeyGenDmaRequest; + +/* SLH-DSA DMA Key Generation Response */ +typedef struct { + whMessageCrypto_DmaAddrStatus dmaAddrStatus; + uint32_t keyId; /* Assigned key ID */ + uint32_t keySize; /* Actual size of generated key */ +} whMessageCrypto_SlhDsaKeyGenDmaResponse; + +/* SLH-DSA DMA Sign Request */ +typedef struct { + whMessageCrypto_DmaBuffer msg; /* Message buffer */ + whMessageCrypto_DmaBuffer sig; /* Signature buffer */ + uint32_t options; /* Same options as non-DMA version */ + uint32_t param; /* enum SlhDsaParam parameter set */ + uint32_t keyId; /* Key ID to use for signing */ + uint32_t contextSz; /* FIPS 205 context length (0-255) */ + uint32_t preHashType; /* enum wc_HashType */ + uint32_t addRndSz; /* Randomizer length, 0 if none */ + /* Data follows: + * uint8_t context[contextSz]; + * uint8_t addRnd[addRndSz]; + */ +} whMessageCrypto_SlhDsaSignDmaRequest; + +/* SLH-DSA DMA Sign Response */ +typedef struct { + whMessageCrypto_DmaAddrStatus dmaAddrStatus; + uint32_t sigLen; /* Actual signature length */ + uint8_t WH_PAD[4]; /* Pad to 8-byte alignment */ +} whMessageCrypto_SlhDsaSignDmaResponse; + +/* SLH-DSA DMA Verify Request */ +typedef struct { + whMessageCrypto_DmaBuffer sig; /* Signature buffer */ + whMessageCrypto_DmaBuffer msg; /* Message buffer */ + uint32_t options; /* Same options as non-DMA version */ + uint32_t param; /* enum SlhDsaParam parameter set */ + uint32_t keyId; /* Key ID to use for verification */ + uint32_t contextSz; /* FIPS 205 context length (0-255) */ + uint32_t preHashType; /* enum wc_HashType */ + uint8_t WH_PAD[4]; /* Pad to 8-byte alignment */ + /* Data follows: + * uint8_t context[contextSz]; + */ +} whMessageCrypto_SlhDsaVerifyDmaRequest; + +/* SLH-DSA DMA Verify Response */ +typedef struct { + whMessageCrypto_DmaAddrStatus dmaAddrStatus; + int32_t verifyResult; /* Result of verification */ + uint8_t WH_PAD[4]; /* Pad to 8-byte alignment */ +} whMessageCrypto_SlhDsaVerifyDmaResponse; + +/* SLH-DSA DMA translation functions */ +int wh_MessageCrypto_TranslateSlhDsaKeyGenDmaRequest( + uint16_t magic, const whMessageCrypto_SlhDsaKeyGenDmaRequest* src, + whMessageCrypto_SlhDsaKeyGenDmaRequest* dest); + +int wh_MessageCrypto_TranslateSlhDsaKeyGenDmaResponse( + uint16_t magic, const whMessageCrypto_SlhDsaKeyGenDmaResponse* src, + whMessageCrypto_SlhDsaKeyGenDmaResponse* dest); + +int wh_MessageCrypto_TranslateSlhDsaSignDmaRequest( + uint16_t magic, const whMessageCrypto_SlhDsaSignDmaRequest* src, + whMessageCrypto_SlhDsaSignDmaRequest* dest); + +int wh_MessageCrypto_TranslateSlhDsaSignDmaResponse( + uint16_t magic, const whMessageCrypto_SlhDsaSignDmaResponse* src, + whMessageCrypto_SlhDsaSignDmaResponse* dest); + +int wh_MessageCrypto_TranslateSlhDsaVerifyDmaRequest( + uint16_t magic, const whMessageCrypto_SlhDsaVerifyDmaRequest* src, + whMessageCrypto_SlhDsaVerifyDmaRequest* dest); + +int wh_MessageCrypto_TranslateSlhDsaVerifyDmaResponse( + uint16_t magic, const whMessageCrypto_SlhDsaVerifyDmaResponse* src, + whMessageCrypto_SlhDsaVerifyDmaResponse* dest); + /* ML-KEM DMA Key Generation Request */ typedef struct { whMessageCrypto_DmaBuffer key; diff --git a/wolfhsm/wh_server_crypto.h b/wolfhsm/wh_server_crypto.h index 56e2a9b33..416d2c772 100644 --- a/wolfhsm/wh_server_crypto.h +++ b/wolfhsm/wh_server_crypto.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * @@ -38,6 +38,7 @@ #include "wolfssl/wolfcrypt/ecc.h" #include "wolfssl/wolfcrypt/ed25519.h" #include "wolfssl/wolfcrypt/wc_mlkem.h" +#include "wolfssl/wolfcrypt/wc_slhdsa.h" #include "wolfssl/wolfcrypt/aes.h" #include "wolfssl/wolfcrypt/sha256.h" #include "wolfssl/wolfcrypt/cmac.h" @@ -106,6 +107,16 @@ int wh_Server_MlDsaKeyCacheExport(whServerContext* ctx, whKeyId keyId, wc_MlDsaKey* key); #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA +/* Store a SlhDsaKey into a server key cache with optional metadata */ +int wh_Server_SlhDsaKeyCacheImport(whServerContext* ctx, SlhDsaKey* key, + whKeyId keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label); +/* Restore a SlhDsaKey from a server key cache */ +int wh_Server_SlhDsaKeyCacheExport(whServerContext* ctx, whKeyId keyId, + SlhDsaKey* key); +#endif /* WOLFSSL_HAVE_SLHDSA */ + #ifdef WOLFSSL_HAVE_MLKEM /* Store a MlKemKey into a server key cache with optional metadata */ int wh_Server_MlKemKeyCacheImport(whServerContext* ctx, MlKemKey* key, diff --git a/wolfhsm/wh_settings.h b/wolfhsm/wh_settings.h index 7e4d16b7e..0317cbf89 100644 --- a/wolfhsm/wh_settings.h +++ b/wolfhsm/wh_settings.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * @@ -194,6 +194,15 @@ * Requires WOLFHSM_CFG_DMA_REQUIRE_ALIGNED_PADDED_OUTPUT. * Default: Not defined * + * SLH-DSA signatures range from 7856 bytes (128s) to 49856 bytes (256f), so + * the non-DMA sign and verify paths only work for a parameter set whose + * signature fits WOLFHSM_CFG_COMM_DATA_LEN alongside the message headers. + * With the default buffer no parameter set fits; at 8192 only 128s does, and + * a maximum-length context with a 64-byte pre-hash still overflows it. The + * server reports WH_ERROR_BUFFER_SIZE rather than truncating. Use the DMA + * sign and verify calls for the other parameter sets. If the verify request + * is too large for the comm buffer, it can fall back to software. + * * WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS - Maximum number of trusted root NVM IDs * accepted in a single wh_Server_CertVerifyMultiRoot request. Bounded so the * non-DMA wire request fits within WOLFHSM_CFG_COMM_DATA_LEN alongside the