[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20251017144311.817771-11-dhowells@redhat.com>
Date: Fri, 17 Oct 2025 15:42:54 +0100
From: David Howells <dhowells@...hat.com>
To: Eric Biggers <ebiggers@...nel.org>
Cc: David Howells <dhowells@...hat.com>,
"Jason A . Donenfeld" <Jason@...c4.com>,
Ard Biesheuvel <ardb@...nel.org>,
Herbert Xu <herbert@...dor.apana.org.au>,
Stephan Mueller <smueller@...onox.de>,
Lukas Wunner <lukas@...ner.de>,
Ignat Korchagin <ignat@...udflare.com>,
Luis Chamberlain <mcgrof@...nel.org>,
Petr Pavlu <petr.pavlu@...e.com>,
Daniel Gomez <da.gomez@...nel.org>,
Sami Tolvanen <samitolvanen@...gle.com>,
linux-crypto@...r.kernel.org,
keyrings@...r.kernel.org,
linux-modules@...r.kernel.org,
linux-kernel@...r.kernel.org
Subject: [PATCH v6 10/17] crypto: Add ML-DSA/Dilithium support
Port Stephan Mueller's Leancrypto implementation of ML-DSA/Dilithium to the
kernel.
[!] NOTE: This isn't entirely cleaned up yet! This includes converting the
documentation comments and removing more compatibility macros. I wanted to
concentrate on getting it actually working first.
Apologies to Stephan, but I've stripped out a bunch of macros to do return
checking and suchlike and removed a bunch of "lc_" prefixes from the code.
The code retains the following features, some of which probably need to
be separated out or dropped entirely:
- Signature verification
- Signing
- Prehash support
- External Mu support
Composite signature support is mostly removed and none of the arch-specific
code from Leancrypto has been included for the moment, so this is pure C.
The interface to this code is through the crypto_sig API as the PKCS#7 code
wants to use that rather than calling it directly. As such, I've placed it
in crypto/ rather than lib/crypto/.
Signed-off-by: David Howells <dhowells@...hat.com>
cc: Stephan Mueller <smueller@...onox.de>
cc: Eric Biggers <ebiggers@...nel.org>
cc: Jason A. Donenfeld <Jason@...c4.com>
cc: Ard Biesheuvel <ardb@...nel.org>
cc: Herbert Xu <herbert@...dor.apana.org.au>
cc: linux-crypto@...r.kernel.org
---
crypto/Kconfig | 1 +
crypto/Makefile | 1 +
crypto/ml_dsa/Kconfig | 17 +
crypto/ml_dsa/Makefile | 18 +
crypto/ml_dsa/dilithium.h | 750 ++++++++++++++++++
crypto/ml_dsa/dilithium_44.c | 33 +
crypto/ml_dsa/dilithium_44.h | 377 +++++++++
crypto/ml_dsa/dilithium_65.c | 33 +
crypto/ml_dsa/dilithium_65.h | 377 +++++++++
crypto/ml_dsa/dilithium_87.c | 33 +
crypto/ml_dsa/dilithium_87.h | 377 +++++++++
crypto/ml_dsa/dilithium_api.c | 729 +++++++++++++++++
crypto/ml_dsa/dilithium_debug.h | 80 ++
crypto/ml_dsa/dilithium_ntt.c | 89 +++
crypto/ml_dsa/dilithium_ntt.h | 35 +
crypto/ml_dsa/dilithium_pack.h | 210 +++++
crypto/ml_dsa/dilithium_poly.c | 586 ++++++++++++++
crypto/ml_dsa/dilithium_poly.h | 190 +++++
crypto/ml_dsa/dilithium_poly_c.h | 149 ++++
crypto/ml_dsa/dilithium_poly_common.h | 35 +
crypto/ml_dsa/dilithium_polyvec.h | 343 ++++++++
crypto/ml_dsa/dilithium_polyvec_c.h | 94 +++
crypto/ml_dsa/dilithium_reduce.h | 108 +++
crypto/ml_dsa/dilithium_rounding.c | 128 +++
crypto/ml_dsa/dilithium_rounding.h | 45 ++
crypto/ml_dsa/dilithium_service_helpers.h | 99 +++
crypto/ml_dsa/dilithium_sig.c | 404 ++++++++++
crypto/ml_dsa/dilithium_signature_c.c | 174 ++++
crypto/ml_dsa/dilithium_signature_c.h | 53 ++
crypto/ml_dsa/dilithium_signature_helper.c | 110 +++
crypto/ml_dsa/dilithium_signature_impl.h | 838 ++++++++++++++++++++
crypto/ml_dsa/dilithium_type.h | 259 ++++++
crypto/ml_dsa/dilithium_zetas.c | 67 ++
crypto/ml_dsa/fips_mode.h | 45 ++
crypto/ml_dsa/signature_domain_separation.c | 213 +++++
crypto/ml_dsa/signature_domain_separation.h | 33 +
crypto/ml_dsa/small_stack_support.h | 40 +
37 files changed, 7173 insertions(+)
create mode 100644 crypto/ml_dsa/Kconfig
create mode 100644 crypto/ml_dsa/Makefile
create mode 100644 crypto/ml_dsa/dilithium.h
create mode 100644 crypto/ml_dsa/dilithium_44.c
create mode 100644 crypto/ml_dsa/dilithium_44.h
create mode 100644 crypto/ml_dsa/dilithium_65.c
create mode 100644 crypto/ml_dsa/dilithium_65.h
create mode 100644 crypto/ml_dsa/dilithium_87.c
create mode 100644 crypto/ml_dsa/dilithium_87.h
create mode 100644 crypto/ml_dsa/dilithium_api.c
create mode 100644 crypto/ml_dsa/dilithium_debug.h
create mode 100644 crypto/ml_dsa/dilithium_ntt.c
create mode 100644 crypto/ml_dsa/dilithium_ntt.h
create mode 100644 crypto/ml_dsa/dilithium_pack.h
create mode 100644 crypto/ml_dsa/dilithium_poly.c
create mode 100644 crypto/ml_dsa/dilithium_poly.h
create mode 100644 crypto/ml_dsa/dilithium_poly_c.h
create mode 100644 crypto/ml_dsa/dilithium_poly_common.h
create mode 100644 crypto/ml_dsa/dilithium_polyvec.h
create mode 100644 crypto/ml_dsa/dilithium_polyvec_c.h
create mode 100644 crypto/ml_dsa/dilithium_reduce.h
create mode 100644 crypto/ml_dsa/dilithium_rounding.c
create mode 100644 crypto/ml_dsa/dilithium_rounding.h
create mode 100644 crypto/ml_dsa/dilithium_service_helpers.h
create mode 100644 crypto/ml_dsa/dilithium_sig.c
create mode 100644 crypto/ml_dsa/dilithium_signature_c.c
create mode 100644 crypto/ml_dsa/dilithium_signature_c.h
create mode 100644 crypto/ml_dsa/dilithium_signature_helper.c
create mode 100644 crypto/ml_dsa/dilithium_signature_impl.h
create mode 100644 crypto/ml_dsa/dilithium_type.h
create mode 100644 crypto/ml_dsa/dilithium_zetas.c
create mode 100644 crypto/ml_dsa/fips_mode.h
create mode 100644 crypto/ml_dsa/signature_domain_separation.c
create mode 100644 crypto/ml_dsa/signature_domain_separation.h
create mode 100644 crypto/ml_dsa/small_stack_support.h
diff --git a/crypto/Kconfig b/crypto/Kconfig
index a04595f9d0ca..b027460d54b7 100644
--- a/crypto/Kconfig
+++ b/crypto/Kconfig
@@ -1451,5 +1451,6 @@ source "drivers/crypto/Kconfig"
source "crypto/asymmetric_keys/Kconfig"
source "certs/Kconfig"
source "crypto/krb5/Kconfig"
+source "crypto/ml_dsa/Kconfig"
endif # if CRYPTO
diff --git a/crypto/Makefile b/crypto/Makefile
index e430e6e99b6a..ecd1ef79c28c 100644
--- a/crypto/Makefile
+++ b/crypto/Makefile
@@ -210,3 +210,4 @@ obj-$(CONFIG_CRYPTO_SIMD) += crypto_simd.o
obj-$(CONFIG_CRYPTO_KDF800108_CTR) += kdf_sp800108.o
obj-$(CONFIG_CRYPTO_KRB5) += krb5/
+obj-$(CONFIG_CRYPTO_ML_DSA) += ml_dsa/
diff --git a/crypto/ml_dsa/Kconfig b/crypto/ml_dsa/Kconfig
new file mode 100644
index 000000000000..cc0567c5b990
--- /dev/null
+++ b/crypto/ml_dsa/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0
+menuconfig CRYPTO_ML_DSA
+ tristate "ML-DSA/Dilithium algorithm"
+ select CRYPTO_LIB_SHA3
+
+if CRYPTO_ML_DSA
+
+config CRYPTO_DILITHIUM_44
+ bool "Enable Dilithium-44 support"
+
+config CRYPTO_DILITHIUM_65
+ bool "Enable Dilithium-65 support"
+
+config CRYPTO_DILITHIUM_87
+ bool "Enable Dilithium-87 support"
+
+endif
diff --git a/crypto/ml_dsa/Makefile b/crypto/ml_dsa/Makefile
new file mode 100644
index 000000000000..d420a8ba6033
--- /dev/null
+++ b/crypto/ml_dsa/Makefile
@@ -0,0 +1,18 @@
+################################################################################
+# Signature implementation: Dilithium
+
+################################################################################
+# C Implementation
+################################################################################
+ml_dsa-y += \
+ signature_domain_separation.o \
+ dilithium_api.o \
+ dilithium_zetas.o \
+ dilithium_sig.o \
+ dilithium_signature_helper.o
+
+ml_dsa-$(CONFIG_CRYPTO_DILITHIUM_87) += dilithium_87.o
+ml_dsa-$(CONFIG_CRYPTO_DILITHIUM_65) += dilithium_65.o
+ml_dsa-$(CONFIG_CRYPTO_DILITHIUM_44) += dilithium_44.o
+
+obj-$(CONFIG_CRYPTO_ML_DSA) += ml_dsa.o
diff --git a/crypto/ml_dsa/dilithium.h b/crypto/ml_dsa/dilithium.h
new file mode 100644
index 000000000000..6d4982164201
--- /dev/null
+++ b/crypto/ml_dsa/dilithium.h
@@ -0,0 +1,750 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#ifndef DILITHIUM_H
+#define DILITHIUM_H
+
+#undef pr_fmt
+#define pr_fmt(fmt) "ML-DSA: " fmt
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <crypto/sha2.h>
+#include <crypto/sha3.h>
+#include <crypto/hash.h>
+#include <crypto/rng.h>
+
+#define DILITHIUM_SEEDBYTES 32
+#define DILITHIUM_CRHBYTES 64
+#define DILITHIUM_TRBYTES 64
+#define DILITHIUM_RNDBYTES 32
+#define DILITHIUM_N 256
+#define DILITHIUM_Q 8380417
+#define DILITHIUM_D 13
+#define DILITHIUM_ROOT_OF_UNITY 1753
+
+extern const int32_t dilithium_zetas[DILITHIUM_N];
+#define lc_seeded_rng crypto_default_rng
+
+struct dilithium_ctx {
+ /**
+ * @brief Hash context used internally to the library - it should not
+ * be touched by the user
+ */
+ struct shake256_ctx dilithium_hash_ctx;
+
+ /**
+ * @brief When using HashML-DSA, set the hash reference used for the
+ * hash operation. Allowed values are lc_sha256, lc_sha512, lc_sha3_256,
+ * lc_sha3_384, lc_sha3_512, lc_shake128 and lc_shake256. Note, the
+ * actual message digest operation can be performed external to
+ * leancrypto. This parameter only shall indicate the used hash
+ * operation.
+ *
+ * \note Use \p dilithium_ctx_hash or
+ * \p dilithium_ed25519_ctx_hash to set this value.
+ */
+ struct crypto_shash *dilithium_prehash_type;
+
+ /**
+ * @brief length of the user context (allowed range between 0 and 255
+ * bytes)
+ *
+ * \note Use \p dilithium_ctx_userctx or
+ * \p dilithium_ed25519_ctx_userctx to set this value.
+ */
+ size_t userctxlen;
+
+ /**
+ * @brief buffer with a caller-specified context string
+ *
+ * \note Use \p dilithium_ctx_userctx or
+ * \p dilithium_ed25519_ctx_userctx to set this value.
+ */
+ const uint8_t *userctx;
+
+ /**
+ * @brief Pointer to the AHat buffer. This can be provided by the caller
+ * or it must be NULL otherwise.
+ *
+ * \note Use \p DILITHIUM_CTX_ON_STACK_AHAT to provide memory for
+ * storing AHat in the caller context and thus make the signature
+ * operation much faster starting with the 2nd use of the key (pair).
+ */
+ void *ahat;
+ unsigned short ahat_size;
+
+ /**
+ * @brief Pointer to the external mu.
+ *
+ * If set, the signature operation will use the provided mu instead of
+ * the message. In this case, the message pointer to the signature
+ * generation or verification can be NULL.
+ */
+ const uint8_t *external_mu;
+ size_t external_mu_len;
+
+ /**
+ * @brief Pointer to the randomizer
+ *
+ * This is used for the Composite signature: For the discussion of the
+ * randomizer, see https://lamps-wg.github.io/draft-composite-sigs/draft-ietf-lamps-pq-composite-sigs.html
+ */
+ const uint8_t *randomizer;
+ size_t randomizerlen;
+
+ /**
+ * @brief NIST category required for composite signatures
+ *
+ * The domain separation logic depends on the selection of the right
+ * OID for the "Domain" data.
+ */
+ unsigned int nist_category;
+
+ /**
+ * @brief When set to true, only the ML-DSA.Sign_internal or
+ * ML-DSA.Verify_internal are performed (see FIPS 204 chapter 6).
+ * Otherwise the ML-DSA.Sign / ML-DSA.Verify (see FIPS chapter 5) is
+ * applied.
+ *
+ * \note Use \p dilithium_ctx_internal or
+ * \p dilithium_ed25519_ctx_internal to set this value.
+ *
+ * \warning Only set this value to true if you exactly know what you are
+ * doing!.
+ */
+ bool ml_dsa_internal:1;
+
+ /**
+ * @brief Was aHat already filled? This is used and set internally.
+ */
+ bool ahat_expanded:1;
+} __aligned(CRYPTO_MINALIGN);
+
+static inline void dilithium_hash_init(struct dilithium_ctx *ctx)
+{
+ shake256_init(&ctx->dilithium_hash_ctx);
+}
+
+static inline bool dilithium_hash_check_blocksize(const struct dilithium_ctx *ctx, size_t bsize)
+{
+ return bsize == SHAKE256_BLOCK_SIZE;
+ //return crypto_shash_blocksize(hash_ctx->tfm) == bsize;
+}
+
+static inline bool dilithium_hash_check_digestsize(const struct dilithium_ctx *ctx, size_t dsize)
+{
+ return true;
+ //return crypto_shash_digestsize(hash_ctx->tfm) == dsize;
+}
+
+static inline void dilithium_hash_clear(struct dilithium_ctx *ctx)
+{
+ shake256_clear(&ctx->dilithium_hash_ctx);
+}
+
+static inline void dilithium_hash_update(struct dilithium_ctx *ctx,
+ const u8 *in, size_t in_size)
+{
+ shake256_update(&ctx->dilithium_hash_ctx, in, in_size);
+}
+
+static inline void dilithium_hash_finup(struct dilithium_ctx *ctx,
+ const u8 *in, size_t in_size,
+ u8 *out, size_t out_size)
+{
+ shake256_update(&ctx->dilithium_hash_ctx, in, in_size);
+ shake256_squeeze(&ctx->dilithium_hash_ctx, out, out_size);
+ shake256_clear(&ctx->dilithium_hash_ctx);
+}
+
+static inline void dilithium_hash_final(struct dilithium_ctx *ctx, u8 *out, size_t out_size)
+{
+ shake256_squeeze(&ctx->dilithium_hash_ctx, out, out_size);
+ shake256_clear(&ctx->dilithium_hash_ctx);
+}
+
+#include "dilithium_87.h"
+#include "dilithium_65.h"
+#include "dilithium_44.h"
+
+enum dilithium_type {
+ DILITHIUM_UNKNOWN, /** Unknown key type */
+ DILITHIUM_87, /** Dilithium 87 */
+ DILITHIUM_65, /** Dilithium 65 */
+ DILITHIUM_44, /** Dilithium 44 */
+};
+
+/** @defgroup Dilithium ML-DSA / CRYSTALS-Dilithium Signature Mechanism
+ *
+ * \note Although the API uses the term "dilithium", the implementation complies
+ * with FIPS 204. Thus the terms Dilithium and ML-DSA are used interchangeably.
+ *
+ * Dilithium API concept
+ *
+ * The Dilithium API is accessible via the following header files with the
+ * mentioned purpose.
+ *
+ * * dilithium.h: This API is the generic API allowing the caller to select
+ * which Dilithium type (Dilithium 87, 65 or 44) are to be used. The selection
+ * is made either with the flag specified during key generation or by matching
+ * the size of the imported data with the different dilithium_*_load API
+ * calls. All remaining APIs take the information about the Dilithium type
+ * from the provided input data.
+ *
+ * This header file only provides inline functions which selectively call
+ * the API provided with the header files below.
+ *
+ * * dilithium_87.h: Direct access to Dilithium 87.
+ *
+ * * dilithium_65.h: Direct access to Dilithium 65.
+ *
+ * * dilithium_44.h: Direct access to Dilithium 44.
+ *
+ * To support the stream mode of the Dilithium signature operation, a
+ * context structure is required. This context structure can be allocated either
+ * on the stack or heap with \p DILITHIUM_CTX_ON_STACK or
+ * \p dilithium_ctx_alloc. The context should be zeroized
+ * and freed (only for heap) with \p dilithium_ctx_zero or
+ * \p dilithium_ctx_zero_free.
+ */
+
+/**
+ * @brief Dilithium secret key
+ */
+struct dilithium_sk {
+ enum dilithium_type dilithium_type;
+ union {
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ struct dilithium_87_sk sk_87;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ struct dilithium_65_sk sk_65;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ struct dilithium_44_sk sk_44;
+#endif
+ } key;
+};
+
+/**
+ * @brief Dilithium public key
+ */
+struct dilithium_pk {
+ enum dilithium_type dilithium_type;
+ union {
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ struct dilithium_87_pk pk_87;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ struct dilithium_65_pk pk_65;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ struct dilithium_44_pk pk_44;
+#endif
+ } key;
+};
+
+/**
+ * @brief Dilithium signature
+ */
+struct dilithium_sig {
+ enum dilithium_type dilithium_type;
+ union {
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ struct dilithium_87_sig sig_87;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ struct dilithium_65_sig sig_65;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ struct dilithium_44_sig sig_44;
+#endif
+ } sig;
+};
+
+/**
+ * @ingroup Dilithium
+ * @brief Allocates Dilithium context on heap
+ *
+ * @param [out] ctx Dilithium context pointer
+ *
+ * @return 0 (success) or < 0 on error
+ */
+struct dilithium_ctx *dilithium_ctx_alloc(void);
+
+/**
+ * @ingroup Dilithium
+ * @brief Allocates Dilithium context on heap with support to keep the internal
+ * representation of the key.
+ *
+ * \note See \p DILITHIUM_CTX_ON_STACK_AHAT for details.
+ *
+ * @param [out] ctx Dilithium context pointer
+ *
+ * @return 0 (success) or < 0 on error
+ */
+struct dilithium_ctx *dilithium_ctx_alloc_ahat(enum dilithium_type type);
+
+/**
+ * @ingroup Dilithium
+ * @brief Zeroizes and frees Dilithium context on heap
+ *
+ * @param [out] ctx Dilithium context pointer
+ */
+void dilithium_ctx_zero_free(struct dilithium_ctx *ctx);
+
+/**
+ * @ingroup Dilithium
+ * @brief Zeroizes Dilithium context either on heap or on stack
+ *
+ * @param [out] ctx Dilithium context pointer
+ */
+void dilithium_ctx_zero(struct dilithium_ctx *ctx);
+
+/**
+ * @ingroup Dilithium
+ * @brief Mark the Dilithium context to execute ML-DSA.Sign_internal /
+ * ML-DSA.Verify_internal.
+ *
+ * @param [in] ctx Dilithium context
+ */
+void dilithium_ctx_internal(struct dilithium_ctx *ctx);
+
+/**
+ * @ingroup Dilithium
+ * @brief Set the hash type that was used for pre-hashing the message. The
+ * message digest is used with the HashML-DSA. The message digest
+ * is to be provided via the message pointer in the sign/verify APIs.
+ *
+ * @param [in] ctx Dilithium context
+ * @param [in] hash Hash context referencing the used hash for pre-hashing the
+ * message
+ */
+void dilithium_ctx_hash(struct dilithium_ctx *ctx,
+ struct crypto_shash *hash);
+
+/**
+ * @ingroup Dilithium
+ * @brief Specify the optional user context string to be applied with the
+ * Dilithium signature operation.
+ *
+ * @param [in] ctx Dilithium context
+ * @param [in] userctx User context string
+ * @param [in] userctxlen Size of the user context string
+ */
+void dilithium_ctx_userctx(struct dilithium_ctx *ctx,
+ const uint8_t *userctx, size_t userctxlen);
+
+/**
+ * @ingroup Dilithium
+ * @brief Specify the optional external mu value.
+ *
+ * \note If the external mu is specified, the signature generation /
+ * verification APIs do not require a message. In this case, the message buffer
+ * can be set to NULL.
+ *
+ * \note If both a message and an external mu are provided, the external mu
+ * takes precedence.
+ *
+ * @param [in] ctx Dilithium context
+ * @param [in] external_mu User context string
+ * @param [in] external_mu_len Size of the user context string
+ */
+void dilithium_ctx_external_mu(struct dilithium_ctx *ctx,
+ const uint8_t *external_mu,
+ size_t external_mu_len);
+
+/**
+ * @ingroup Dilithium
+ * @brief Invalidate the expanded key that potentially is stored in the context.
+ *
+ * This call can be executed on a context irrespective it was allocated with
+ * space for the expanded representation or not. Thus, the caller does not need
+ * to track whether the context supports the expanded key.
+ *
+ * @param [in] ctx Dilithium context
+ */
+void dilithium_ctx_drop_ahat(struct dilithium_ctx *ctx);
+
+/**
+ * @ingroup Dilithium
+ * @brief Obtain Dilithium type from secret key
+ *
+ * @param [in] sk Secret key from which the type is to be obtained
+ *
+ * @return key type
+ */
+enum dilithium_type dilithium_sk_type(const struct dilithium_sk *sk);
+
+/**
+ * @ingroup Dilithium
+ * @brief Obtain Dilithium type from public key
+ *
+ * @param [in] pk Public key from which the type is to be obtained
+ *
+ * @return key type
+ */
+enum dilithium_type dilithium_pk_type(const struct dilithium_pk *pk);
+
+/**
+ * @ingroup Dilithium
+ * @brief Obtain Dilithium type from signature
+ *
+ * @param [in] sig Signature from which the type is to be obtained
+ *
+ * @return key type
+ */
+enum dilithium_type
+dilithium_sig_type(const struct dilithium_sig *sig);
+
+/**
+ * @ingroup Dilithium
+ * @brief Return the size of the Dilithium secret key.
+ *
+ * @param [in] dilithium_type Dilithium type for which the size is requested
+ *
+ * @return requested size
+ */
+__pure unsigned int
+dilithium_sk_size(enum dilithium_type dilithium_type);
+
+/**
+ * @ingroup Dilithium
+ * @brief Return the size of the Dilithium public key.
+ *
+ * @param [in] dilithium_type Dilithium type for which the size is requested
+ *
+ * @return requested size
+ */
+__pure unsigned int
+dilithium_pk_size(enum dilithium_type dilithium_type);
+
+/**
+ * @ingroup Dilithium
+ * @brief Return the size of the Dilithium signature.
+ *
+ * @param [in] dilithium_type Dilithium type for which the size is requested
+ *
+ * @return requested size
+ */
+unsigned int __pure
+dilithium_sig_size(enum dilithium_type dilithium_type);
+
+/**
+ * @ingroup Dilithium
+ * @brief Load a Dilithium secret key provided with a buffer into the leancrypto
+ * data structure.
+ *
+ * @param [out] sk Secret key to be filled (the caller must have it allocated)
+ * @param [in] src_key Buffer that holds the key to be imported
+ * @param [in] src_key_len Buffer length that holds the key to be imported
+ *
+ * @return 0 on success or < 0 on error
+ */
+int dilithium_sk_load(struct dilithium_sk *sk, const uint8_t *src_key,
+ size_t src_key_len);
+
+/**
+ * @ingroup Dilithium
+ * @brief Load a Dilithium public key provided with a buffer into the leancrypto
+ * data structure.
+ *
+ * @param [out] pk Secret key to be filled (the caller must have it allocated)
+ * @param [in] src_key Buffer that holds the key to be imported
+ * @param [in] src_key_len Buffer length that holds the key to be imported
+ *
+ * @return 0 on success or < 0 on error
+ */
+int dilithium_pk_load(struct dilithium_pk *pk, const uint8_t *src_key,
+ size_t src_key_len);
+
+/**
+ * @ingroup Dilithium
+ * @brief Load a Dilithium signature provided with a buffer into the leancrypto
+ * data structure.
+ *
+ * @param [out] sig Secret key to be filled (the caller must have it allocated)
+ * @param [in] src_sig Buffer that holds the signature to be imported
+ * @param [in] src_sig_len Buffer length that holds the signature to be imported
+ *
+ * @return 0 on success or < 0 on error
+ */
+int dilithium_sig_load(struct dilithium_sig *sig, const uint8_t *src_sig,
+ size_t src_sig_len);
+
+/**
+ * @ingroup Dilithium
+ * @brief Obtain the reference to the Dilithium key and its length
+ *
+ * \note Only pointer references into the leancrypto data structure are returned
+ * which implies that any modification will modify the leancrypto key, too.
+ *
+ * @param [out] dilithium_key Dilithium key pointer
+ * @param [out] dilithium_key_len Length of the key buffer
+ * @param [in] sk Dilithium secret key from which the references are obtained
+ *
+ * @return 0 on success, != 0 on error
+ */
+int dilithium_sk_ptr(uint8_t **dilithium_key, size_t *dilithium_key_len,
+ struct dilithium_sk *sk);
+
+/**
+ * @ingroup Dilithium
+ * @brief Obtain the reference to the Dilithium key and its length
+ *
+ * \note Only pointer references into the leancrypto data structure are returned
+ * which implies that any modification will modify the leancrypto key, too.
+ *
+ * @param [out] dilithium_key Dilithium key pointer
+ * @param [out] dilithium_key_len Length of the key buffer
+ * @param [in] pk Dilithium publi key from which the references are obtained
+ *
+ * @return 0 on success, != 0 on error
+ */
+int dilithium_pk_ptr(uint8_t **dilithium_key, size_t *dilithium_key_len,
+ struct dilithium_pk *pk);
+
+/**
+ * @ingroup Dilithium
+ * @brief Obtain the reference to the Dilithium signature and its length
+ *
+ * \note Only pointer references into the leancrypto data structure are returned
+ * which implies that any modification will modify the leancrypto signature,
+ * too.
+ *
+ * @param [out] dilithium_sig Dilithium signature pointer
+ * @param [out] dilithium_sig_len Length of the signature buffer
+ * @param [in] sig Dilithium signature from which the references are obtained
+ *
+ * @return 0 on success, != 0 on error
+ */
+int dilithium_sig_ptr(uint8_t **dilithium_sig, size_t *dilithium_sig_len,
+ struct dilithium_sig *sig);
+
+/**
+ * @ingroup Dilithium
+ * @brief Computes signature in one shot
+ *
+ * @param [out] sig pointer to output signature
+ * @param [in] m pointer to message to be signed
+ * @param [in] mlen length of message
+ * @param [in] sk pointer to bit-packed secret key
+ * @param [in] rng_ctx pointer to seeded random number generator context - when
+ * pointer is non-NULL, perform a randomized signing.
+ * Otherwise use deterministic signing.
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_sign(struct dilithium_sig *sig, const uint8_t *m,
+ size_t mlen, const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx);
+
+/**
+ * @ingroup Dilithium
+ * @brief Computes signature woth user context in one shot
+ *
+ * This API allows the caller to provide an arbitrary context buffer which
+ * is hashed together with the message to form the message digest to be signed.
+ *
+ * Using the ctx structure, the caller can select 3 different types of ML-DSA:
+ *
+ * * ctx->dilithium_prehash_type set to a hash type, HashML-DSA is assumed which
+ * implies that the message m must be exactly digest size (FIPS 204 section
+ * 5.4)
+ *
+ * * ctx->ml_dsa_internal set to 1, the ML-DSA.Sign_internal and
+ * .Verify_internal are executed (FIPS 204 chapter 6)
+ *
+ * * both aforementioned parameter set to NULL / 0, ML-DSA.Sign and
+ * ML-DSA.Verify are executed (FIPS 204 sections 5.2 and 5.3)
+ *
+ * @param [out] sig pointer to output signature
+ * @param [in] ctx reference to the allocated Dilithium context handle
+ * @param [in] m pointer to message to be signed
+ * @param [in] mlen length of message
+ * @param [in] sk pointer to bit-packed secret key
+ * @param [in] rng_ctx pointer to seeded random number generator context - when
+ * pointer is non-NULL, perform a randomized signing.
+ * Otherwise use deterministic signing.
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_sign_ctx(struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen, const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx);
+
+/**
+ * @ingroup Dilithium
+ * @brief Initializes a signature operation
+ *
+ * This call is intended to support messages that are located in non-contiguous
+ * places and even becomes available at different times. This call is to be
+ * used together with the dilithium_sign_update and dilithium_sign_final.
+ *
+ * @param [in,out] ctx pointer Dilithium context
+ * @param [in] sk pointer to bit-packed secret key
+ *
+ * @return 0 (success) or < 0 on error; -EOPNOTSUPP is returned if a different
+ * hash than lc_shake256 is used.
+ */
+int dilithium_sign_init(struct dilithium_ctx *ctx,
+ const struct dilithium_sk *sk);
+
+/**
+ * @ingroup Dilithium
+ * @brief Add more data to an already initialized signature state
+ *
+ * This call is intended to support messages that are located in non-contiguous
+ * places and even becomes available at different times. This call is to be
+ * used together with the dilithium_sign_init and dilithium_sign_final.
+ *
+ * @param [in] ctx pointer to Dilithium context that was initialized with
+ * dilithium_sign_init
+ * @param [in] m pointer to message to be signed
+ * @param [in] mlen length of message
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_sign_update(struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen);
+
+/**
+ * @ingroup Dilithium
+ * @brief Computes signature
+ *
+ * @param [out] sig pointer to output signature
+ * @param [in] ctx pointer to Dilithium context that was initialized with
+ * dilithium_sign_init and filled with
+ * dilithium_sign_update
+ * @param [in] sk pointer to bit-packed secret key
+ * @param [in] rng_ctx pointer to seeded random number generator context - when
+ * pointer is non-NULL, perform a randomized signing.
+ * Otherwise use deterministic signing.
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_sign_final(struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx);
+
+/**
+ * @ingroup Dilithium
+ * @brief Verifies signature in one shot
+ *
+ * @param [in] sig pointer to input signature
+ * @param [in] m pointer to message
+ * @param [in] mlen length of message
+ * @param [in] pk pointer to bit-packed public key
+ *
+ * @return 0 if signature could be verified correctly and -EBADMSG when
+ * signature cannot be verified, < 0 on other errors
+ */
+int dilithium_verify(const struct dilithium_sig *sig, const uint8_t *m,
+ size_t mlen, const struct dilithium_pk *pk);
+
+/**
+ * @ingroup Dilithium
+ * @brief Verifies signature with Dilithium context in one shot
+ *
+ * This API allows the caller to provide an arbitrary context buffer which
+ * is hashed together with the message to form the message digest to be signed.
+ *
+ * @param [in] sig pointer to input signature
+ * @param [in] ctx reference to the allocated Dilithium context handle
+ * @param [in] m pointer to message
+ * @param [in] mlen length of message
+ * @param [in] pk pointer to bit-packed public key
+ *
+ * @return 0 if signature could be verified correctly and -EBADMSG when
+ * signature cannot be verified, < 0 on other errors
+ */
+int dilithium_verify_ctx(const struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen, const struct dilithium_pk *pk);
+
+/**
+ * @ingroup Dilithium
+ * @brief Initializes a signature verification operation
+ *
+ * This call is intended to support messages that are located in non-contiguous
+ * places and even becomes available at different times. This call is to be
+ * used together with the dilithium_verify_update and
+ * dilithium_verify_final.
+ *
+ * @param [in,out] ctx pointer to an allocated Dilithium context
+ * @param [in] pk pointer to bit-packed public key
+ *
+ * @return 0 (success) or < 0 on error; -EOPNOTSUPP is returned if a different
+ * hash than lc_shake256 is used.
+ */
+int dilithium_verify_init(struct dilithium_ctx *ctx,
+ const struct dilithium_pk *pk);
+
+/**
+ * @ingroup Dilithium
+ * @brief Add more data to an already initialized signature state
+ *
+ * This call is intended to support messages that are located in non-contiguous
+ * places and even becomes available at different times. This call is to be
+ * used together with the dilithium_verify_init and
+ * dilithium_verify_final.
+ *
+ * @param [in] ctx pointer to Dilithium context that was initialized with
+ * dilithium_sign_init
+ * @param [in] m pointer to message to be signed
+ * @param [in] mlen length of message
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_verify_update(struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen);
+
+/**
+ * @ingroup Dilithium
+ * @brief Verifies signature
+ *
+ * @param [in] sig pointer to output signature
+ * @param [in] ctx pointer to Dilithium context that was initialized with
+ * dilithium_sign_init and filled with
+ * dilithium_sign_update
+ * @param [in] pk pointer to bit-packed public key
+ *
+ * @return 0 if signature could be verified correctly and -EBADMSG when
+ * signature cannot be verified, < 0 on other errors
+ */
+int dilithium_verify_final(const struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_pk *pk);
+
+/* No valgrind */
+#define poison(addr, len)
+#define unpoison(addr, len)
+#define is_poisoned(addr, len)
+
+#endif /* DILITHIUM_H */
diff --git a/crypto/ml_dsa/dilithium_44.c b/crypto/ml_dsa/dilithium_44.c
new file mode 100644
index 000000000000..1aea716016f0
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_44.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+#define DILITHIUM_TYPE_44
+#include "dilithium.h"
+
+#define DILITHIUM_TYPE "44"
+#define DILITHIUM_MODE 2
+#define DILITHIUM_NIST_CATEGORY DILITHIUM44_NIST_CATEGORY
+#define DILITHIUM_LAMBDA DILITHIUM44_LAMBDA
+#define DILITHIUM_K DILITHIUM44_K
+#define DILITHIUM_L DILITHIUM44_L
+#define DILITHIUM_ETA DILITHIUM44_ETA
+#define DILITHIUM_TAU DILITHIUM44_TAU
+#define DILITHIUM_BETA DILITHIUM44_BETA
+#define DILITHIUM_GAMMA1 DILITHIUM44_GAMMA1
+#define DILITHIUM_GAMMA2 DILITHIUM44_GAMMA2
+#define DILITHIUM_OMEGA DILITHIUM44_OMEGA
+
+#define DILITHIUM_CTILDE_BYTES DILITHIUM44_CTILDE_BYTES
+#define DILITHIUM_POLYT1_PACKEDBYTES DILITHIUM44_POLYT1_PACKEDBYTES
+#define DILITHIUM_POLYT0_PACKEDBYTES DILITHIUM44_POLYT0_PACKEDBYTES
+#define DILITHIUM_POLYVECH_PACKEDBYTES DILITHIUM44_POLYVECH_PACKEDBYTES
+#define DILITHIUM_POLYZ_PACKEDBYTES DILITHIUM44_POLYZ_PACKEDBYTES
+#define DILITHIUM_POLYW1_PACKEDBYTES DILITHIUM44_POLYW1_PACKEDBYTES
+#define DILITHIUM_POLYETA_PACKEDBYTES DILITHIUM44_POLYETA_PACKEDBYTES
+
+#define DILITHIUM_PUBLICKEYBYTES DILITHIUM44_PUBLICKEYBYTES
+#define DILITHIUM_SECRETKEYBYTES DILITHIUM44_SECRETKEYBYTES
+#define DILITHIUM_CRYPTO_BYTES DILITHIUM44_CRYPTO_BYTES
+
+#include "dilithium_ntt.c"
+#include "dilithium_poly.c"
+#include "dilithium_rounding.c"
+#include "dilithium_signature_c.c"
diff --git a/crypto/ml_dsa/dilithium_44.h b/crypto/ml_dsa/dilithium_44.h
new file mode 100644
index 000000000000..6490c83e7100
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_44.h
@@ -0,0 +1,377 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#ifndef DILITHIUM_44_H
+#define DILITHIUM_44_H
+
+/*
+ * Dilithium Security Levels
+ * 2 -> 192 bits of security strength
+ * 3 -> 225 bits of security strength
+ * 5 -> 257 bits of security strength
+ */
+
+/* DILITHIUM_MODE 2 */
+#define DILITHIUM44_NIST_CATEGORY 1
+#define DILITHIUM44_LAMBDA 128
+#define DILITHIUM44_K 4
+#define DILITHIUM44_L 4
+#define DILITHIUM44_ETA 2
+#define DILITHIUM44_TAU 39
+#define DILITHIUM44_BETA 78
+#define DILITHIUM44_GAMMA1 (1 << 17)
+#define DILITHIUM44_GAMMA2 ((DILITHIUM_Q - 1) / 88)
+#define DILITHIUM44_OMEGA 80
+
+#define DILITHIUM44_CTILDE_BYTES (DILITHIUM44_LAMBDA * 2 / 8)
+#define DILITHIUM44_POLYT1_PACKEDBYTES 320
+#define DILITHIUM44_POLYT0_PACKEDBYTES 416
+#define DILITHIUM44_POLYVECH_PACKEDBYTES (DILITHIUM44_OMEGA + DILITHIUM44_K)
+
+#if DILITHIUM44_GAMMA1 == (1 << 17)
+#define DILITHIUM44_POLYZ_PACKEDBYTES 576
+#elif DILITHIUM44_GAMMA1 == (1 << 19)
+#define DILITHIUM44_POLYZ_PACKEDBYTES 640
+#endif
+
+#if DILITHIUM44_GAMMA2 == (DILITHIUM_Q - 1) / 88
+#define DILITHIUM44_POLYW1_PACKEDBYTES 192
+#elif DILITHIUM44_GAMMA2 == (DILITHIUM_Q - 1) / 32
+#define DILITHIUM44_POLYW1_PACKEDBYTES 128
+#endif
+
+#if DILITHIUM44_ETA == 2
+#define DILITHIUM44_POLYETA_PACKEDBYTES 96
+#elif DILITHIUM44_ETA == 4
+#define DILITHIUM44_POLYETA_PACKEDBYTES 128
+#endif
+
+/*
+ * Sizes of the different Dilithium buffer types.
+ *
+ * WARNING: Do not use these defines in your code. If you need the sizes of
+ * the different variable sizes, use sizeof of the different variable structs or
+ * use the different *_size functions documented below to retrieve the data size
+ * of a particular Dilithium component.
+ */
+#define DILITHIUM44_PUBLICKEYBYTES \
+ (DILITHIUM_SEEDBYTES + \
+ DILITHIUM44_K * DILITHIUM44_POLYT1_PACKEDBYTES)
+#define DILITHIUM44_SECRETKEYBYTES \
+ (2 * DILITHIUM_SEEDBYTES + DILITHIUM_TRBYTES + \
+ DILITHIUM44_L * DILITHIUM44_POLYETA_PACKEDBYTES + \
+ DILITHIUM44_K * DILITHIUM44_POLYETA_PACKEDBYTES + \
+ DILITHIUM44_K * DILITHIUM44_POLYT0_PACKEDBYTES)
+
+#define DILITHIUM44_CRYPTO_BYTES \
+ (DILITHIUM44_CTILDE_BYTES + \
+ DILITHIUM44_L * DILITHIUM44_POLYZ_PACKEDBYTES + \
+ DILITHIUM44_POLYVECH_PACKEDBYTES)
+
+#ifndef __ASSEMBLER__
+/**
+ * @brief Dilithium secret key
+ */
+struct dilithium_44_sk {
+ uint8_t sk[DILITHIUM44_SECRETKEYBYTES];
+};
+
+/**
+ * @brief Dilithium public key
+ */
+struct dilithium_44_pk {
+ uint8_t pk[DILITHIUM44_PUBLICKEYBYTES];
+};
+
+/**
+ * @brief Dilithium signature
+ */
+struct dilithium_44_sig {
+ uint8_t sig[DILITHIUM44_CRYPTO_BYTES];
+};
+
+/*
+ * The alignment is based on largest alignment of a polyvecl typedef - this is
+ * the AVX2 definition.
+ */
+#define DILITHIUM_AHAT_ALIGNMENT (32)
+
+/* Size of the AHat matrix for ML-DSA 87 */
+#define DILITHIUM_44_AHAT_SIZE \
+ (256 * sizeof(int32_t) * DILITHIUM44_K * DILITHIUM44_L)
+
+/**
+ * @brief Zeroize Dilithium context allocated with
+ * DILITHIUM_CTX_ON_STACK dilithium_ed25519_alloc
+ *
+ * @param [in] ctx Dilithium context to be zeroized
+ */
+static inline void dilithium_44_ctx_zero(struct dilithium_ctx *ctx)
+{
+ if (!ctx)
+ return;
+ dilithium_hash_clear(ctx);
+ if (ctx->ahat) {
+ memzero_explicit(ctx->ahat, ctx->ahat_size);
+ ctx->ahat_expanded = 0;
+ }
+}
+
+/**
+ * @brief Allocate Dilithium stream context on heap
+ *
+ * @param [out] ctx Allocated Dilithium stream context
+ *
+ * @return: 0 on success, < 0 on error
+ */
+struct dilithium_ctx *dilithium_44_ctx_alloc(void);
+
+/**
+ * @brief Allocate Dilithium stream context on heap including additional
+ * parameter relevant for the signature operation.
+ *
+ * \note See \p DILITHIUM_44_CTX_ON_STACK_AHAT for details.
+ *
+ * @param [out] ctx Allocated Dilithium stream context
+ *
+ * @return: 0 on success, < 0 on error
+ */
+struct dilithium_ctx *dilithium_44_ctx_alloc_ahat(void);
+
+/**
+ * @brief Zeroize and free Dilithium stream context
+ *
+ * @param [in] ctx Dilithium stream context to be zeroized and freed
+ */
+void dilithium_44_ctx_zero_free(struct dilithium_ctx *ctx);
+
+/**
+ * @brief Return the size of the Dilithium secret key.
+ */
+__pure
+static inline unsigned int dilithium_44_sk_size(void)
+{
+ return sizeof_field(struct dilithium_44_sk, sk);
+}
+
+/**
+ * @brief Return the size of the Dilithium public key.
+ */
+__pure
+static inline unsigned int dilithium_44_pk_size(void)
+{
+ return sizeof_field(struct dilithium_44_pk, pk);
+}
+
+/**
+ * @brief Return the size of the Dilithium signature.
+ */
+__pure
+static inline unsigned int dilithium_44_sig_size(void)
+{
+ return sizeof_field(struct dilithium_44_sig, sig);
+}
+
+/**
+ * @brief Computes ML-DSA signature in one shot
+ *
+ * @param [out] sig pointer to output signature
+ * @param [in] m pointer to message to be signed
+ * @param [in] mlen length of message
+ * @param [in] sk pointer to bit-packed secret key
+ * @param [in] rng_ctx pointer to seeded random number generator context - when
+ * pointer is non-NULL, perform a randomized signing.
+ * Otherwise use deterministic signing.
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_44_sign(struct dilithium_44_sig *sig, const uint8_t *m,
+ size_t mlen, const struct dilithium_44_sk *sk,
+ struct crypto_rng *rng_ctx);
+
+/**
+ * @brief Computes signature with Dilithium context in one shot
+ *
+ * This API allows the caller to provide an arbitrary context buffer which
+ * is hashed together with the message to form the message digest to be signed.
+ *
+ * @param [out] sig pointer to output signature
+ * @param [in] ctx reference to the allocated Dilithium context handle
+ * @param [in] m pointer to message to be signed
+ * @param [in] mlen length of message
+ * @param [in] sk pointer to bit-packed secret key
+ * @param [in] rng_ctx pointer to seeded random number generator context - when
+ * pointer is non-NULL, perform a randomized signing.
+ * Otherwise use deterministic signing.
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_44_sign_ctx(struct dilithium_44_sig *sig,
+ struct dilithium_ctx *ctx,
+ const uint8_t *m, size_t mlen,
+ const struct dilithium_44_sk *sk,
+ struct crypto_rng *rng_ctx);
+
+/**
+ * @brief Initializes a signature operation
+ *
+ * This call is intended to support messages that are located in non-contiguous
+ * places and even becomes available at different times. This call is to be
+ * used together with the dilithium_sign_update and dilithium_sign_final.
+ *
+ * @param [in,out] ctx pointer to an allocated Dilithium context
+ * @param [in] sk pointer to bit-packed secret key
+ *
+ * @return 0 (success) or < 0 on error; -EOPNOTSUPP is returned if a different
+ * hash than lc_shake256 is used.
+ */
+int dilithium_44_sign_init(struct dilithium_ctx *ctx,
+ const struct dilithium_44_sk *sk);
+
+/**
+ * @brief Add more data to an already initialized signature state
+ *
+ * This call is intended to support messages that are located in non-contiguous
+ * places and even becomes available at different times. This call is to be
+ * used together with the dilithium_sign_init and dilithium_sign_final.
+ *
+ * @param [in] ctx pointer to Dilithium context that was initialized with
+ * dilithium_sign_init
+ * @param [in] m pointer to message to be signed
+ * @param [in] mlen length of message
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_44_sign_update(struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen);
+
+/**
+ * @brief Computes signature
+ *
+ * @param [out] sig pointer to output signature
+ * @param [in] ctx pointer to Dilithium context that was initialized with
+ * dilithium_sign_init and filled with
+ * dilithium_sign_update
+ * @param [in] sk pointer to bit-packed secret key
+ * @param [in] rng_ctx pointer to seeded random number generator context - when
+ * pointer is non-NULL, perform a randomized signing.
+ * Otherwise use deterministic signing.
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_44_sign_final(struct dilithium_44_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_44_sk *sk,
+ struct crypto_rng *rng_ctx);
+
+/**
+ * @brief Verifies ML-DSA signature in one shot
+ *
+ * @param [in] sig pointer to input signature
+ * @param [in] m pointer to message
+ * @param [in] mlen length of message
+ * @param [in] pk pointer to bit-packed public key
+ *
+ * @return 0 if signature could be verified correctly and -EBADMSG when
+ * signature cannot be verified, < 0 on other errors
+ */
+int dilithium_44_verify(const struct dilithium_44_sig *sig, const uint8_t *m,
+ size_t mlen, const struct dilithium_44_pk *pk);
+
+/**
+ * @brief Verifies signature with Dilithium context in one shot
+ *
+ * This API allows the caller to provide an arbitrary context buffer which
+ * is hashed together with the message to form the message digest to be signed.
+ *
+ * @param [in] sig pointer to input signature
+ * @param [in] ctx reference to the allocated Dilithium context handle
+ * @param [in] m pointer to message
+ * @param [in] mlen length of message
+ * @param [in] pk pointer to bit-packed public key
+ *
+ * @return 0 if signature could be verified correctly and -EBADMSG when
+ * signature cannot be verified, < 0 on other errors
+ */
+int dilithium_44_verify_ctx(const struct dilithium_44_sig *sig,
+ struct dilithium_ctx *ctx,
+ const uint8_t *m, size_t mlen,
+ const struct dilithium_44_pk *pk);
+
+/**
+ * @brief Initializes a signature verification operation
+ *
+ * This call is intended to support messages that are located in non-contiguous
+ * places and even becomes available at different times. This call is to be
+ * used together with the dilithium_verify_update and
+ * dilithium_verify_final.
+ *
+ * @param [in,out] ctx pointer to an allocated Dilithium context
+ * @param [in] pk pointer to bit-packed public key
+ *
+ * @return 0 (success) or < 0 on error; -EOPNOTSUPP is returned if a different
+ * hash than lc_shake256 is used.
+ */
+int dilithium_44_verify_init(struct dilithium_ctx *ctx,
+ const struct dilithium_44_pk *pk);
+
+/**
+ * @brief Add more data to an already initialized signature state
+ *
+ * This call is intended to support messages that are located in non-contiguous
+ * places and even becomes available at different times. This call is to be
+ * used together with the dilithium_verify_init and
+ * dilithium_verify_final.
+ *
+ * @param [in,out] ctx pointer to Dilithium context that was initialized with
+ * dilithium_sign_init
+ * @param [in] m pointer to message to be signed
+ * @param [in] mlen length of message
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_44_verify_update(struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen);
+
+/**
+ * @brief Verifies signature
+ *
+ * @param [in] sig pointer to output signature
+ * @param [in] ctx pointer to Dilithium context that was initialized with
+ * dilithium_sign_init and filled with
+ * dilithium_sign_update
+ * @param [in] pk pointer to bit-packed public key
+ *
+ * @return 0 if signature could be verified correctly and -EBADMSG when
+ * signature cannot be verified, < 0 on other errors
+ */
+int dilithium_44_verify_final(const struct dilithium_44_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_44_pk *pk);
+
+#endif /* __ASSEMBLER__ */
+
+#endif /* DILITHIUM_44_H */
diff --git a/crypto/ml_dsa/dilithium_65.c b/crypto/ml_dsa/dilithium_65.c
new file mode 100644
index 000000000000..08f3a8e71228
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_65.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+#define DILITHIUM_TYPE_65
+#include "dilithium.h"
+
+#define DILITHIUM_TYPE "65"
+#define DILITHIUM_MODE 3
+#define DILITHIUM_NIST_CATEGORY DILITHIUM65_NIST_CATEGORY
+#define DILITHIUM_LAMBDA DILITHIUM65_LAMBDA
+#define DILITHIUM_K DILITHIUM65_K
+#define DILITHIUM_L DILITHIUM65_L
+#define DILITHIUM_ETA DILITHIUM65_ETA
+#define DILITHIUM_TAU DILITHIUM65_TAU
+#define DILITHIUM_BETA DILITHIUM65_BETA
+#define DILITHIUM_GAMMA1 DILITHIUM65_GAMMA1
+#define DILITHIUM_GAMMA2 DILITHIUM65_GAMMA2
+#define DILITHIUM_OMEGA DILITHIUM65_OMEGA
+
+#define DILITHIUM_CTILDE_BYTES DILITHIUM65_CTILDE_BYTES
+#define DILITHIUM_POLYT1_PACKEDBYTES DILITHIUM65_POLYT1_PACKEDBYTES
+#define DILITHIUM_POLYT0_PACKEDBYTES DILITHIUM65_POLYT0_PACKEDBYTES
+#define DILITHIUM_POLYVECH_PACKEDBYTES DILITHIUM65_POLYVECH_PACKEDBYTES
+#define DILITHIUM_POLYZ_PACKEDBYTES DILITHIUM65_POLYZ_PACKEDBYTES
+#define DILITHIUM_POLYW1_PACKEDBYTES DILITHIUM65_POLYW1_PACKEDBYTES
+#define DILITHIUM_POLYETA_PACKEDBYTES DILITHIUM65_POLYETA_PACKEDBYTES
+
+#define DILITHIUM_PUBLICKEYBYTES DILITHIUM65_PUBLICKEYBYTES
+#define DILITHIUM_SECRETKEYBYTES DILITHIUM65_SECRETKEYBYTES
+#define DILITHIUM_CRYPTO_BYTES DILITHIUM65_CRYPTO_BYTES
+
+#include "dilithium_ntt.c"
+#include "dilithium_poly.c"
+#include "dilithium_rounding.c"
+#include "dilithium_signature_c.c"
diff --git a/crypto/ml_dsa/dilithium_65.h b/crypto/ml_dsa/dilithium_65.h
new file mode 100644
index 000000000000..0a36f7eed63e
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_65.h
@@ -0,0 +1,377 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#ifndef DILITHIUM_65_H
+#define DILITHIUM_65_H
+
+/*
+ * Dilithium Security Levels
+ * 2 -> 192 bits of security strength
+ * 3 -> 225 bits of security strength
+ * 5 -> 257 bits of security strength
+ */
+
+/* DILITHIUM_MODE 3 */
+#define DILITHIUM65_NIST_CATEGORY 3
+#define DILITHIUM65_LAMBDA 192
+#define DILITHIUM65_K 6
+#define DILITHIUM65_L 5
+#define DILITHIUM65_ETA 4
+#define DILITHIUM65_TAU 49
+#define DILITHIUM65_BETA 196
+#define DILITHIUM65_GAMMA1 (1 << 19)
+#define DILITHIUM65_GAMMA2 ((DILITHIUM_Q - 1) / 32)
+#define DILITHIUM65_OMEGA 55
+
+#define DILITHIUM65_CTILDE_BYTES (DILITHIUM65_LAMBDA * 2 / 8)
+#define DILITHIUM65_POLYT1_PACKEDBYTES 320
+#define DILITHIUM65_POLYT0_PACKEDBYTES 416
+#define DILITHIUM65_POLYVECH_PACKEDBYTES (DILITHIUM65_OMEGA + DILITHIUM65_K)
+
+#if DILITHIUM65_GAMMA1 == (1 << 17)
+#define DILITHIUM65_POLYZ_PACKEDBYTES 576
+#elif DILITHIUM65_GAMMA1 == (1 << 19)
+#define DILITHIUM65_POLYZ_PACKEDBYTES 640
+#endif
+
+#if DILITHIUM65_GAMMA2 == (DILITHIUM_Q - 1) / 88
+#define DILITHIUM65_POLYW1_PACKEDBYTES 192
+#elif DILITHIUM65_GAMMA2 == (DILITHIUM_Q - 1) / 32
+#define DILITHIUM65_POLYW1_PACKEDBYTES 128
+#endif
+
+#if DILITHIUM65_ETA == 2
+#define DILITHIUM65_POLYETA_PACKEDBYTES 96
+#elif DILITHIUM65_ETA == 4
+#define DILITHIUM65_POLYETA_PACKEDBYTES 128
+#endif
+
+/*
+ * Sizes of the different Dilithium buffer types.
+ *
+ * WARNING: Do not use these defines in your code. If you need the sizes of
+ * the different variable sizes, use sizeof of the different variable structs or
+ * use the different *_size functions documented below to retrieve the data size
+ * of a particular Dilithium component.
+ */
+#define DILITHIUM65_PUBLICKEYBYTES \
+ (DILITHIUM_SEEDBYTES + \
+ DILITHIUM65_K * DILITHIUM65_POLYT1_PACKEDBYTES)
+#define DILITHIUM65_SECRETKEYBYTES \
+ (2 * DILITHIUM_SEEDBYTES + DILITHIUM_TRBYTES + \
+ DILITHIUM65_L * DILITHIUM65_POLYETA_PACKEDBYTES + \
+ DILITHIUM65_K * DILITHIUM65_POLYETA_PACKEDBYTES + \
+ DILITHIUM65_K * DILITHIUM65_POLYT0_PACKEDBYTES)
+
+#define DILITHIUM65_CRYPTO_BYTES \
+ (DILITHIUM65_CTILDE_BYTES + \
+ DILITHIUM65_L * DILITHIUM65_POLYZ_PACKEDBYTES + \
+ DILITHIUM65_POLYVECH_PACKEDBYTES)
+
+#ifndef __ASSEMBLER__
+/**
+ * @brief Dilithium secret key
+ */
+struct dilithium_65_sk {
+ uint8_t sk[DILITHIUM65_SECRETKEYBYTES];
+};
+
+/**
+ * @brief Dilithium public key
+ */
+struct dilithium_65_pk {
+ uint8_t pk[DILITHIUM65_PUBLICKEYBYTES];
+};
+
+/**
+ * @brief Dilithium signature
+ */
+struct dilithium_65_sig {
+ uint8_t sig[DILITHIUM65_CRYPTO_BYTES];
+};
+
+/*
+ * The alignment is based on largest alignment of a polyvecl typedef - this is
+ * the AVX2 definition.
+ */
+#define DILITHIUM_AHAT_ALIGNMENT (32)
+
+/* Size of the AHat matrix for ML-DSA 87 */
+#define DILITHIUM_65_AHAT_SIZE \
+ (256 * sizeof(int32_t) * DILITHIUM65_K * DILITHIUM65_L)
+
+/**
+ * @brief Zeroize Dilithium context allocated with
+ * DILITHIUM_CTX_ON_STACK dilithium_ed25519_alloc
+ *
+ * @param [in] ctx Dilithium context to be zeroized
+ */
+static inline void dilithium_65_ctx_zero(struct dilithium_ctx *ctx)
+{
+ if (!ctx)
+ return;
+ dilithium_hash_clear(ctx);
+ if (ctx->ahat) {
+ memzero_explicit(ctx->ahat, ctx->ahat_size);
+ ctx->ahat_expanded = 0;
+ }
+}
+
+/**
+ * @brief Allocate Dilithium stream context on heap
+ *
+ * @param [out] ctx Allocated Dilithium stream context
+ *
+ * @return: 0 on success, < 0 on error
+ */
+struct dilithium_ctx *dilithium_65_ctx_alloc(void);
+
+/**
+ * @brief Allocate Dilithium stream context on heap including additional
+ * parameter relevant for the signature operation.
+ *
+ * \note See \p DILITHIUM_65_CTX_ON_STACK_AHAT for details.
+ *
+ * @param [out] ctx Allocated Dilithium stream context
+ *
+ * @return: 0 on success, < 0 on error
+ */
+struct dilithium_ctx *dilithium_65_ctx_alloc_ahat(void);
+
+/**
+ * @brief Zeroize and free Dilithium stream context
+ *
+ * @param [in] ctx Dilithium stream context to be zeroized and freed
+ */
+void dilithium_65_ctx_zero_free(struct dilithium_ctx *ctx);
+
+/**
+ * @brief Return the size of the Dilithium secret key.
+ */
+__pure
+static inline unsigned int dilithium_65_sk_size(void)
+{
+ return sizeof_field(struct dilithium_65_sk, sk);
+}
+
+/**
+ * @brief Return the size of the Dilithium public key.
+ */
+__pure
+static inline unsigned int dilithium_65_pk_size(void)
+{
+ return sizeof_field(struct dilithium_65_pk, pk);
+}
+
+/**
+ * @brief Return the size of the Dilithium signature.
+ */
+__pure
+static inline unsigned int dilithium_65_sig_size(void)
+{
+ return sizeof_field(struct dilithium_65_sig, sig);
+}
+
+/**
+ * @brief Computes ML-DSA signature in one shot
+ *
+ * @param [out] sig pointer to output signature
+ * @param [in] m pointer to message to be signed
+ * @param [in] mlen length of message
+ * @param [in] sk pointer to bit-packed secret key
+ * @param [in] rng_ctx pointer to seeded random number generator context - when
+ * pointer is non-NULL, perform a randomized signing.
+ * Otherwise use deterministic signing.
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_65_sign(struct dilithium_65_sig *sig, const uint8_t *m,
+ size_t mlen, const struct dilithium_65_sk *sk,
+ struct crypto_rng *rng_ctx);
+
+/**
+ * @brief Computes signature with Dilithium context in one shot
+ *
+ * This API allows the caller to provide an arbitrary context buffer which
+ * is hashed together with the message to form the message digest to be signed.
+ *
+ * @param [out] sig pointer to output signature
+ * @param [in] ctx reference to the allocated Dilithium context handle
+ * @param [in] m pointer to message to be signed
+ * @param [in] mlen length of message
+ * @param [in] sk pointer to bit-packed secret key
+ * @param [in] rng_ctx pointer to seeded random number generator context - when
+ * pointer is non-NULL, perform a randomized signing.
+ * Otherwise use deterministic signing.
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_65_sign_ctx(struct dilithium_65_sig *sig,
+ struct dilithium_ctx *ctx,
+ const uint8_t *m, size_t mlen,
+ const struct dilithium_65_sk *sk,
+ struct crypto_rng *rng_ctx);
+
+/**
+ * @brief Initializes a signature operation
+ *
+ * This call is intended to support messages that are located in non-contiguous
+ * places and even becomes available at different times. This call is to be
+ * used together with the dilithium_sign_update and dilithium_sign_final.
+ *
+ * @param [in,out] ctx pointer to an allocated Dilithium context
+ * @param [in] sk pointer to bit-packed secret key
+ *
+ * @return 0 (success) or < 0 on error; -EOPNOTSUPP is returned if a different
+ * hash than lc_shake256 is used.
+ */
+int dilithium_65_sign_init(struct dilithium_ctx *ctx,
+ const struct dilithium_65_sk *sk);
+
+/**
+ * @brief Add more data to an already initialized signature state
+ *
+ * This call is intended to support messages that are located in non-contiguous
+ * places and even becomes available at different times. This call is to be
+ * used together with the dilithium_sign_init and dilithium_sign_final.
+ *
+ * @param [in] ctx pointer to Dilithium context that was initialized with
+ * dilithium_sign_init
+ * @param [in] m pointer to message to be signed
+ * @param [in] mlen length of message
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_65_sign_update(struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen);
+
+/**
+ * @brief Computes signature
+ *
+ * @param [out] sig pointer to output signature
+ * @param [in] ctx pointer to Dilithium context that was initialized with
+ * dilithium_sign_init and filled with
+ * dilithium_sign_update
+ * @param [in] sk pointer to bit-packed secret key
+ * @param [in] rng_ctx pointer to seeded random number generator context - when
+ * pointer is non-NULL, perform a randomized signing.
+ * Otherwise use deterministic signing.
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_65_sign_final(struct dilithium_65_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_65_sk *sk,
+ struct crypto_rng *rng_ctx);
+
+/**
+ * @brief Verifies ML-DSA signature in one shot
+ *
+ * @param [in] sig pointer to input signature
+ * @param [in] m pointer to message
+ * @param [in] mlen length of message
+ * @param [in] pk pointer to bit-packed public key
+ *
+ * @return 0 if signature could be verified correctly and -EBADMSG when
+ * signature cannot be verified, < 0 on other errors
+ */
+int dilithium_65_verify(const struct dilithium_65_sig *sig, const uint8_t *m,
+ size_t mlen, const struct dilithium_65_pk *pk);
+
+/**
+ * @brief Verifies signature with Dilithium context in one shot
+ *
+ * This API allows the caller to provide an arbitrary context buffer which
+ * is hashed together with the message to form the message digest to be signed.
+ *
+ * @param [in] sig pointer to input signature
+ * @param [in] ctx reference to the allocated Dilithium context handle
+ * @param [in] m pointer to message
+ * @param [in] mlen length of message
+ * @param [in] pk pointer to bit-packed public key
+ *
+ * @return 0 if signature could be verified correctly and -EBADMSG when
+ * signature cannot be verified, < 0 on other errors
+ */
+int dilithium_65_verify_ctx(const struct dilithium_65_sig *sig,
+ struct dilithium_ctx *ctx,
+ const uint8_t *m, size_t mlen,
+ const struct dilithium_65_pk *pk);
+
+/**
+ * @brief Initializes a signature verification operation
+ *
+ * This call is intended to support messages that are located in non-contiguous
+ * places and even becomes available at different times. This call is to be
+ * used together with the dilithium_verify_update and
+ * dilithium_verify_final.
+ *
+ * @param [in,out] ctx pointer to an allocated Dilithium context
+ * @param [in] pk pointer to bit-packed public key
+ *
+ * @return 0 (success) or < 0 on error; -EOPNOTSUPP is returned if a different
+ * hash than lc_shake256 is used.
+ */
+int dilithium_65_verify_init(struct dilithium_ctx *ctx,
+ const struct dilithium_65_pk *pk);
+
+/**
+ * @brief Add more data to an already initialized signature state
+ *
+ * This call is intended to support messages that are located in non-contiguous
+ * places and even becomes available at different times. This call is to be
+ * used together with the dilithium_verify_init and
+ * dilithium_verify_final.
+ *
+ * @param [in,out] ctx pointer to Dilithium context that was initialized with
+ * dilithium_sign_init
+ * @param [in] m pointer to message to be signed
+ * @param [in] mlen length of message
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_65_verify_update(struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen);
+
+/**
+ * @brief Verifies signature
+ *
+ * @param [in] sig pointer to output signature
+ * @param [in] ctx pointer to Dilithium context that was initialized with
+ * dilithium_sign_init and filled with
+ * dilithium_sign_update
+ * @param [in] pk pointer to bit-packed public key
+ *
+ * @return 0 if signature could be verified correctly and -EBADMSG when
+ * signature cannot be verified, < 0 on other errors
+ */
+int dilithium_65_verify_final(const struct dilithium_65_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_65_pk *pk);
+
+#endif /* __ASSEMBLER__ */
+
+#endif /* DILITHIUM_65_H */
diff --git a/crypto/ml_dsa/dilithium_87.c b/crypto/ml_dsa/dilithium_87.c
new file mode 100644
index 000000000000..fcc3e0229ed9
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_87.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+#define DILITHIUM_TYPE_87
+#include "dilithium.h"
+
+#define DILITHIUM_TYPE "87"
+#define DILITHIUM_MODE 5
+#define DILITHIUM_NIST_CATEGORY DILITHIUM87_NIST_CATEGORY
+#define DILITHIUM_LAMBDA DILITHIUM87_LAMBDA
+#define DILITHIUM_K DILITHIUM87_K
+#define DILITHIUM_L DILITHIUM87_L
+#define DILITHIUM_ETA DILITHIUM87_ETA
+#define DILITHIUM_TAU DILITHIUM87_TAU
+#define DILITHIUM_BETA DILITHIUM87_BETA
+#define DILITHIUM_GAMMA1 DILITHIUM87_GAMMA1
+#define DILITHIUM_GAMMA2 DILITHIUM87_GAMMA2
+#define DILITHIUM_OMEGA DILITHIUM87_OMEGA
+
+#define DILITHIUM_CTILDE_BYTES DILITHIUM87_CTILDE_BYTES
+#define DILITHIUM_POLYT1_PACKEDBYTES DILITHIUM87_POLYT1_PACKEDBYTES
+#define DILITHIUM_POLYT0_PACKEDBYTES DILITHIUM87_POLYT0_PACKEDBYTES
+#define DILITHIUM_POLYVECH_PACKEDBYTES DILITHIUM87_POLYVECH_PACKEDBYTES
+#define DILITHIUM_POLYZ_PACKEDBYTES DILITHIUM87_POLYZ_PACKEDBYTES
+#define DILITHIUM_POLYW1_PACKEDBYTES DILITHIUM87_POLYW1_PACKEDBYTES
+#define DILITHIUM_POLYETA_PACKEDBYTES DILITHIUM87_POLYETA_PACKEDBYTES
+
+#define DILITHIUM_PUBLICKEYBYTES DILITHIUM87_PUBLICKEYBYTES
+#define DILITHIUM_SECRETKEYBYTES DILITHIUM87_SECRETKEYBYTES
+#define DILITHIUM_CRYPTO_BYTES DILITHIUM87_CRYPTO_BYTES
+
+#include "dilithium_ntt.c"
+#include "dilithium_poly.c"
+#include "dilithium_rounding.c"
+#include "dilithium_signature_c.c"
diff --git a/crypto/ml_dsa/dilithium_87.h b/crypto/ml_dsa/dilithium_87.h
new file mode 100644
index 000000000000..eaed8e675383
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_87.h
@@ -0,0 +1,377 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#ifndef DILITHIUM_87_H
+#define DILITHIUM_87_H
+
+/*
+ * Dilithium Security Levels
+ * 2 -> 192 bits of security strength
+ * 3 -> 225 bits of security strength
+ * 5 -> 257 bits of security strength
+ */
+
+/* DILITHIUM_MODE 5 */
+#define DILITHIUM87_NIST_CATEGORY 5
+#define DILITHIUM87_LAMBDA 256
+#define DILITHIUM87_K 8
+#define DILITHIUM87_L 7
+#define DILITHIUM87_ETA 2
+#define DILITHIUM87_TAU 60
+#define DILITHIUM87_BETA 120
+#define DILITHIUM87_GAMMA1 (1 << 19)
+#define DILITHIUM87_GAMMA2 ((DILITHIUM_Q - 1) / 32)
+#define DILITHIUM87_OMEGA 75
+
+#define DILITHIUM87_CTILDE_BYTES (DILITHIUM87_LAMBDA * 2 / 8)
+#define DILITHIUM87_POLYT1_PACKEDBYTES 320
+#define DILITHIUM87_POLYT0_PACKEDBYTES 416
+#define DILITHIUM87_POLYVECH_PACKEDBYTES (DILITHIUM87_OMEGA + DILITHIUM87_K)
+
+#if DILITHIUM87_GAMMA1 == (1 << 17)
+#define DILITHIUM87_POLYZ_PACKEDBYTES 576
+#elif DILITHIUM87_GAMMA1 == (1 << 19)
+#define DILITHIUM87_POLYZ_PACKEDBYTES 640
+#endif
+
+#if DILITHIUM87_GAMMA2 == (DILITHIUM_Q - 1) / 88
+#define DILITHIUM87_POLYW1_PACKEDBYTES 192
+#elif DILITHIUM87_GAMMA2 == (DILITHIUM_Q - 1) / 32
+#define DILITHIUM87_POLYW1_PACKEDBYTES 128
+#endif
+
+#if DILITHIUM87_ETA == 2
+#define DILITHIUM87_POLYETA_PACKEDBYTES 96
+#elif DILITHIUM87_ETA == 4
+#define DILITHIUM87_POLYETA_PACKEDBYTES 128
+#endif
+
+/*
+ * Sizes of the different Dilithium buffer types.
+ *
+ * WARNING: Do not use these defines in your code. If you need the sizes of
+ * the different variable sizes, use sizeof of the different variable structs or
+ * use the different *_size functions documented below to retrieve the data size
+ * of a particular Dilithium component.
+ */
+#define DILITHIUM87_PUBLICKEYBYTES \
+ (DILITHIUM_SEEDBYTES + \
+ DILITHIUM87_K * DILITHIUM87_POLYT1_PACKEDBYTES)
+#define DILITHIUM87_SECRETKEYBYTES \
+ (2 * DILITHIUM_SEEDBYTES + DILITHIUM_TRBYTES + \
+ DILITHIUM87_L * DILITHIUM87_POLYETA_PACKEDBYTES + \
+ DILITHIUM87_K * DILITHIUM87_POLYETA_PACKEDBYTES + \
+ DILITHIUM87_K * DILITHIUM87_POLYT0_PACKEDBYTES)
+
+#define DILITHIUM87_CRYPTO_BYTES \
+ (DILITHIUM87_CTILDE_BYTES + \
+ DILITHIUM87_L * DILITHIUM87_POLYZ_PACKEDBYTES + \
+ DILITHIUM87_POLYVECH_PACKEDBYTES)
+
+#ifndef __ASSEMBLER__
+/**
+ * @brief Dilithium secret key
+ */
+struct dilithium_87_sk {
+ uint8_t sk[DILITHIUM87_SECRETKEYBYTES];
+};
+
+/**
+ * @brief Dilithium public key
+ */
+struct dilithium_87_pk {
+ uint8_t pk[DILITHIUM87_PUBLICKEYBYTES];
+};
+
+/**
+ * @brief Dilithium signature
+ */
+struct dilithium_87_sig {
+ uint8_t sig[DILITHIUM87_CRYPTO_BYTES];
+};
+
+/*
+ * The alignment is based on largest alignment of a polyvecl typedef - this is
+ * the AVX2 definition.
+ */
+#define DILITHIUM_AHAT_ALIGNMENT (32)
+
+/* Size of the AHat matrix for ML-DSA 87 */
+#define DILITHIUM_87_AHAT_SIZE \
+ (256 * sizeof(int32_t) * DILITHIUM87_K * DILITHIUM87_L)
+
+/**
+ * @brief Zeroize Dilithium context allocated with
+ * DILITHIUM87_CTX_ON_STACK dilithium_ed25519_alloc
+ *
+ * @param [in] ctx Dilithium context to be zeroized
+ */
+static inline void dilithium_87_ctx_zero(struct dilithium_ctx *ctx)
+{
+ if (!ctx)
+ return;
+ dilithium_hash_clear(ctx);
+ if (ctx->ahat) {
+ memzero_explicit(ctx->ahat, ctx->ahat_size);
+ ctx->ahat_expanded = 0;
+ }
+}
+
+/**
+ * @brief Allocate Dilithium stream context on heap
+ *
+ * @param [out] ctx Allocated Dilithium stream context
+ *
+ * @return: 0 on success, < 0 on error
+ */
+struct dilithium_ctx *dilithium_87_ctx_alloc(void);
+
+/**
+ * @brief Allocate Dilithium stream context on heap including additional
+ * parameter relevant for the signature operation.
+ *
+ * \note See \p DILITHIUM_87_CTX_ON_STACK_AHAT for details.
+ *
+ * @param [out] ctx Allocated Dilithium stream context
+ *
+ * @return: 0 on success, < 0 on error
+ */
+struct dilithium_ctx *dilithium_87_ctx_alloc_ahat(void);
+
+/**
+ * @brief Zeroize and free Dilithium stream context
+ *
+ * @param [in] ctx Dilithium stream context to be zeroized and freed
+ */
+void dilithium_87_ctx_zero_free(struct dilithium_ctx *ctx);
+
+/**
+ * @brief Return the size of the Dilithium secret key.
+ */
+__pure
+static inline unsigned int dilithium_87_sk_size(void)
+{
+ return sizeof_field(struct dilithium_87_sk, sk);
+}
+
+/**
+ * @brief Return the size of the Dilithium public key.
+ */
+__pure
+static inline unsigned int dilithium_87_pk_size(void)
+{
+ return sizeof_field(struct dilithium_87_pk, pk);
+}
+
+/**
+ * @brief Return the size of the Dilithium signature.
+ */
+__pure
+static inline unsigned int dilithium_87_sig_size(void)
+{
+ return sizeof_field(struct dilithium_87_sig, sig);
+}
+
+/**
+ * @brief Computes ML-DSA signature in one shot
+ *
+ * @param [out] sig pointer to output signature
+ * @param [in] m pointer to message to be signed
+ * @param [in] mlen length of message
+ * @param [in] sk pointer to bit-packed secret key
+ * @param [in] rng_ctx pointer to seeded random number generator context - when
+ * pointer is non-NULL, perform a randomized signing.
+ * Otherwise use deterministic signing.
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_87_sign(struct dilithium_87_sig *sig, const uint8_t *m,
+ size_t mlen, const struct dilithium_87_sk *sk,
+ struct crypto_rng *rng_ctx);
+
+/**
+ * @brief Computes signature with Dilithium context in one shot
+ *
+ * This API allows the caller to provide an arbitrary context buffer which
+ * is hashed together with the message to form the message digest to be signed.
+ *
+ * @param [out] sig pointer to output signature
+ * @param [in] ctx reference to the allocated Dilithium context handle
+ * @param [in] m pointer to message to be signed
+ * @param [in] mlen length of message
+ * @param [in] sk pointer to bit-packed secret key
+ * @param [in] rng_ctx pointer to seeded random number generator context - when
+ * pointer is non-NULL, perform a randomized signing.
+ * Otherwise use deterministic signing.
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_87_sign_ctx(struct dilithium_87_sig *sig,
+ struct dilithium_ctx *ctx,
+ const uint8_t *m, size_t mlen,
+ const struct dilithium_87_sk *sk,
+ struct crypto_rng *rng_ctx);
+
+/**
+ * @brief Initializes a signature operation
+ *
+ * This call is intended to support messages that are located in non-contiguous
+ * places and even becomes available at different times. This call is to be
+ * used together with the dilithium_sign_update and dilithium_sign_final.
+ *
+ * @param [in,out] ctx pointer to an allocated Dilithium context
+ * @param [in] sk pointer to bit-packed secret key
+ *
+ * @return 0 (success) or < 0 on error; -EOPNOTSUPP is returned if a different
+ * hash than lc_shake256 is used.
+ */
+int dilithium_87_sign_init(struct dilithium_ctx *ctx,
+ const struct dilithium_87_sk *sk);
+
+/**
+ * @brief Add more data to an already initialized signature state
+ *
+ * This call is intended to support messages that are located in non-contiguous
+ * places and even becomes available at different times. This call is to be
+ * used together with the dilithium_sign_init and dilithium_sign_final.
+ *
+ * @param [in] ctx pointer to Dilithium context that was initialized with
+ * dilithium_sign_init
+ * @param [in] m pointer to message to be signed
+ * @param [in] mlen length of message
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_87_sign_update(struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen);
+
+/**
+ * @brief Computes signature
+ *
+ * @param [out] sig pointer to output signature
+ * @param [in] ctx pointer to Dilithium context that was initialized with
+ * dilithium_sign_init and filled with
+ * dilithium_sign_update
+ * @param [in] sk pointer to bit-packed secret key
+ * @param [in] rng_ctx pointer to seeded random number generator context - when
+ * pointer is non-NULL, perform a randomized signing.
+ * Otherwise use deterministic signing.
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_87_sign_final(struct dilithium_87_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_87_sk *sk,
+ struct crypto_rng *rng_ctx);
+
+/**
+ * @brief Verifies ML-DSA signature in one shot
+ *
+ * @param [in] sig pointer to input signature
+ * @param [in] m pointer to message
+ * @param [in] mlen length of message
+ * @param [in] pk pointer to bit-packed public key
+ *
+ * @return 0 if signature could be verified correctly and -EBADMSG when
+ * signature cannot be verified, < 0 on other errors
+ */
+int dilithium_87_verify(const struct dilithium_87_sig *sig, const uint8_t *m,
+ size_t mlen, const struct dilithium_87_pk *pk);
+
+/**
+ * @brief Verifies signature with Dilithium context in one shot
+ *
+ * This API allows the caller to provide an arbitrary context buffer which
+ * is hashed together with the message to form the message digest to be signed.
+ *
+ * @param [in] sig pointer to input signature
+ * @param [in] ctx reference to the allocated Dilithium context handle
+ * @param [in] m pointer to message
+ * @param [in] mlen length of message
+ * @param [in] pk pointer to bit-packed public key
+ *
+ * @return 0 if signature could be verified correctly and -EBADMSG when
+ * signature cannot be verified, < 0 on other errors
+ */
+int dilithium_87_verify_ctx(const struct dilithium_87_sig *sig,
+ struct dilithium_ctx *ctx,
+ const uint8_t *m, size_t mlen,
+ const struct dilithium_87_pk *pk);
+
+/**
+ * @brief Initializes a signature verification operation
+ *
+ * This call is intended to support messages that are located in non-contiguous
+ * places and even becomes available at different times. This call is to be
+ * used together with the dilithium_verify_update and
+ * dilithium_verify_final.
+ *
+ * @param [in,out] ctx pointer to an allocated Dilithium context
+ * @param [in] pk pointer to bit-packed public key
+ *
+ * @return 0 (success) or < 0 on error; -EOPNOTSUPP is returned if a different
+ * hash than lc_shake256 is used.
+ */
+int dilithium_87_verify_init(struct dilithium_ctx *ctx,
+ const struct dilithium_87_pk *pk);
+
+/**
+ * @brief Add more data to an already initialized signature state
+ *
+ * This call is intended to support messages that are located in non-contiguous
+ * places and even becomes available at different times. This call is to be
+ * used together with the dilithium_verify_init and
+ * dilithium_verify_final.
+ *
+ * @param [in,out] ctx pointer to Dilithium context that was initialized with
+ * dilithium_sign_init
+ * @param [in] m pointer to message to be signed
+ * @param [in] mlen length of message
+ *
+ * @return 0 (success) or < 0 on error
+ */
+int dilithium_87_verify_update(struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen);
+
+/**
+ * @brief Verifies signature
+ *
+ * @param [in] sig pointer to output signature
+ * @param [in] ctx pointer to Dilithium context that was initialized with
+ * dilithium_sign_init and filled with
+ * dilithium_sign_update
+ * @param [in] pk pointer to bit-packed public key
+ *
+ * @return 0 if signature could be verified correctly and -EBADMSG when
+ * signature cannot be verified, < 0 on other errors
+ */
+int dilithium_87_verify_final(const struct dilithium_87_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_87_pk *pk);
+
+#endif /* __ASSEMBLER__ */
+
+#endif /* DILITHIUM_87_H */
diff --git a/crypto/ml_dsa/dilithium_api.c b/crypto/ml_dsa/dilithium_api.c
new file mode 100644
index 000000000000..363984f01169
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_api.c
@@ -0,0 +1,729 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#ifndef DILITHIUM_API_H
+#define DILITHIUM_API_H
+
+#include <linux/export.h>
+#include "dilithium.h"
+
+void dilithium_ctx_zero(struct dilithium_ctx *ctx)
+{
+ if (!ctx)
+ return;
+
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ dilithium_87_ctx_zero(ctx);
+#elif defined(CONFIG_CRYPTO_DILITHIUM_65)
+ dilithium_65_ctx_zero(ctx);
+#elif defined(CONFIG_CRYPTO_DILITHIUM_44)
+ dilithium_44_ctx_zero(ctx);
+#endif
+}
+EXPORT_SYMBOL(dilithium_ctx_zero);
+
+void dilithium_ctx_internal(struct dilithium_ctx *ctx)
+{
+ if (ctx)
+ ctx->ml_dsa_internal = 1;
+}
+EXPORT_SYMBOL(dilithium_ctx_internal);
+
+void dilithium_ctx_hash(struct dilithium_ctx *ctx,
+ struct crypto_shash *hash)
+{
+ if (ctx)
+ ctx->dilithium_prehash_type = hash;
+}
+EXPORT_SYMBOL(dilithium_ctx_hash);
+
+void dilithium_ctx_userctx(struct dilithium_ctx *ctx, const uint8_t *userctx,
+ size_t userctxlen)
+{
+ if (ctx) {
+ ctx->userctx = userctx;
+ ctx->userctxlen = userctxlen;
+ }
+}
+EXPORT_SYMBOL(dilithium_ctx_userctx);
+
+void dilithium_ctx_external_mu(struct dilithium_ctx *ctx, const uint8_t *external_mu,
+ size_t external_mu_len)
+{
+ if (ctx) {
+ ctx->external_mu = external_mu;
+ ctx->external_mu_len = external_mu_len;
+ }
+}
+EXPORT_SYMBOL(dilithium_ctx_external_mu);
+
+void dilithium_ctx_drop_ahat(struct dilithium_ctx *ctx)
+{
+ if (ctx)
+ ctx->ahat_expanded = 0;
+}
+EXPORT_SYMBOL(dilithium_ctx_drop_ahat);
+
+enum dilithium_type dilithium_sk_type(const struct dilithium_sk *sk)
+{
+ if (!sk)
+ return DILITHIUM_UNKNOWN;
+ return sk->dilithium_type;
+}
+EXPORT_SYMBOL(dilithium_sk_type);
+
+enum dilithium_type dilithium_pk_type(const struct dilithium_pk *pk)
+{
+ if (!pk)
+ return DILITHIUM_UNKNOWN;
+ return pk->dilithium_type;
+}
+EXPORT_SYMBOL(dilithium_pk_type);
+
+enum dilithium_type dilithium_sig_type(const struct dilithium_sig *sig)
+{
+ if (!sig)
+ return DILITHIUM_UNKNOWN;
+ return sig->dilithium_type;
+}
+EXPORT_SYMBOL(dilithium_sig_type);
+
+__pure unsigned int dilithium_sk_size(enum dilithium_type dilithium_type)
+{
+ switch (dilithium_type) {
+ case DILITHIUM_87:
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ return sizeof_field(struct dilithium_sk, key.sk_87);
+#else
+ return 0;
+#endif
+ case DILITHIUM_65:
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ return sizeof_field(struct dilithium_sk, key.sk_65);
+#else
+ return 0;
+#endif
+ case DILITHIUM_44:
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ return sizeof_field(struct dilithium_sk, key.sk_44);
+#else
+ return 0;
+#endif
+ case DILITHIUM_UNKNOWN:
+ default:
+ return 0;
+ }
+}
+EXPORT_SYMBOL(dilithium_sk_size);
+
+__pure unsigned int dilithium_pk_size(enum dilithium_type dilithium_type)
+{
+ switch (dilithium_type) {
+ case DILITHIUM_87:
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ return sizeof_field(struct dilithium_pk, key.pk_87);
+#else
+ return 0;
+#endif
+ case DILITHIUM_65:
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ return sizeof_field(struct dilithium_pk, key.pk_65);
+#else
+ return 0;
+#endif
+ case DILITHIUM_44:
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ return sizeof_field(struct dilithium_pk, key.pk_44);
+#else
+ return 0;
+#endif
+ case DILITHIUM_UNKNOWN:
+ default:
+ return 0;
+ }
+}
+EXPORT_SYMBOL(dilithium_pk_size);
+
+__pure unsigned int dilithium_sig_size(enum dilithium_type dilithium_type)
+{
+ switch (dilithium_type) {
+ case DILITHIUM_87:
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ return sizeof_field(struct dilithium_sig, sig.sig_87);
+#else
+ return 0;
+#endif
+ case DILITHIUM_65:
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ return sizeof_field(struct dilithium_sig, sig.sig_65);
+#else
+ return 0;
+#endif
+ case DILITHIUM_44:
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ return sizeof_field(struct dilithium_sig, sig.sig_44);
+#else
+ return 0;
+#endif
+ case DILITHIUM_UNKNOWN:
+ default:
+ return 0;
+ }
+}
+EXPORT_SYMBOL(dilithium_sig_size);
+
+int dilithium_sk_load(struct dilithium_sk *sk,
+ const uint8_t *src_key, size_t src_key_len)
+{
+ if (!sk || !src_key || src_key_len == 0) {
+ return -EINVAL;
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ } else if (src_key_len == dilithium_sk_size(DILITHIUM_87)) {
+ struct dilithium_87_sk *_sk = &sk->key.sk_87;
+
+ memcpy(_sk->sk, src_key, src_key_len);
+ sk->dilithium_type = DILITHIUM_87;
+ return 0;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ } else if (src_key_len == dilithium_sk_size(DILITHIUM_65)) {
+ struct dilithium_65_sk *_sk = &sk->key.sk_65;
+
+ memcpy(_sk->sk, src_key, src_key_len);
+ sk->dilithium_type = DILITHIUM_65;
+ return 0;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ } else if (src_key_len == dilithium_sk_size(DILITHIUM_44)) {
+ struct dilithium_44_sk *_sk = &sk->key.sk_44;
+
+ memcpy(_sk->sk, src_key, src_key_len);
+ sk->dilithium_type = DILITHIUM_44;
+ return 0;
+#endif
+ } else {
+ return -EINVAL;
+ }
+}
+EXPORT_SYMBOL(dilithium_sk_load);
+
+int dilithium_pk_load(struct dilithium_pk *pk,
+ const uint8_t *src_key, size_t src_key_len)
+{
+ if (!pk || !src_key || src_key_len == 0) {
+ return -EINVAL;
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ } else if (src_key_len == dilithium_pk_size(DILITHIUM_87)) {
+ struct dilithium_87_pk *_pk = &pk->key.pk_87;
+
+ memcpy(_pk->pk, src_key, src_key_len);
+ pk->dilithium_type = DILITHIUM_87;
+ return 0;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ } else if (src_key_len == dilithium_pk_size(DILITHIUM_65)) {
+ struct dilithium_65_pk *_pk = &pk->key.pk_65;
+
+ memcpy(_pk->pk, src_key, src_key_len);
+ pk->dilithium_type = DILITHIUM_65;
+ return 0;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ } else if (src_key_len == dilithium_pk_size(DILITHIUM_44)) {
+ struct dilithium_44_pk *_pk = &pk->key.pk_44;
+
+ memcpy(_pk->pk, src_key, src_key_len);
+ pk->dilithium_type = DILITHIUM_44;
+ return 0;
+#endif
+ } else {
+ return -EINVAL;
+ }
+}
+EXPORT_SYMBOL(dilithium_pk_load);
+
+int dilithium_sig_load(struct dilithium_sig *sig,
+ const uint8_t *src_sig, size_t src_sig_len)
+{
+ if (!sig || !src_sig || src_sig_len == 0) {
+ return -EINVAL;
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ } else if (src_sig_len == dilithium_sig_size(DILITHIUM_87)) {
+ struct dilithium_87_sig *_sig = &sig->sig.sig_87;
+
+ memcpy(_sig->sig, src_sig, src_sig_len);
+ sig->dilithium_type = DILITHIUM_87;
+ return 0;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ } else if (src_sig_len == dilithium_sig_size(DILITHIUM_65)) {
+ struct dilithium_65_sig *_sig = &sig->sig.sig_65;
+
+ memcpy(_sig->sig, src_sig, src_sig_len);
+ sig->dilithium_type = DILITHIUM_65;
+ return 0;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ } else if (src_sig_len == dilithium_sig_size(DILITHIUM_44)) {
+ struct dilithium_44_sig *_sig = &sig->sig.sig_44;
+
+ memcpy(_sig->sig, src_sig, src_sig_len);
+ sig->dilithium_type = DILITHIUM_44;
+ return 0;
+#endif
+ } else {
+ return -EINVAL;
+ }
+}
+EXPORT_SYMBOL(dilithium_sig_load);
+
+int dilithium_sk_ptr(uint8_t **dilithium_key,
+ size_t *dilithium_key_len, struct dilithium_sk *sk)
+{
+ if (!sk || !dilithium_key || !dilithium_key_len) {
+ return -EINVAL;
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ } else if (sk->dilithium_type == DILITHIUM_87) {
+ struct dilithium_87_sk *_sk = &sk->key.sk_87;
+
+ *dilithium_key = _sk->sk;
+ *dilithium_key_len = dilithium_sk_size(sk->dilithium_type);
+ return 0;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ } else if (sk->dilithium_type == DILITHIUM_65) {
+ struct dilithium_65_sk *_sk = &sk->key.sk_65;
+
+ *dilithium_key = _sk->sk;
+ *dilithium_key_len = dilithium_sk_size(sk->dilithium_type);
+ return 0;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ } else if (sk->dilithium_type == DILITHIUM_44) {
+ struct dilithium_44_sk *_sk = &sk->key.sk_44;
+
+ *dilithium_key = _sk->sk;
+ *dilithium_key_len = dilithium_sk_size(sk->dilithium_type);
+ return 0;
+#endif
+ } else {
+ return -EINVAL;
+ }
+}
+EXPORT_SYMBOL(dilithium_sk_ptr);
+
+int dilithium_pk_ptr(uint8_t **dilithium_key,
+ size_t *dilithium_key_len, struct dilithium_pk *pk)
+{
+ if (!pk || !dilithium_key || !dilithium_key_len) {
+ return -EINVAL;
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ } else if (pk->dilithium_type == DILITHIUM_87) {
+ struct dilithium_87_pk *_pk = &pk->key.pk_87;
+
+ *dilithium_key = _pk->pk;
+ *dilithium_key_len = dilithium_pk_size(pk->dilithium_type);
+ return 0;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ } else if (pk->dilithium_type == DILITHIUM_65) {
+ struct dilithium_65_pk *_pk = &pk->key.pk_65;
+
+ *dilithium_key = _pk->pk;
+ *dilithium_key_len = dilithium_pk_size(pk->dilithium_type);
+ return 0;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ } else if (pk->dilithium_type == DILITHIUM_44) {
+ struct dilithium_44_pk *_pk = &pk->key.pk_44;
+
+ *dilithium_key = _pk->pk;
+ *dilithium_key_len = dilithium_pk_size(pk->dilithium_type);
+ return 0;
+#endif
+ } else {
+ return -EINVAL;
+ }
+}
+EXPORT_SYMBOL(dilithium_pk_ptr);
+
+int dilithium_sig_ptr(uint8_t **dilithium_sig,
+ size_t *dilithium_sig_len, struct dilithium_sig *sig)
+{
+ if (!sig || !dilithium_sig || !dilithium_sig_len) {
+ return -EINVAL;
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ } else if (sig->dilithium_type == DILITHIUM_87) {
+ struct dilithium_87_sig *_sig = &sig->sig.sig_87;
+
+ *dilithium_sig = _sig->sig;
+ *dilithium_sig_len = dilithium_sig_size(sig->dilithium_type);
+ return 0;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ } else if (sig->dilithium_type == DILITHIUM_65) {
+ struct dilithium_65_sig *_sig = &sig->sig.sig_65;
+
+ *dilithium_sig = _sig->sig;
+ *dilithium_sig_len = dilithium_sig_size(sig->dilithium_type);
+ return 0;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ } else if (sig->dilithium_type == DILITHIUM_44) {
+ struct dilithium_44_sig *_sig = &sig->sig.sig_44;
+
+ *dilithium_sig = _sig->sig;
+ *dilithium_sig_len = dilithium_sig_size(sig->dilithium_type);
+ return 0;
+#endif
+ } else {
+ return -EINVAL;
+ }
+}
+EXPORT_SYMBOL(dilithium_sig_ptr);
+
+int dilithium_sign(struct dilithium_sig *sig,
+ const uint8_t *m, size_t mlen,
+ const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx)
+{
+ if (!sk || !sig)
+ return -EINVAL;
+
+ switch (sk->dilithium_type) {
+ case DILITHIUM_87:
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ sig->dilithium_type = DILITHIUM_87;
+ return dilithium_87_sign(&sig->sig.sig_87, m, mlen,
+ &sk->key.sk_87, rng_ctx);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_65:
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ sig->dilithium_type = DILITHIUM_65;
+ return dilithium_65_sign(&sig->sig.sig_65, m, mlen,
+ &sk->key.sk_65, rng_ctx);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_44:
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ sig->dilithium_type = DILITHIUM_44;
+ return dilithium_44_sign(&sig->sig.sig_44, m, mlen,
+ &sk->key.sk_44, rng_ctx);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_UNKNOWN:
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+EXPORT_SYMBOL(dilithium_sign);
+
+int dilithium_sign_ctx(struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen, const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx)
+{
+ if (!sk || !sig)
+ return -EINVAL;
+
+ switch (sk->dilithium_type) {
+ case DILITHIUM_87:
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ sig->dilithium_type = DILITHIUM_87;
+ return dilithium_87_sign_ctx(&sig->sig.sig_87, ctx, m, mlen,
+ &sk->key.sk_87, rng_ctx);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_65:
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ sig->dilithium_type = DILITHIUM_65;
+ return dilithium_65_sign_ctx(&sig->sig.sig_65, ctx, m, mlen,
+ &sk->key.sk_65, rng_ctx);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_44:
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ sig->dilithium_type = DILITHIUM_44;
+ return dilithium_44_sign_ctx(&sig->sig.sig_44, ctx, m, mlen,
+ &sk->key.sk_44, rng_ctx);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_UNKNOWN:
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+EXPORT_SYMBOL(dilithium_sign_ctx);
+
+int dilithium_sign_init(struct dilithium_ctx *ctx,
+ const struct dilithium_sk *sk)
+{
+ if (!sk)
+ return -EINVAL;
+
+ switch (sk->dilithium_type) {
+ case DILITHIUM_87:
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ return dilithium_87_sign_init(ctx, &sk->key.sk_87);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_65:
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ return dilithium_65_sign_init(ctx, &sk->key.sk_65);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_44:
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ return dilithium_44_sign_init(ctx, &sk->key.sk_44);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_UNKNOWN:
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+EXPORT_SYMBOL(dilithium_sign_init);
+
+int dilithium_sign_update(struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen)
+{
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ return dilithium_87_sign_update(ctx, m, mlen);
+#elif defined(CONFIG_CRYPTO_DILITHIUM_65)
+ return dilithium_65_sign_update(ctx, m, mlen);
+#elif defined(CONFIG_CRYPTO_DILITHIUM_44)
+ return dilithium_44_sign_update(ctx, m, mlen);
+#else
+ return -EOPNOTSUPP;
+#endif
+}
+EXPORT_SYMBOL(dilithium_sign_update);
+
+int dilithium_sign_final(struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx)
+{
+ if (!sk || !sig)
+ return -EINVAL;
+
+ switch (sk->dilithium_type) {
+ case DILITHIUM_87:
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ sig->dilithium_type = DILITHIUM_87;
+ return dilithium_87_sign_final(&sig->sig.sig_87, ctx,
+ &sk->key.sk_87, rng_ctx);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_65:
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ sig->dilithium_type = DILITHIUM_65;
+ return dilithium_65_sign_final(&sig->sig.sig_65, ctx,
+ &sk->key.sk_65, rng_ctx);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_44:
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ sig->dilithium_type = DILITHIUM_44;
+ return dilithium_44_sign_final(&sig->sig.sig_44, ctx,
+ &sk->key.sk_44, rng_ctx);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_UNKNOWN:
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+EXPORT_SYMBOL(dilithium_sign_final);
+
+int dilithium_verify(const struct dilithium_sig *sig, const uint8_t *m,
+ size_t mlen, const struct dilithium_pk *pk)
+{
+ if (!pk || !sig || sig->dilithium_type != pk->dilithium_type)
+ return -EINVAL;
+
+ switch (pk->dilithium_type) {
+ case DILITHIUM_87:
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ return dilithium_87_verify(&sig->sig.sig_87, m, mlen,
+ &pk->key.pk_87);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_65:
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ return dilithium_65_verify(&sig->sig.sig_65, m, mlen,
+ &pk->key.pk_65);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_44:
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ return dilithium_44_verify(&sig->sig.sig_44, m, mlen,
+ &pk->key.pk_44);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_UNKNOWN:
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+EXPORT_SYMBOL(dilithium_verify);
+
+int dilithium_verify_ctx(const struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen, const struct dilithium_pk *pk)
+{
+ if (!pk || !sig || sig->dilithium_type != pk->dilithium_type)
+ return -EINVAL;
+
+ switch (pk->dilithium_type) {
+ case DILITHIUM_87:
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ return dilithium_87_verify_ctx(&sig->sig.sig_87, ctx, m,
+ mlen, &pk->key.pk_87);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_65:
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ return dilithium_65_verify_ctx(&sig->sig.sig_65, ctx, m,
+ mlen, &pk->key.pk_65);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_44:
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ return dilithium_44_verify_ctx(&sig->sig.sig_44, ctx, m,
+ mlen, &pk->key.pk_44);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_UNKNOWN:
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+EXPORT_SYMBOL(dilithium_verify_ctx);
+
+int dilithium_verify_init(struct dilithium_ctx *ctx,
+ const struct dilithium_pk *pk)
+{
+ if (!pk)
+ return -EINVAL;
+
+ switch (pk->dilithium_type) {
+ case DILITHIUM_87:
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ return dilithium_87_verify_init(ctx, &pk->key.pk_87);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_65:
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ return dilithium_65_verify_init(ctx, &pk->key.pk_65);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_44:
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ return dilithium_44_verify_init(ctx, &pk->key.pk_44);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_UNKNOWN:
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+EXPORT_SYMBOL(dilithium_verify_init);
+
+int dilithium_verify_update(struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen)
+{
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ return dilithium_87_verify_update(ctx, m, mlen);
+#elif defined(CONFIG_CRYPTO_DILITHIUM_65)
+ return dilithium_65_verify_update(ctx, m, mlen);
+#elif defined(CONFIG_CRYPTO_DILITHIUM_44)
+ return dilithium_44_verify_update(ctx, m, mlen);
+#else
+ return -EOPNOTSUPP;
+#endif
+}
+EXPORT_SYMBOL(dilithium_verify_update);
+
+int dilithium_verify_final(const struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_pk *pk)
+{
+ if (!pk || !sig || sig->dilithium_type != pk->dilithium_type)
+ return -EINVAL;
+
+ switch (pk->dilithium_type) {
+ case DILITHIUM_87:
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ return dilithium_87_verify_final(&sig->sig.sig_87, ctx,
+ &pk->key.pk_87);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_65:
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ return dilithium_65_verify_final(&sig->sig.sig_65, ctx,
+ &pk->key.pk_65);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_44:
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ return dilithium_44_verify_final(&sig->sig.sig_44, ctx,
+ &pk->key.pk_44);
+#else
+ return -EOPNOTSUPP;
+#endif
+ case DILITHIUM_UNKNOWN:
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+EXPORT_SYMBOL(dilithium_verify_final);
+
+#endif /* DILITHIUM_API_H */
diff --git a/crypto/ml_dsa/dilithium_debug.h b/crypto/ml_dsa/dilithium_debug.h
new file mode 100644
index 000000000000..9448c252175f
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_debug.h
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2023 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef DILITHIUM_DEBUG_H
+#define DILITHIUM_DEBUG_H
+
+#include "dilithium_type.h"
+
+#ifdef DILITHIUM_DEBUG
+
+/* Disable selftests */
+#define DILITHIUM_TEST_INIT 1
+
+void dilithium_print_buffer(const uint8_t *buffer, const size_t bufferlen,
+ const char *explanation);
+void dilithium_print_polyvecl_k(polyvecl mat[DILITHIUM_K],
+ const char *explanation);
+void dilithium_print_polyvecl(polyvecl *polyvec, const char *explanation);
+void dilithium_print_polyveck(polyveck *polyvec, const char *explanation);
+void dilithium_print_poly(poly *vec, const char *explanation);
+
+#else /* DILITHIUM_DEBUG */
+
+/* Enable selftests */
+#define DILITHIUM_TEST_INIT 0
+
+static inline void dilithium_print_buffer(const uint8_t *buffer,
+ const size_t bufferlen,
+ const char *explanation)
+{
+ (void)buffer;
+ (void)bufferlen;
+ (void)explanation;
+}
+
+static inline void dilithium_print_polyvecl_k(polyvecl mat[DILITHIUM_K],
+ const char *explanation)
+{
+ (void)mat;
+ (void)explanation;
+}
+
+static inline void dilithium_print_polyvecl(polyvecl *polyvec,
+ const char *explanation)
+{
+ (void)polyvec;
+ (void)explanation;
+}
+
+static inline void dilithium_print_polyveck(polyveck *polyvec,
+ const char *explanation)
+{
+ (void)polyvec;
+ (void)explanation;
+}
+
+static inline void dilithium_print_poly(poly *vec, const char *explanation)
+{
+ (void)vec;
+ (void)explanation;
+}
+
+#endif /* DILITHIUM_DEBUG */
+
+#endif /* DILITHIUM_DEBUG_H */
diff --git a/crypto/ml_dsa/dilithium_ntt.c b/crypto/ml_dsa/dilithium_ntt.c
new file mode 100644
index 000000000000..839f4cb8a0aa
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_ntt.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#include "dilithium_ntt.h"
+#include "dilithium_reduce.h"
+
+/**
+ * @brief ntt - Forward NTT, in-place. No modular reduction is performed after
+ * additions or subtractions. Output vector is in bitreversed
+ * order.
+ *
+ * @param [in,out] p input/output coefficient array
+ */
+void ntt(int32_t a[DILITHIUM_N])
+{
+ unsigned int len, start, j, k;
+ int32_t zeta, t;
+
+ k = 0;
+
+ for (len = 128; len > 0; len >>= 1) {
+ for (start = 0; start < DILITHIUM_N; start = j + len) {
+ zeta = dilithium_zetas[++k];
+ for (j = start; j < start + len; ++j) {
+ t = montgomery_reduce((int64_t)zeta *
+ a[j + len]);
+ a[j + len] = a[j] - t;
+ a[j] = a[j] + t;
+ }
+ }
+ }
+}
+
+/**
+ * @brief invntt_tomont - Inverse NTT and multiplication by Montgomery factor
+ * 2^32. In-place. No modular reductions after additions
+ * or subtractions; input coefficients need to be smaller
+ * than Q in absolute value. Output coefficient are
+ * smaller than Q in absolute value.
+ *
+ * @param [in,out] p input/output coefficient array
+ */
+void invntt_tomont(int32_t a[DILITHIUM_N])
+{
+ unsigned int start, len, j, k;
+ int32_t t, zeta;
+ const int32_t f = 41978; // mont^2/256
+
+ k = 256;
+
+ for (len = 1; len < DILITHIUM_N; len <<= 1) {
+ for (start = 0; start < DILITHIUM_N; start = j + len) {
+ zeta = -dilithium_zetas[--k];
+ for (j = start; j < start + len; ++j) {
+ t = a[j];
+ a[j] = t + a[j + len];
+ a[j + len] = t - a[j + len];
+ a[j + len] = montgomery_reduce((int64_t)zeta *
+ a[j + len]);
+ }
+ }
+ }
+
+ for (j = 0; j < DILITHIUM_N; ++j)
+ a[j] = montgomery_reduce((int64_t)f * a[j]);
+}
diff --git a/crypto/ml_dsa/dilithium_ntt.h b/crypto/ml_dsa/dilithium_ntt.h
new file mode 100644
index 000000000000..5dfea3a936cd
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_ntt.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#ifndef DILITHIUM_NTT_H
+#define DILITHIUM_NTT_H
+
+#include "dilithium_type.h"
+
+void ntt(int32_t a[DILITHIUM_N]);
+void invntt_tomont(int32_t a[DILITHIUM_N]);
+
+#endif /* DILITHIUM_NTT_H */
diff --git a/crypto/ml_dsa/dilithium_pack.h b/crypto/ml_dsa/dilithium_pack.h
new file mode 100644
index 000000000000..e17c6256a16d
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_pack.h
@@ -0,0 +1,210 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#ifndef DILITHIUM_PACK_H
+#define DILITHIUM_PACK_H
+
+/*******************************************************************************
+ * Pack / Unpack public key
+ ******************************************************************************/
+
+static inline void unpack_pk_rho(uint8_t rho[DILITHIUM_SEEDBYTES],
+ const struct dilithium_pk *pk)
+{
+ memcpy(rho, pk->pk, DILITHIUM_SEEDBYTES);
+}
+
+static inline void unpack_pk_t1(polyveck *t1, const struct dilithium_pk *pk)
+{
+ unsigned int i;
+ const uint8_t *pubkey = pk->pk + DILITHIUM_SEEDBYTES;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ polyt1_unpack(&t1->vec[i],
+ pubkey + i * DILITHIUM_POLYT1_PACKEDBYTES);
+}
+
+/*******************************************************************************
+ * Pack / Unpack secret key
+ ******************************************************************************/
+static inline void unpack_sk_key(uint8_t key[DILITHIUM_SEEDBYTES],
+ const struct dilithium_sk *sk)
+{
+ memcpy(key, sk->sk + DILITHIUM_SEEDBYTES, DILITHIUM_SEEDBYTES);
+}
+
+static inline void unpack_sk_tr(uint8_t tr[DILITHIUM_TRBYTES],
+ const struct dilithium_sk *sk)
+{
+ memcpy(tr, sk->sk + 2 * DILITHIUM_SEEDBYTES, DILITHIUM_TRBYTES);
+}
+
+static inline void unpack_sk_s1(polyvecl *s1, const struct dilithium_sk *sk)
+{
+ unsigned int i;
+ const uint8_t *seckey =
+ sk->sk + 2 * DILITHIUM_SEEDBYTES + DILITHIUM_TRBYTES;
+
+ for (i = 0; i < DILITHIUM_L; ++i)
+ polyeta_unpack(&s1->vec[i],
+ seckey + i * DILITHIUM_POLYETA_PACKEDBYTES);
+}
+
+static inline void unpack_sk_s2(polyveck *s2, const struct dilithium_sk *sk)
+{
+ unsigned int i;
+ const uint8_t *seckey =
+ sk->sk + 2 * DILITHIUM_SEEDBYTES + DILITHIUM_TRBYTES +
+ DILITHIUM_L * DILITHIUM_POLYETA_PACKEDBYTES;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ polyeta_unpack(&s2->vec[i],
+ seckey + i * DILITHIUM_POLYETA_PACKEDBYTES);
+}
+
+static inline void unpack_sk_t0(polyveck *t0, const struct dilithium_sk *sk)
+{
+ unsigned int i;
+ const uint8_t *seckey =
+ sk->sk + 2 * DILITHIUM_SEEDBYTES + DILITHIUM_TRBYTES +
+ DILITHIUM_L * DILITHIUM_POLYETA_PACKEDBYTES +
+ DILITHIUM_K * DILITHIUM_POLYETA_PACKEDBYTES;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ polyt0_unpack(&t0->vec[i],
+ seckey + i * DILITHIUM_POLYT0_PACKEDBYTES);
+}
+
+/**
+ * @brief pack_sig - Bit-pack signature sig = (c, z, h).
+ *
+ * NOTE: A signature is the concatenation of sig = (c || packed z || packed h).
+ * As c is already present in the first bytes of sig, this function does
+ * not need to copy it yet again to the right location. This implies that
+ * this function does not process c.
+ *
+ * @param [out] sig signature
+ * @param [in] z pointer to vector z
+ * @param [in] h pointer to hint vector h
+ */
+static inline void pack_sig(struct dilithium_sig *sig, const polyvecl *z,
+ const polyveck *h)
+{
+ unsigned int i, j, k;
+ /* Skip c */
+ uint8_t *signature = sig->sig + DILITHIUM_CTILDE_BYTES;
+
+ BUILD_BUG_ON((1ULL << (sizeof(j) << 3)) < DILITHIUM_N);
+ BUILD_BUG_ON((1ULL << (sizeof(k) << 3)) < DILITHIUM_N);
+
+ for (i = 0; i < DILITHIUM_L; ++i)
+ polyz_pack(signature + i * DILITHIUM_POLYZ_PACKEDBYTES,
+ &z->vec[i]);
+ signature += DILITHIUM_L * DILITHIUM_POLYZ_PACKEDBYTES;
+
+ /* Encode h */
+ memset(signature, 0, DILITHIUM_OMEGA + DILITHIUM_K);
+
+ k = 0;
+ for (i = 0; i < DILITHIUM_K; ++i) {
+ for (j = 0; j < DILITHIUM_N; ++j)
+ if (h->vec[i].coeffs[j] != 0)
+ signature[k++] = (uint8_t)j;
+
+ signature[DILITHIUM_OMEGA + i] = (uint8_t)k;
+ }
+}
+
+/**
+ * @brief unpack_sig_z - Unpack z part of signature sig = (c, z, h).
+ *
+ * NOTE: The c value is not unpacked as it can be used right from the signature.
+ * To access it, a caller simply needs to use the first
+ * DILITHIUM_CTILDE_BYTES of the signature.
+ *
+ * @param [out] z pointer to output vector z
+ * @param [in] sig signature
+ */
+static inline void unpack_sig_z(polyvecl *z, const struct dilithium_sig *sig)
+{
+ unsigned int i;
+ /* Skip c */
+ const uint8_t *signature = sig->sig + DILITHIUM_CTILDE_BYTES;
+
+ for (i = 0; i < DILITHIUM_L; ++i)
+ polyz_unpack(&z->vec[i],
+ signature + i * DILITHIUM_POLYZ_PACKEDBYTES);
+}
+
+/**
+ * @brief unpack_sig - Unpack h value of signature sig = (c, z, h).
+ *
+ * NOTE: The c value is not unpacked as it can be used right from the signature.
+ * To access it, a caller simply needs to use the first
+ * DILITHIUM_CTILDE_BYTES of the signature.
+ *
+ * @param [out] h pointer to output hint vector h
+ * @param [in] sig signature
+ *
+ * @return 1 in case of malformed signature; otherwise 0.
+ */
+static inline int unpack_sig_h(polyveck *h, const struct dilithium_sig *sig)
+{
+ unsigned int i, j, k;
+ /* Skip c */
+ const uint8_t *signature =
+ sig->sig + DILITHIUM_CTILDE_BYTES +
+ DILITHIUM_L * DILITHIUM_POLYZ_PACKEDBYTES;
+
+ /* Decode h */
+ k = 0;
+ for (i = 0; i < DILITHIUM_K; ++i) {
+ for (j = 0; j < DILITHIUM_N; ++j)
+ h->vec[i].coeffs[j] = 0;
+
+ if (signature[DILITHIUM_OMEGA + i] < k ||
+ signature[DILITHIUM_OMEGA + i] > DILITHIUM_OMEGA)
+ return 1;
+
+ for (j = k; j < signature[DILITHIUM_OMEGA + i]; ++j) {
+ /* Coefficients are ordered for strong unforgeability */
+ if (j > k && signature[j] <= signature[j - 1])
+ return 1;
+ h->vec[i].coeffs[signature[j]] = 1;
+ }
+
+ k = signature[DILITHIUM_OMEGA + i];
+ }
+
+ /* Extra indices are zero for strong unforgeability */
+ for (j = k; j < DILITHIUM_OMEGA; ++j)
+ if (signature[j])
+ return 1;
+
+ return 0;
+}
+
+#endif /* DILITHIUM_PACK_H */
diff --git a/crypto/ml_dsa/dilithium_poly.c b/crypto/ml_dsa/dilithium_poly.c
new file mode 100644
index 000000000000..45ce07d2d4fb
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_poly.c
@@ -0,0 +1,586 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#include "dilithium_poly.h"
+#include "dilithium_poly_common.h"
+#include "dilithium_service_helpers.h"
+#include <crypto/sha3.h>
+
+/**
+ * @brief poly_chknorm - Check infinity norm of polynomial against given bound.
+ * Assumes input coefficients were reduced by reduce32().
+ *
+ * @param [in] a pointer to polynomial
+ * @param [in] B norm bound
+ *
+ * @return 0 if norm is strictly smaller than B <= (Q-1)/8 and 1 otherwise.
+ */
+int poly_chknorm(const poly *a, int32_t B)
+{
+ unsigned int i;
+ int32_t t;
+
+ if (B > (DILITHIUM_Q - 1) / 8)
+ return 1;
+
+ /*
+ * It is ok to leak which coefficient violates the bound since
+ * the probability for each coefficient *is independent of secret
+ * data but we must not leak the sign of the centralized representative.
+ */
+ for (i = 0; i < DILITHIUM_N; ++i) {
+ /* Absolute value */
+ t = a->coeffs[i] >> 31;
+ t = a->coeffs[i] - (t & 2 * a->coeffs[i]);
+
+ if (t >= B)
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * @brief poly_uniform - Sample polynomial with uniformly random coefficients
+ * in [0,Q-1] by performing rejection sampling on the
+ * output stream of SHAKE128(seed|nonce).
+ *
+ * @param [out] a pointer to output polynomial
+ * @param [in] seed byte array with seed of length DILITHIUM_SEEDBYTES
+ * @param [in] nonce 2-byte nonce
+ */
+void poly_uniform(poly *a, const uint8_t seed[DILITHIUM_SEEDBYTES],
+ uint16_t nonce, void *ws_buf)
+{
+ struct shake128_ctx hash_ctx;
+ unsigned int i, ctr, off;
+ unsigned int buflen = POLY_UNIFORM_NBLOCKS * SHAKE128_BLOCK_SIZE;
+ uint8_t *buf = ws_buf;
+
+ shake128_init(&hash_ctx);
+ shake128_update(&hash_ctx, seed, DILITHIUM_SEEDBYTES);
+ shake128_update(&hash_ctx, (uint8_t *)&nonce, sizeof(nonce));
+ shake128_squeeze(&hash_ctx, buf, buflen);
+
+ ctr = rej_uniform(a->coeffs, DILITHIUM_N, buf, buflen);
+
+ while (ctr < DILITHIUM_N) {
+ off = buflen % 3;
+ for (i = 0; i < off; ++i)
+ buf[i] = buf[buflen - off + i];
+
+ shake128_squeeze(&hash_ctx, buf + off, SHAKE128_BLOCK_SIZE);
+ buflen = DILITHIUM_SEEDBYTES + off;
+ ctr += rej_uniform(a->coeffs + ctr, DILITHIUM_N - ctr, buf,
+ buflen);
+ }
+
+ shake128_clear(&hash_ctx);
+}
+
+/**
+ * @brief poly_uniform_eta - Sample polynomial with uniformly random
+ * coefficients in [-ETA,ETA] by performing rejection
+ * sampling on the output stream from
+ * SHAKE256(seed|nonce).
+ *
+ * @param [out] a pointer to output polynomial
+ * @param [in] seed byte array with seed of length DILITHIUM_CRHBYTES
+ * @param [in] nonce 2-byte nonce
+ */
+void poly_uniform_eta(poly *a, const uint8_t seed[DILITHIUM_CRHBYTES],
+ uint16_t nonce, void *ws_buf)
+{
+ struct shake256_ctx hash_ctx;
+ unsigned int ctr;
+ uint8_t *buf = ws_buf;
+
+ shake256_init(&hash_ctx);
+ shake256_update(&hash_ctx, seed, DILITHIUM_CRHBYTES);
+ shake256_update(&hash_ctx, (uint8_t *)&nonce, sizeof(nonce));
+ shake256_squeeze(&hash_ctx, buf, POLY_UNIFORM_ETA_BYTES);
+
+ ctr = rej_eta(a->coeffs, DILITHIUM_N, buf, POLY_UNIFORM_ETA_BYTES);
+
+ while (ctr < DILITHIUM_N) {
+ shake256_squeeze(&hash_ctx, buf, POLY_UNIFORM_ETA_BYTES);
+
+ ctr += rej_eta(a->coeffs + ctr, DILITHIUM_N - ctr, buf,
+ SHAKE256_BLOCK_SIZE);
+ }
+
+ shake256_clear(&hash_ctx);
+}
+
+/**
+ * @brief poly_uniform_gamma1 - Sample polynomial with uniformly random
+ * coefficients in [-(GAMMA1 - 1), GAMMA1] by
+ * unpacking output stream of
+ * SHAKE256(seed|nonce).
+ *
+ * @param [out] a pointer to output polynomial
+ * @param [in] seed: byte array with seed of length DILITHIUM_CRHBYTES
+ * @param nonce 16-bit nonce
+ */
+void poly_uniform_gamma1(poly *a, const uint8_t seed[DILITHIUM_CRHBYTES],
+ uint16_t nonce, void *ws_buf)
+{
+ struct shake256_ctx hash_ctx;
+
+ shake256_init(&hash_ctx);
+ shake256_update(&hash_ctx, seed, DILITHIUM_CRHBYTES);
+ shake256_update(&hash_ctx, (uint8_t *)&nonce, sizeof(nonce));
+ shake256_squeeze(&hash_ctx, ws_buf, POLY_UNIFORM_GAMMA1_BYTES);
+ shake256_clear(&hash_ctx);
+
+ polyz_unpack(a, ws_buf);
+}
+
+/**
+ * @brief poly_challenge - Implementation of H. Samples polynomial with TAU
+ * nonzero coefficients in {-1,1} using the output
+ * stream of SHAKE256(seed).
+ *
+ * @param [out] c pointer to output polynomial
+ * @param [in] mu byte array containing seed of length DILITHIUM_CTILDE_BYTES
+ */
+void poly_challenge(poly *c, const uint8_t seed[DILITHIUM_CTILDE_BYTES],
+ void *ws_buf)
+{
+ struct shake256_ctx hash_ctx;
+ unsigned int i, b, pos;
+ uint64_t signs;
+ uint8_t *buf = ws_buf;
+
+ shake256_init(&hash_ctx);
+ shake256_update(&hash_ctx, seed, DILITHIUM_CTILDE_BYTES);
+ shake256_squeeze(&hash_ctx, buf, POLY_CHALLENGE_BYTES);
+
+ signs = 0;
+ for (i = 0; i < 8; ++i)
+ signs |= (uint64_t)buf[i] << 8 * i;
+ pos = 8;
+
+ for (i = 0; i < DILITHIUM_N; ++i)
+ c->coeffs[i] = 0;
+
+ for (i = DILITHIUM_N - DILITHIUM_TAU; i < DILITHIUM_N; ++i) {
+ do {
+ if (pos >= SHAKE256_BLOCK_SIZE) {
+ shake256_squeeze(&hash_ctx, buf, POLY_CHALLENGE_BYTES);
+ pos = 0;
+ }
+
+ b = buf[pos++];
+ } while (b > i);
+
+ c->coeffs[i] = c->coeffs[b];
+ c->coeffs[b] = 1 - (int32_t)(2 * (signs & 1));
+ signs >>= 1;
+ }
+
+ shake256_clear(&hash_ctx);
+}
+
+/**
+ * @brief polyeta_pack - Bit-pack polynomial with coefficients in [-ETA,ETA].
+ *
+ * @param [out] r pointer to output byte array with at least
+ * DILITHIUM_POLYETA_PACKEDBYTES bytes
+ * @param [in] a pointer to input polynomial
+ */
+void polyeta_pack(uint8_t *r, const poly *a)
+{
+ unsigned int i;
+ uint8_t t[8];
+
+#if DILITHIUM_ETA == 2
+ for (i = 0; i < DILITHIUM_N / 8; ++i) {
+ t[0] = (uint8_t)(DILITHIUM_ETA - a->coeffs[8 * i + 0]);
+ t[1] = (uint8_t)(DILITHIUM_ETA - a->coeffs[8 * i + 1]);
+ t[2] = (uint8_t)(DILITHIUM_ETA - a->coeffs[8 * i + 2]);
+ t[3] = (uint8_t)(DILITHIUM_ETA - a->coeffs[8 * i + 3]);
+ t[4] = (uint8_t)(DILITHIUM_ETA - a->coeffs[8 * i + 4]);
+ t[5] = (uint8_t)(DILITHIUM_ETA - a->coeffs[8 * i + 5]);
+ t[6] = (uint8_t)(DILITHIUM_ETA - a->coeffs[8 * i + 6]);
+ t[7] = (uint8_t)(DILITHIUM_ETA - a->coeffs[8 * i + 7]);
+
+ r[3 * i + 0] =
+ (uint8_t)((t[0] >> 0) | (t[1] << 3) | (t[2] << 6));
+ r[3 * i + 1] = (uint8_t)((t[2] >> 2) | (t[3] << 1) |
+ (t[4] << 4) | (t[5] << 7));
+ r[3 * i + 2] =
+ (uint8_t)((t[5] >> 1) | (t[6] << 2) | (t[7] << 5));
+ }
+#elif DILITHIUM_ETA == 4
+ for (i = 0; i < DILITHIUM_N / 2; ++i) {
+ t[0] = (uint8_t)(DILITHIUM_ETA - a->coeffs[2 * i + 0]);
+ t[1] = (uint8_t)(DILITHIUM_ETA - a->coeffs[2 * i + 1]);
+ r[i] = (uint8_t)(t[0] | (t[1] << 4));
+ }
+#else
+#error "Undefined DILITHIUM_ETA"
+#endif
+}
+
+/**
+ * @brief polyeta_unpack - Unpack polynomial with coefficients in [-ETA,ETA].
+ *
+ * @param [out] r pointer to output polynomial
+ * @param [in] a byte array with bit-packed polynomial
+ */
+void polyeta_unpack(poly *r, const uint8_t *a)
+{
+ unsigned int i;
+
+#if DILITHIUM_ETA == 2
+ for (i = 0; i < DILITHIUM_N / 8; ++i) {
+ r->coeffs[8 * i + 0] = (a[3 * i + 0] >> 0) & 7;
+ r->coeffs[8 * i + 1] = (a[3 * i + 0] >> 3) & 7;
+ r->coeffs[8 * i + 2] =
+ ((a[3 * i + 0] >> 6) | (a[3 * i + 1] << 2)) & 7;
+ r->coeffs[8 * i + 3] = (a[3 * i + 1] >> 1) & 7;
+ r->coeffs[8 * i + 4] = (a[3 * i + 1] >> 4) & 7;
+ r->coeffs[8 * i + 5] =
+ ((a[3 * i + 1] >> 7) | (a[3 * i + 2] << 1)) & 7;
+ r->coeffs[8 * i + 6] = (a[3 * i + 2] >> 2) & 7;
+ r->coeffs[8 * i + 7] = (a[3 * i + 2] >> 5) & 7;
+
+ r->coeffs[8 * i + 0] = DILITHIUM_ETA - r->coeffs[8 * i + 0];
+ r->coeffs[8 * i + 1] = DILITHIUM_ETA - r->coeffs[8 * i + 1];
+ r->coeffs[8 * i + 2] = DILITHIUM_ETA - r->coeffs[8 * i + 2];
+ r->coeffs[8 * i + 3] = DILITHIUM_ETA - r->coeffs[8 * i + 3];
+ r->coeffs[8 * i + 4] = DILITHIUM_ETA - r->coeffs[8 * i + 4];
+ r->coeffs[8 * i + 5] = DILITHIUM_ETA - r->coeffs[8 * i + 5];
+ r->coeffs[8 * i + 6] = DILITHIUM_ETA - r->coeffs[8 * i + 6];
+ r->coeffs[8 * i + 7] = DILITHIUM_ETA - r->coeffs[8 * i + 7];
+ }
+#elif DILITHIUM_ETA == 4
+ for (i = 0; i < DILITHIUM_N / 2; ++i) {
+ r->coeffs[2 * i + 0] = a[i] & 0x0F;
+ r->coeffs[2 * i + 1] = a[i] >> 4;
+ r->coeffs[2 * i + 0] = DILITHIUM_ETA - r->coeffs[2 * i + 0];
+ r->coeffs[2 * i + 1] = DILITHIUM_ETA - r->coeffs[2 * i + 1];
+ }
+#else
+#error "Undefined DILITHIUM_ETA"
+#endif
+}
+
+/**
+ * @brief polyt1_pack - Bit-pack polynomial t1 with coefficients fitting in 10
+ * bits. Input coefficients are assumed to be standard
+ * representatives.
+ *
+ * @param [out] r pointer to output byte array with at least
+ * DILITHIUM_POLYT1_PACKEDBYTES bytes
+ * @param [in] a pointer to input polynomial
+ */
+void polyt1_pack(uint8_t *r, const poly *a)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_N / 4; ++i) {
+ r[5 * i + 0] = (uint8_t)((a->coeffs[4 * i + 0] >> 0));
+ r[5 * i + 1] = (uint8_t)((a->coeffs[4 * i + 0] >> 8) |
+ (a->coeffs[4 * i + 1] << 2));
+ r[5 * i + 2] = (uint8_t)((a->coeffs[4 * i + 1] >> 6) |
+ (a->coeffs[4 * i + 2] << 4));
+ r[5 * i + 3] = (uint8_t)((a->coeffs[4 * i + 2] >> 4) |
+ (a->coeffs[4 * i + 3] << 6));
+ r[5 * i + 4] = (uint8_t)((a->coeffs[4 * i + 3] >> 2));
+ }
+}
+
+/**
+ * @brief polyt0_pack - Bit-pack polynomial t0 with coefficients in
+ * ]-2^{D-1}, 2^{D-1}].
+ *
+ * @param [out] r pointer to output byte array with at least
+ * DILITHIUM_POLYT0_PACKEDBYTES bytes
+ * @param [in] a pointer to input polynomial
+ */
+void polyt0_pack(uint8_t *r, const poly *a)
+{
+ unsigned int i;
+ uint32_t t[8];
+
+ for (i = 0; i < DILITHIUM_N / 8; ++i) {
+ t[0] = (uint32_t)((1 << (DILITHIUM_D - 1)) -
+ a->coeffs[8 * i + 0]);
+ t[1] = (uint32_t)((1 << (DILITHIUM_D - 1)) -
+ a->coeffs[8 * i + 1]);
+ t[2] = (uint32_t)((1 << (DILITHIUM_D - 1)) -
+ a->coeffs[8 * i + 2]);
+ t[3] = (uint32_t)((1 << (DILITHIUM_D - 1)) -
+ a->coeffs[8 * i + 3]);
+ t[4] = (uint32_t)((1 << (DILITHIUM_D - 1)) -
+ a->coeffs[8 * i + 4]);
+ t[5] = (uint32_t)((1 << (DILITHIUM_D - 1)) -
+ a->coeffs[8 * i + 5]);
+ t[6] = (uint32_t)((1 << (DILITHIUM_D - 1)) -
+ a->coeffs[8 * i + 6]);
+ t[7] = (uint32_t)((1 << (DILITHIUM_D - 1)) -
+ a->coeffs[8 * i + 7]);
+
+ r[13 * i + 0] = (uint8_t)(t[0]);
+ r[13 * i + 1] = (uint8_t)(t[0] >> 8);
+ r[13 * i + 1] |= (uint8_t)(t[1] << 5);
+ r[13 * i + 2] = (uint8_t)(t[1] >> 3);
+ r[13 * i + 3] = (uint8_t)(t[1] >> 11);
+ r[13 * i + 3] |= (uint8_t)(t[2] << 2);
+ r[13 * i + 4] = (uint8_t)(t[2] >> 6);
+ r[13 * i + 4] |= (uint8_t)(t[3] << 7);
+ r[13 * i + 5] = (uint8_t)(t[3] >> 1);
+ r[13 * i + 6] = (uint8_t)(t[3] >> 9);
+ r[13 * i + 6] |= (uint8_t)(t[4] << 4);
+ r[13 * i + 7] = (uint8_t)(t[4] >> 4);
+ r[13 * i + 8] = (uint8_t)(t[4] >> 12);
+ r[13 * i + 8] |= (uint8_t)(t[5] << 1);
+ r[13 * i + 9] = (uint8_t)(t[5] >> 7);
+ r[13 * i + 9] |= (uint8_t)(t[6] << 6);
+ r[13 * i + 10] = (uint8_t)(t[6] >> 2);
+ r[13 * i + 11] = (uint8_t)(t[6] >> 10);
+ r[13 * i + 11] |= (uint8_t)(t[7] << 3);
+ r[13 * i + 12] = (uint8_t)(t[7] >> 5);
+ }
+
+ memzero_explicit(t, sizeof(t));
+}
+
+/**
+ * @brief polyt0_unpack - Unpack polynomial t0 with coefficients in
+ * ]-2^{D-1}, 2^{D-1}].
+ *
+ * @param [out] r pointer to output polynomial
+ * @param [in] a byte array with bit-packed polynomial
+ */
+void polyt0_unpack(poly *r, const uint8_t *a)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_N / 8; ++i) {
+ r->coeffs[8 * i + 0] = a[13 * i + 0];
+ r->coeffs[8 * i + 0] |= (int32_t)a[13 * i + 1] << 8;
+ r->coeffs[8 * i + 0] &= 0x1FFF;
+
+ r->coeffs[8 * i + 1] = a[13 * i + 1] >> 5;
+ r->coeffs[8 * i + 1] |= (int32_t)a[13 * i + 2] << 3;
+ r->coeffs[8 * i + 1] |= (int32_t)a[13 * i + 3] << 11;
+ r->coeffs[8 * i + 1] &= 0x1FFF;
+
+ r->coeffs[8 * i + 2] = a[13 * i + 3] >> 2;
+ r->coeffs[8 * i + 2] |= (int32_t)a[13 * i + 4] << 6;
+ r->coeffs[8 * i + 2] &= 0x1FFF;
+
+ r->coeffs[8 * i + 3] = a[13 * i + 4] >> 7;
+ r->coeffs[8 * i + 3] |= (int32_t)a[13 * i + 5] << 1;
+ r->coeffs[8 * i + 3] |= (int32_t)a[13 * i + 6] << 9;
+ r->coeffs[8 * i + 3] &= 0x1FFF;
+
+ r->coeffs[8 * i + 4] = a[13 * i + 6] >> 4;
+ r->coeffs[8 * i + 4] |= (int32_t)a[13 * i + 7] << 4;
+ r->coeffs[8 * i + 4] |= (int32_t)a[13 * i + 8] << 12;
+ r->coeffs[8 * i + 4] &= 0x1FFF;
+
+ r->coeffs[8 * i + 5] = a[13 * i + 8] >> 1;
+ r->coeffs[8 * i + 5] |= (int32_t)a[13 * i + 9] << 7;
+ r->coeffs[8 * i + 5] &= 0x1FFF;
+
+ r->coeffs[8 * i + 6] = a[13 * i + 9] >> 6;
+ r->coeffs[8 * i + 6] |= (int32_t)a[13 * i + 10] << 2;
+ r->coeffs[8 * i + 6] |= (int32_t)a[13 * i + 11] << 10;
+ r->coeffs[8 * i + 6] &= 0x1FFF;
+
+ r->coeffs[8 * i + 7] = a[13 * i + 11] >> 3;
+ r->coeffs[8 * i + 7] |= (int32_t)a[13 * i + 12] << 5;
+ r->coeffs[8 * i + 7] &= 0x1FFF;
+
+ r->coeffs[8 * i + 0] =
+ (1 << (DILITHIUM_D - 1)) - r->coeffs[8 * i + 0];
+ r->coeffs[8 * i + 1] =
+ (1 << (DILITHIUM_D - 1)) - r->coeffs[8 * i + 1];
+ r->coeffs[8 * i + 2] =
+ (1 << (DILITHIUM_D - 1)) - r->coeffs[8 * i + 2];
+ r->coeffs[8 * i + 3] =
+ (1 << (DILITHIUM_D - 1)) - r->coeffs[8 * i + 3];
+ r->coeffs[8 * i + 4] =
+ (1 << (DILITHIUM_D - 1)) - r->coeffs[8 * i + 4];
+ r->coeffs[8 * i + 5] =
+ (1 << (DILITHIUM_D - 1)) - r->coeffs[8 * i + 5];
+ r->coeffs[8 * i + 6] =
+ (1 << (DILITHIUM_D - 1)) - r->coeffs[8 * i + 6];
+ r->coeffs[8 * i + 7] =
+ (1 << (DILITHIUM_D - 1)) - r->coeffs[8 * i + 7];
+ }
+}
+
+/**
+ * @param polyz_pack - Bit-pack polynomial with coefficients
+ * in [-(GAMMA1 - 1), GAMMA1].
+ *
+ * @param [out] r pointer to output byte array with at least
+ * DILITHIUM_POLYZ_PACKEDBYTES bytes
+ * @param [in] a pointer to input polynomial
+ */
+void polyz_pack(uint8_t *r, const poly *a)
+{
+ unsigned int i;
+ uint32_t t[4];
+
+#if DILITHIUM_GAMMA1 == (1 << 17)
+ for (i = 0; i < DILITHIUM_N / 4; ++i) {
+ t[0] = (uint32_t)(DILITHIUM_GAMMA1 - a->coeffs[4 * i + 0]);
+ t[1] = (uint32_t)(DILITHIUM_GAMMA1 - a->coeffs[4 * i + 1]);
+ t[2] = (uint32_t)(DILITHIUM_GAMMA1 - a->coeffs[4 * i + 2]);
+ t[3] = (uint32_t)(DILITHIUM_GAMMA1 - a->coeffs[4 * i + 3]);
+
+ r[9 * i + 0] = (uint8_t)(t[0]);
+ r[9 * i + 1] = (uint8_t)(t[0] >> 8);
+ r[9 * i + 2] = (uint8_t)(t[0] >> 16);
+ r[9 * i + 2] |= (uint8_t)(t[1] << 2);
+ r[9 * i + 3] = (uint8_t)(t[1] >> 6);
+ r[9 * i + 4] = (uint8_t)(t[1] >> 14);
+ r[9 * i + 4] |= (uint8_t)(t[2] << 4);
+ r[9 * i + 5] = (uint8_t)(t[2] >> 4);
+ r[9 * i + 6] = (uint8_t)(t[2] >> 12);
+ r[9 * i + 6] |= (uint8_t)(t[3] << 6);
+ r[9 * i + 7] = (uint8_t)(t[3] >> 2);
+ r[9 * i + 8] = (uint8_t)(t[3] >> 10);
+ }
+#elif DILITHIUM_GAMMA1 == (1 << 19)
+ for (i = 0; i < DILITHIUM_N / 2; ++i) {
+ t[0] = (uint32_t)(DILITHIUM_GAMMA1 - a->coeffs[2 * i + 0]);
+ t[1] = (uint32_t)(DILITHIUM_GAMMA1 - a->coeffs[2 * i + 1]);
+
+ r[5 * i + 0] = (uint8_t)(t[0]);
+ r[5 * i + 1] = (uint8_t)(t[0] >> 8);
+ r[5 * i + 2] = (uint8_t)(t[0] >> 16);
+ r[5 * i + 2] |= (uint8_t)(t[1] << 4);
+ r[5 * i + 3] = (uint8_t)(t[1] >> 4);
+ r[5 * i + 4] = (uint8_t)(t[1] >> 12);
+ }
+#else
+#error "Undefined Gamma"
+#endif
+
+ memzero_explicit(t, sizeof(t));
+}
+
+/**
+ * @brief polyz_unpack - Unpack polynomial z with coefficients
+ * in [-(GAMMA1 - 1), GAMMA1].
+ *
+ * @param [out] r pointer to output polynomial
+ * @param [in] a byte array with bit-packed polynomial
+ */
+void polyz_unpack(poly *r, const uint8_t *a)
+{
+ unsigned int i;
+
+#if DILITHIUM_GAMMA1 == (1 << 17)
+ for (i = 0; i < DILITHIUM_N / 4; ++i) {
+ r->coeffs[4 * i + 0] = a[9 * i + 0];
+ r->coeffs[4 * i + 0] |= (int32_t)a[9 * i + 1] << 8;
+ r->coeffs[4 * i + 0] |= (int32_t)a[9 * i + 2] << 16;
+ r->coeffs[4 * i + 0] &= 0x3FFFF;
+
+ r->coeffs[4 * i + 1] = a[9 * i + 2] >> 2;
+ r->coeffs[4 * i + 1] |= (int32_t)a[9 * i + 3] << 6;
+ r->coeffs[4 * i + 1] |= (int32_t)a[9 * i + 4] << 14;
+ r->coeffs[4 * i + 1] &= 0x3FFFF;
+
+ r->coeffs[4 * i + 2] = a[9 * i + 4] >> 4;
+ r->coeffs[4 * i + 2] |= (int32_t)a[9 * i + 5] << 4;
+ r->coeffs[4 * i + 2] |= (int32_t)a[9 * i + 6] << 12;
+ r->coeffs[4 * i + 2] &= 0x3FFFF;
+
+ r->coeffs[4 * i + 3] = a[9 * i + 6] >> 6;
+ r->coeffs[4 * i + 3] |= (int32_t)a[9 * i + 7] << 2;
+ r->coeffs[4 * i + 3] |= (int32_t)a[9 * i + 8] << 10;
+ r->coeffs[4 * i + 3] &= 0x3FFFF;
+
+ r->coeffs[4 * i + 0] =
+ DILITHIUM_GAMMA1 - r->coeffs[4 * i + 0];
+ r->coeffs[4 * i + 1] =
+ DILITHIUM_GAMMA1 - r->coeffs[4 * i + 1];
+ r->coeffs[4 * i + 2] =
+ DILITHIUM_GAMMA1 - r->coeffs[4 * i + 2];
+ r->coeffs[4 * i + 3] =
+ DILITHIUM_GAMMA1 - r->coeffs[4 * i + 3];
+ }
+#elif DILITHIUM_GAMMA1 == (1 << 19)
+ for (i = 0; i < DILITHIUM_N / 2; ++i) {
+ r->coeffs[2 * i + 0] = a[5 * i + 0];
+ r->coeffs[2 * i + 0] |= (int32_t)a[5 * i + 1] << 8;
+ r->coeffs[2 * i + 0] |= (int32_t)a[5 * i + 2] << 16;
+ r->coeffs[2 * i + 0] &= 0xFFFFF;
+
+ r->coeffs[2 * i + 1] = a[5 * i + 2] >> 4;
+ r->coeffs[2 * i + 1] |= (int32_t)a[5 * i + 3] << 4;
+ r->coeffs[2 * i + 1] |= (int32_t)a[5 * i + 4] << 12;
+ r->coeffs[2 * i + 1] &= 0xFFFFF;
+
+ r->coeffs[2 * i + 0] =
+ DILITHIUM_GAMMA1 - r->coeffs[2 * i + 0];
+ r->coeffs[2 * i + 1] =
+ DILITHIUM_GAMMA1 - r->coeffs[2 * i + 1];
+ }
+#else
+#error "Undefined Gamma"
+#endif
+}
+
+/**
+ * @brief polyw1_pack - Bit-pack polynomial w1 with coefficients in [0,15] or
+ * [0,43]. Input coefficients are assumed to be standard
+ * representatives.
+ *
+ * @param [out] r pointer to output byte array with at least
+ * DILITHIUM_POLYW1_PACKEDBYTES bytes
+ * @param [in] a pointer to input polynomial
+ */
+void polyw1_pack(uint8_t *r, const poly *a)
+{
+ unsigned int i;
+
+#if DILITHIUM_GAMMA2 == (DILITHIUM_Q - 1) / 88
+ for (i = 0; i < DILITHIUM_N / 4; ++i) {
+ r[3 * i + 0] = (uint8_t)(a->coeffs[4 * i + 0]);
+ r[3 * i + 0] |= (uint8_t)(a->coeffs[4 * i + 1] << 6);
+ r[3 * i + 1] = (uint8_t)(a->coeffs[4 * i + 1] >> 2);
+ r[3 * i + 1] |= (uint8_t)(a->coeffs[4 * i + 2] << 4);
+ r[3 * i + 2] = (uint8_t)(a->coeffs[4 * i + 2] >> 4);
+ r[3 * i + 2] |= (uint8_t)(a->coeffs[4 * i + 3] << 2);
+ }
+#elif DILITHIUM_GAMMA2 == (DILITHIUM_Q - 1) / 32
+ for (i = 0; i < DILITHIUM_N / 2; ++i)
+ r[i] = (uint8_t)(a->coeffs[2 * i + 0] |
+ (a->coeffs[2 * i + 1] << 4));
+#else
+#error "Undefined Gamma"
+#endif
+}
diff --git a/crypto/ml_dsa/dilithium_poly.h b/crypto/ml_dsa/dilithium_poly.h
new file mode 100644
index 000000000000..a894f5483fe8
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_poly.h
@@ -0,0 +1,190 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#ifndef DILITHIUM_POLY_H
+#define DILITHIUM_POLY_H
+
+#include "dilithium_type.h"
+#include "dilithium_reduce.h"
+#include "dilithium_rounding.h"
+
+typedef struct {
+ int32_t coeffs[DILITHIUM_N];
+} poly;
+
+/**
+ * @brief poly_add - Add polynomials. No modular reduction is performed.
+ *
+ * @param [out] c pointer to output polynomial
+ * @param [in] a pointer to first summand
+ * @param [in] b pointer to second summand
+ */
+static inline void poly_add(poly *c, const poly *a, const poly *b)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_N; ++i)
+ c->coeffs[i] = a->coeffs[i] + b->coeffs[i];
+}
+
+/**
+ * @brief poly_sub - Subtract polynomials. No modular reduction is
+ * performed.
+ *
+ * @param [out] c pointer to output polynomial
+ * @param [in] a pointer to first input polynomial
+ * @param [in] b pointer to second input polynomial to be subtraced from first
+ * input polynomial
+ */
+static inline void poly_sub(poly *c, const poly *a, const poly *b)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_N; ++i)
+ c->coeffs[i] = a->coeffs[i] - b->coeffs[i];
+}
+
+/**
+ * @brief poly_shiftl - Multiply polynomial by 2^D without modular reduction.
+ * Assumes input coefficients to be less than 2^{31-D} in
+ * absolute value.
+ *
+ * @param [in,out] a pointer to input/output polynomial
+ */
+static inline void poly_shiftl(poly *a)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_N; ++i)
+ a->coeffs[i] <<= DILITHIUM_D;
+}
+
+/**
+ * @brief poly_decompose - For all coefficients c of the input polynomial,
+ * compute high and low bits c0, c1 such
+ * c mod Q = c1*ALPHA + c0 with
+ * -ALPHA/2 < c0 <= ALPHA/2 except c1 = (Q-1)/ALPHA
+ * where we set c1 = 0 and
+ * -ALPHA/2 <= c0 = c mod Q - Q < 0.
+ * Assumes coefficients to be standard representatives.
+ *
+ * @param [out] a1 pointer to output polynomial with coefficients c1
+ * @param [out] a0 pointer to output polynomial with coefficients c0
+ * @param [in] a pointer to input polynomial
+ */
+static inline void poly_decompose(poly *a1, poly *a0, const poly *a)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_N; ++i)
+ a1->coeffs[i] = decompose(&a0->coeffs[i], a->coeffs[i]);
+}
+
+/**
+ * @brief poly_make_hint - Compute hint polynomial. The coefficients of which
+ * indicate whether the low bits of the corresponding
+ * coefficient of the input polynomial overflow into the
+ * high bits.
+ *
+ * @param [out] h pointer to output hint polynomial
+ * @param [in] a0 pointer to low part of input polynomial
+ * @param [in] a1 pointer to high part of input polynomial
+ *
+ * @return number of 1 bits.
+ */
+static inline unsigned int poly_make_hint(poly *h, const poly *a0,
+ const poly *a1)
+{
+ unsigned int i, s = 0;
+
+ for (i = 0; i < DILITHIUM_N; ++i) {
+ h->coeffs[i] = make_hint(a0->coeffs[i], a1->coeffs[i]);
+ s += (unsigned int)h->coeffs[i];
+ }
+
+ return s;
+}
+
+/**
+ * @brief poly_use_hint - Use hint polynomial to correct the high bits of a
+ * polynomial.
+ *
+ * @param [out] b pointer to output polynomial with corrected high bits
+ * @param [in] a pointer to input polynomial
+ * @param [in] h pointer to input hint polynomial
+ */
+static inline void poly_use_hint(poly *b, const poly *a, const poly *h)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_N; ++i)
+ b->coeffs[i] = use_hint(a->coeffs[i], h->coeffs[i]);
+}
+
+int poly_chknorm(const poly *a, int32_t B);
+
+#define POLY_UNIFORM_NBLOCKS \
+ ((768 + SHAKE128_BLOCK_SIZE - 1) / SHAKE128_BLOCK_SIZE)
+void poly_uniform(poly *a, const uint8_t seed[DILITHIUM_SEEDBYTES],
+ uint16_t nonce, void *ws_buf);
+
+#if DILITHIUM_ETA == 2
+#define POLY_UNIFORM_ETA_NBLOCKS \
+ ((136 + SHAKE256_BLOCK_SIZE - 1) / SHAKE256_BLOCK_SIZE)
+#elif DILITHIUM_ETA == 4
+#define POLY_UNIFORM_ETA_NBLOCKS \
+ ((227 + SHAKE256_BLOCK_SIZE - 1) / SHAKE256_BLOCK_SIZE)
+#else
+#error "Undefined DILITHIUM_ETA"
+#endif
+#define POLY_UNIFORM_ETA_BYTES POLY_UNIFORM_ETA_NBLOCKS *SHAKE256_BLOCK_SIZE
+void poly_uniform_eta(poly *a, const uint8_t seed[DILITHIUM_CRHBYTES],
+ uint16_t nonce, void *ws_buf);
+
+#define POLY_UNIFORM_GAMMA1_NBLOCKS \
+ ((DILITHIUM_POLYZ_PACKEDBYTES + SHAKE256_BLOCK_SIZE - 1) / \
+ SHAKE256_BLOCK_SIZE)
+#define POLY_UNIFORM_GAMMA1_BYTES \
+ POLY_UNIFORM_GAMMA1_NBLOCKS *SHAKE256_BLOCK_SIZE
+
+#define POLY_CHALLENGE_BYTES SHAKE256_BLOCK_SIZE
+void poly_challenge(poly *c, const uint8_t seed[DILITHIUM_CTILDE_BYTES],
+ void *ws_buf);
+
+void polyeta_pack(uint8_t *r, const poly *a);
+void polyeta_unpack(poly *r, const uint8_t *a);
+
+void polyt1_pack(uint8_t *r, const poly *a);
+
+void polyt0_pack(uint8_t *r, const poly *a);
+void polyt0_unpack(poly *r, const uint8_t *a);
+
+void polyz_pack(uint8_t *r, const poly *a);
+void polyz_unpack(poly *r, const uint8_t *a);
+
+void polyw1_pack(uint8_t *r, const poly *a);
+
+#endif /* DILITHIUM_POLY_H */
diff --git a/crypto/ml_dsa/dilithium_poly_c.h b/crypto/ml_dsa/dilithium_poly_c.h
new file mode 100644
index 000000000000..ca0107f75bab
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_poly_c.h
@@ -0,0 +1,149 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2023 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef DILITHIUM_POLY_C_H
+#define DILITHIUM_POLY_C_H
+
+#include "dilithium_ntt.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief poly_reduce - Inplace reduction of all coefficients of polynomial to
+ * representative in [-6283009,6283007].
+ *
+ * @param [in,out] a pointer to input/output polynomial
+ */
+static inline void poly_reduce(poly *a)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_N; ++i)
+ a->coeffs[i] = reduce32(a->coeffs[i]);
+}
+
+/**
+ * @brief poly_caddq - For all coefficients of in/out polynomial add Q if
+ * coefficient is negative.
+ *
+ * @param [in,out] a pointer to input/output polynomial
+ */
+static inline void poly_caddq(poly *a)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_N; ++i)
+ a->coeffs[i] = caddq(a->coeffs[i]);
+}
+
+/**
+ * @brief poly_pointwise_montgomery - Pointwise multiplication of polynomials in
+ * NTT domain representation and
+ * multiplication of resulting polynomial
+ * by 2^{-32}.
+ *
+ * @param [out] c pointer to output polynomial
+ * @param [in] a pointer to first input polynomial
+ * @param [in] b pointer to second input polynomial
+ */
+static inline void poly_pointwise_montgomery(poly *c, const poly *a,
+ const poly *b)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_N; ++i)
+ c->coeffs[i] =
+ montgomery_reduce((int64_t)a->coeffs[i] * b->coeffs[i]);
+}
+
+/**
+ * @brief poly_power2round - For all coefficients c of the input polynomial,
+ * compute c0, c1 such that c mod Q = c1*2^D + c0
+ * with -2^{D-1} < c0 <= 2^{D-1}. Assumes coefficients
+ * to be standard representatives.
+ *
+ * @param [out] a1 pointer to output polynomial with coefficients c1
+ * @param [out] a0 pointer to output polynomial with coefficients c0
+ * @param [in] a pointer to input polynomial
+ */
+static inline void poly_power2round(poly *a1, poly *a0, const poly *a)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_N; ++i)
+ a1->coeffs[i] = power2round(&a0->coeffs[i], a->coeffs[i]);
+}
+
+/**
+ * @brief polyt1_unpack - Unpack polynomial t1 with 10-bit coefficients.
+ * Output coefficients are standard representatives.
+ *
+ * @param [out] r pointer to output polynomial
+ * @param [in] a byte array with bit-packed polynomial
+ */
+static inline void polyt1_unpack(poly *r, const uint8_t *a)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_N / 4; ++i) {
+ r->coeffs[4 * i + 0] =
+ ((a[5 * i + 0] >> 0) | ((uint32_t)a[5 * i + 1] << 8)) &
+ 0x3FF;
+ r->coeffs[4 * i + 1] =
+ ((a[5 * i + 1] >> 2) | ((uint32_t)a[5 * i + 2] << 6)) &
+ 0x3FF;
+ r->coeffs[4 * i + 2] =
+ ((a[5 * i + 2] >> 4) | ((uint32_t)a[5 * i + 3] << 4)) &
+ 0x3FF;
+ r->coeffs[4 * i + 3] =
+ ((a[5 * i + 3] >> 6) | ((uint32_t)a[5 * i + 4] << 2)) &
+ 0x3FF;
+ }
+}
+
+/**
+ * @brief poly_ntt - Inplace forward NTT. Coefficients can grow by
+ * 8*Q in absolute value.
+ *
+ * @param [in,out] a pointer to input/output polynomial
+ */
+static inline void poly_ntt(poly *a)
+{
+ ntt(a->coeffs);
+}
+
+/**
+ * @brief poly_invntt_tomont - Inplace inverse NTT and multiplication by 2^{32}.
+ * Input coefficients need to be less than Q in
+ * absolute value and output coefficients are again
+ * bounded by Q.
+ *
+ * @param [in,out] a pointer to input/output polynomial
+ */
+static inline void poly_invntt_tomont(poly *a)
+{
+ invntt_tomont(a->coeffs);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DILITHIUM_POLY_C_H */
diff --git a/crypto/ml_dsa/dilithium_poly_common.h b/crypto/ml_dsa/dilithium_poly_common.h
new file mode 100644
index 000000000000..7c7cef28b545
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_poly_common.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#ifndef DILITHIUM_POLY_COMMON_H
+#define DILITHIUM_POLY_COMMON_H
+
+#include "dilithium_type.h"
+
+void poly_uniform_gamma1(poly *a, const uint8_t seed[DILITHIUM_CRHBYTES],
+ uint16_t nonce, void *ws_buf);
+
+#endif /* DILITHIUM_POLY_COMMON_H */
diff --git a/crypto/ml_dsa/dilithium_polyvec.h b/crypto/ml_dsa/dilithium_polyvec.h
new file mode 100644
index 000000000000..7e428e3becfd
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_polyvec.h
@@ -0,0 +1,343 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#ifndef DILITHIUM_POLYVEC_H
+#define DILITHIUM_POLYVEC_H
+
+#include "dilithium_type.h"
+#include "dilithium_poly.h"
+
+typedef struct {
+ poly vec[DILITHIUM_L];
+} polyvecl;
+
+/* Vectors of polynomials of length K */
+typedef struct {
+ poly vec[DILITHIUM_K];
+} polyveck;
+
+/**************************************************************/
+/************ Vectors of polynomials of length L **************/
+/**************************************************************/
+
+static inline void polyvecl_reduce(polyvecl *v)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_L; ++i)
+ poly_reduce(&v->vec[i]);
+}
+
+/**
+ * @brief polyvecl_add - Add vectors of polynomials of length L.
+ * No modular reduction is performed.
+ *
+ * @param [out] w pointer to output vector
+ * @param [in] u pointer to first summand
+ * @param [in] v pointer to second summand
+ */
+static inline void polyvecl_add(polyvecl *w, const polyvecl *u,
+ const polyvecl *v)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_L; ++i)
+ poly_add(&w->vec[i], &u->vec[i], &v->vec[i]);
+}
+
+/**
+ * @brief polyvecl_ntt - Forward NTT of all polynomials in vector of length L.
+ * Output coefficients can be up to 16*Q larger than input
+ * coefficients.
+ *
+ * @param [in,out] v pointer to input/output vector
+ */
+static inline void polyvecl_ntt(polyvecl *v)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_L; ++i)
+ poly_ntt(&v->vec[i]);
+}
+
+static inline void polyvecl_invntt_tomont(polyvecl *v)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_L; ++i)
+ poly_invntt_tomont(&v->vec[i]);
+}
+
+static inline void polyvecl_pointwise_poly_montgomery(polyvecl *r,
+ const poly *a,
+ const polyvecl *v)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_L; ++i)
+ poly_pointwise_montgomery(&r->vec[i], a, &v->vec[i]);
+}
+
+/**
+ * @brief polyvecl_chknorm - Check infinity norm of polynomials in vector of
+ * length L. Assumes input polyvecl to be reduced by
+ * polyvecl_reduce().
+ *
+ * @param [in] v pointer to vector
+ * @param [in] bound norm bound
+ *
+ * @return 0 if norm of all polynomials is strictly smaller than B <= (Q-1)/8
+ * and 1 otherwise.
+ */
+static inline int polyvecl_chknorm(const polyvecl *v, int32_t bound)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_L; ++i)
+ if (poly_chknorm(&v->vec[i], bound))
+ return 1;
+
+ return 0;
+}
+
+/**************************************************************/
+/************ Vectors of polynomials of length K **************/
+/**************************************************************/
+
+/**
+ * @brief polyveck_reduce - Reduce coefficients of polynomials in vector of
+ * length DILITHIUM_K to representatives in
+ * [-6283009,6283007].
+ *
+ * @param [in,out] v pointer to input/output vector
+ */
+static inline void polyveck_reduce(polyveck *v)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ poly_reduce(&v->vec[i]);
+}
+
+/**
+ * @brief polyveck_caddq - For all coefficients of polynomials in vector of
+ * length DILITHIUM_K add DILITHIUM_Q if
+ * coefficient is negative.
+ *
+ * @param [in,out] v pointer to input/output vector
+ */
+static inline void polyveck_caddq(polyveck *v)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ poly_caddq(&v->vec[i]);
+}
+
+/**
+ * @brief polyveck_add - Add vectors of polynomials of length DILITHIUM_K.
+ * No modular reduction is performed.
+ *
+ * @param [out] w pointer to output vector
+ * @param [in] u pointer to first summand
+ * @param [in] v pointer to second summand
+ */
+static inline void polyveck_add(polyveck *w, const polyveck *u,
+ const polyveck *v)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ poly_add(&w->vec[i], &u->vec[i], &v->vec[i]);
+}
+
+/**
+ * @brief olyveck_sub - Subtract vectors of polynomials of length
+ * DILITHIUM_K. No modular reduction is performed.
+ *
+ * @param [out] w pointer to output vector
+ * @param [in] u pointer to first input vector
+ * @param [in] v pointer to second input vector to be subtracted from first
+ * input vector
+ */
+static inline void polyveck_sub(polyveck *w, const polyveck *u,
+ const polyveck *v)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ poly_sub(&w->vec[i], &u->vec[i], &v->vec[i]);
+}
+
+/**
+ * @brief polyveck_shiftl - Multiply vector of polynomials of Length K by
+ * 2^D without modular reduction. Assumes input
+ * coefficients to be less than 2^{31-D}.
+ *
+ * @param [in,out] v pointer to input/output vector
+ */
+static inline void polyveck_shiftl(polyveck *v)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ poly_shiftl(&v->vec[i]);
+}
+
+/**
+ * @brief polyveck_ntt - Forward NTT of all polynomials in vector of length K.
+ * Output coefficients can be up to 16*Q larger than input
+ * coefficients.
+ *
+ * @param [in,out] v pointer to input/output vector
+ */
+static inline void polyveck_ntt(polyveck *v)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ poly_ntt(&v->vec[i]);
+}
+
+/**
+ * @brief polyveck_invntt_tomont - Inverse NTT and multiplication by 2^{32} of
+ * polynomials in vector of length K. Input
+ * coefficients need to be less than 2*Q.
+ *
+ * @param [in,out] v pointer to input/output vector
+ */
+static inline void polyveck_invntt_tomont(polyveck *v)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ poly_invntt_tomont(&v->vec[i]);
+}
+
+static inline void polyveck_pointwise_poly_montgomery(polyveck *r,
+ const poly *a,
+ const polyveck *v)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ poly_pointwise_montgomery(&r->vec[i], a, &v->vec[i]);
+}
+
+/**
+ * @brief polyveck_chknorm - Check infinity norm of polynomials in vector of
+ * length K. Assumes input polyveck to be reduced by
+ * polyveck_reduce().
+ *
+ * @param [in] v pointer to vector
+ * @param [in] bound norm bound
+ *
+ * @return 0 if norm of all polynomials are strictly smaller than B <= (Q-1)/8
+ * and 1 otherwise.
+ */
+static inline int polyveck_chknorm(const polyveck *v, int32_t bound)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ if (poly_chknorm(&v->vec[i], bound))
+ return 1;
+
+ return 0;
+}
+
+/**
+ * @brief polyveck_decompose - For all coefficients a of polynomials in vector
+ * of length K, compute high and low bits a0, a1
+ * such a mod^+ Q = a1*ALPHA + a0 with
+ * -ALPHA/2 < a0 <= ALPHA/2 except a1 = (Q-1)/ALPHA
+ * where we set a1 = 0 and
+ * -ALPHA/2 <= a0 = a mod Q - Q < 0. Assumes
+ * coefficients to be standard representatives.
+ *
+ * @param [out] v1 pointer to output vector of polynomials with coefficients a1
+ * @param [in] v0 pointer to output vector of polynomials with coefficients a0
+ * @param [in] v pointer to input vector
+ */
+static inline void polyveck_decompose(polyveck *v1, polyveck *v0,
+ const polyveck *v)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ poly_decompose(&v1->vec[i], &v0->vec[i], &v->vec[i]);
+}
+
+/**
+ * @brief polyveck_make_hint - Compute hint vector.
+ *
+ * @param [out] h pointer to output vector
+ * @param [in] v0 pointer to low part of input vector
+ * @param [in] v1 pointer to high part of input vector
+ *
+ * @return number of 1 bits.
+ */
+static inline unsigned int polyveck_make_hint(polyveck *h, const polyveck *v0,
+ const polyveck *v1)
+{
+ unsigned int i, s = 0;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ s += poly_make_hint(&h->vec[i], &v0->vec[i], &v1->vec[i]);
+
+ return s;
+}
+
+/**
+ * @brief polyveck_use_hint - Use hint vector to correct the high bits of input
+ * vector.
+ *
+ * @param [out] w pointer to output vector of polynomials with corrected high
+ * bits
+ * @param [in] u pointer to input vector
+ * @param [in] h pointer to input hint vector
+ */
+static inline void polyveck_use_hint(polyveck *w, const polyveck *u,
+ const polyveck *h)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ poly_use_hint(&w->vec[i], &u->vec[i], &h->vec[i]);
+}
+
+static inline void
+polyveck_pack_w1(uint8_t r[DILITHIUM_K * DILITHIUM_POLYW1_PACKEDBYTES],
+ const polyveck *w1)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ polyw1_pack(&r[i * DILITHIUM_POLYW1_PACKEDBYTES],
+ &w1->vec[i]);
+}
+
+#endif /* DILITHIUM_POLYVEC_H */
diff --git a/crypto/ml_dsa/dilithium_polyvec_c.h b/crypto/ml_dsa/dilithium_polyvec_c.h
new file mode 100644
index 000000000000..0f8c2d914e4d
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_polyvec_c.h
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2023 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef DILITHIUM_POLYVEC_C_H
+#define DILITHIUM_POLYVEC_C_H
+
+static inline void
+polyvecl_uniform_gamma1(polyvecl *v, const uint8_t seed[DILITHIUM_CRHBYTES],
+ uint16_t nonce, void *ws_buf)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_L; ++i)
+ poly_uniform_gamma1(
+ &v->vec[i], seed,
+ cpu_to_le16((uint16_t)(DILITHIUM_L * nonce + i)),
+ ws_buf);
+}
+
+/**
+ * @brief expand_mat - Implementation of ExpandA. Generates matrix A with
+ * uniformly random coefficients a_{i,j} by performing
+ * rejection sampling on the output stream of
+ * SHAKE128(rho|j|i).
+ *
+ * @param [out] mat output matrix
+ * @param [in] rho byte array containing seed rho
+ */
+static inline void
+polyvec_matrix_expand(polyvecl mat[DILITHIUM_K],
+ const uint8_t rho[DILITHIUM_SEEDBYTES], void *ws_buf)
+{
+ unsigned int i, j;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ for (j = 0; j < DILITHIUM_L; ++j)
+ poly_uniform(
+ &mat[i].vec[j], rho,
+ cpu_to_le16((uint16_t)(i << 8) + (uint16_t)j),
+ ws_buf);
+}
+
+/**
+ * @brief polyvecl_pointwise_acc_montgomery -
+ * Pointwise multiply vectors of polynomials of length L, multiply
+ * resulting vector by 2^{-32} and add (accumulate) polynomials
+ * in it. Input/output vectors are in NTT domain representation.
+ *
+ * @param [out] w output polynomial
+ * @param [in] u pointer to first input vector
+ * @param [in] v pointer to second input vector
+ */
+static inline void polyvecl_pointwise_acc_montgomery(poly *w, const polyvecl *u,
+ const polyvecl *v,
+ void *ws_buf)
+{
+ unsigned int i;
+ poly *t = ws_buf;
+
+ poly_pointwise_montgomery(w, &u->vec[0], &v->vec[0]);
+ for (i = 1; i < DILITHIUM_L; ++i) {
+ poly_pointwise_montgomery(t, &u->vec[i], &v->vec[i]);
+ poly_add(w, w, t);
+ }
+}
+
+static inline void
+polyvec_matrix_pointwise_montgomery(polyveck *t,
+ const polyvecl mat[DILITHIUM_K],
+ const polyvecl *v, void *ws_buf)
+{
+ unsigned int i;
+
+ for (i = 0; i < DILITHIUM_K; ++i)
+ polyvecl_pointwise_acc_montgomery(&t->vec[i], &mat[i], v,
+ ws_buf);
+}
+
+#endif /* DILITHIUM_POLYVEC_C_H */
diff --git a/crypto/ml_dsa/dilithium_reduce.h b/crypto/ml_dsa/dilithium_reduce.h
new file mode 100644
index 000000000000..c4dd78be2575
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_reduce.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#ifndef DILITHIUM_REDUCE_H
+#define DILITHIUM_REDUCE_H
+
+#include "dilithium_type.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MONT -4186625 // 2^32 % Q
+#define QINV 58728449 // q^(-1) mod 2^32
+
+/**
+ * @brief montgomery_reduce - For finite field element a with
+ * -2^{31}Q <= a <= Q*2^31,
+ * compute r \equiv a*2^{-32} (mod Q) such that
+ * -Q < r < Q.
+ *
+ * @param [in] a finite field element
+ *
+ * @return r
+ */
+static inline int32_t montgomery_reduce(int64_t a)
+{
+ int32_t t;
+
+ t = (int32_t)a * QINV;
+ t = (int32_t)((a - (int64_t)t * DILITHIUM_Q) >> 32);
+ return t;
+}
+
+/**
+ * @brief reduce32 - For finite field element a with a <= 2^{31} - 2^{22} - 1,
+ * compute r \equiv a (mod Q) such that
+ * -6283009 <= r <= 6283007.
+ *
+ * @param [in] a finite field element
+ *
+ * @return r
+ */
+static inline int32_t reduce32(int32_t a)
+{
+ int32_t t;
+
+ t = (a + (1 << 22)) >> 23;
+ t = a - t * DILITHIUM_Q;
+ return t;
+}
+
+/**
+ * @brief caddq - Add Q if input coefficient is negative.
+ *
+ * @param [in] a finite field element
+ *
+ * @return r
+ */
+static inline int32_t caddq(int32_t a)
+{
+ a += (a >> 31) & DILITHIUM_Q;
+ return a;
+}
+
+/**
+ * @brief freeze - For finite field element a, compute standard representative
+ * r = a mod^+ Q.
+ *
+ * @param [in] a finite field element a
+ *
+ * @return r
+ */
+static inline int32_t freeze(int32_t a)
+{
+ a = reduce32(a);
+ a = caddq(a);
+ return a;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DILITHIUM_REDUCE_H */
diff --git a/crypto/ml_dsa/dilithium_rounding.c b/crypto/ml_dsa/dilithium_rounding.c
new file mode 100644
index 000000000000..c8a9c88185cf
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_rounding.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#include "dilithium_rounding.h"
+
+/**
+ * @brief power2round - For finite field element a, compute a0, a1 such that
+ * a mod^+ Q = a1*2^D + a0 with -2^{D-1} < a0 <= 2^{D-1}.
+ * Assumes a to be standard representative.
+ *
+ * @param [in] a input element
+ * @param [out] a0 pointer to output element a0
+ *
+ * @return a1.
+ */
+int32_t power2round(int32_t *a0, int32_t a)
+{
+ int32_t a1;
+
+ a1 = (a + (1 << (DILITHIUM_D - 1)) - 1) >> DILITHIUM_D;
+ *a0 = a - (a1 << DILITHIUM_D);
+ return a1;
+}
+
+/**
+ * @brief decompose - For finite field element a, compute high and low bits a0,
+ * a1 such that a mod^+ Q = a1*ALPHA + a0 with
+ * -ALPHA/2 < a0 <= ALPHA/2 except if a1 = (Q-1)/ALPHA where
+ * we set a1 = 0 and -ALPHA/2 <= a0 = a mod^+ Q - Q < 0.
+ * Assumes a to be standard representative.
+ *
+ * @param [in] a input element
+ * @param [out] a0 pointer to output element a0
+ *
+ * @return a1.
+ */
+int32_t decompose(int32_t *a0, int32_t a)
+{
+ int32_t a1;
+
+ a1 = (a + 127) >> 7;
+#if DILITHIUM_GAMMA2 == (DILITHIUM_Q - 1) / 32
+ a1 = (a1 * 1025 + (1 << 21)) >> 22;
+ a1 &= 15;
+#elif DILITHIUM_GAMMA2 == (DILITHIUM_Q - 1) / 88
+ a1 = (a1 * 11275 + (1 << 23)) >> 24;
+ a1 ^= ((43 - a1) >> 31) & a1;
+#else
+#error "Uknown GAMMA2"
+#endif
+
+ *a0 = a - a1 * 2 * DILITHIUM_GAMMA2;
+ *a0 -= (((DILITHIUM_Q - 1) / 2 - *a0) >> 31) & DILITHIUM_Q;
+
+ return a1;
+}
+
+/**
+ * @brief make_hint - Compute hint bit indicating whether the low bits of the
+ * input element overflow into the high bits.
+ *
+ * @param a0 [in] low bits of input element
+ * @param a1 [in] high bits of input element
+ *
+ * @return 1 if overflow.
+ */
+int32_t make_hint(int32_t a0, int32_t a1)
+{
+ if (a0 > DILITHIUM_GAMMA2 || a0 < -DILITHIUM_GAMMA2 ||
+ (a0 == -DILITHIUM_GAMMA2 && a1 != 0))
+ return 1;
+
+ return 0;
+}
+
+/**
+ * @brief use_hint - Correct high bits according to hint.
+ *
+ * @param [in] a input element
+ * @param [in] hint hint bit
+ *
+ * @return corrected high bits.
+ */
+int32_t use_hint(int32_t a, int32_t hint)
+{
+ int32_t a0, a1;
+
+ a1 = decompose(&a0, a);
+ if (hint == 0)
+ return a1;
+
+#if DILITHIUM_GAMMA2 == (DILITHIUM_Q - 1) / 32
+ if (a0 > 0)
+ return (a1 + 1) & 15;
+ else
+ return (a1 - 1) & 15;
+#elif DILITHIUM_GAMMA2 == (DILITHIUM_Q - 1) / 88
+ if (a0 > 0)
+ return (a1 == 43) ? 0 : a1 + 1;
+ else
+ return (a1 == 0) ? 43 : a1 - 1;
+#else
+#error "Uknown GAMMA2"
+#endif
+}
diff --git a/crypto/ml_dsa/dilithium_rounding.h b/crypto/ml_dsa/dilithium_rounding.h
new file mode 100644
index 000000000000..928b041856e9
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_rounding.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#ifndef DILITHIUM_ROUNDING_H
+#define DILITHIUM_ROUNDING_H
+
+#include "dilithium_type.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int32_t power2round(int32_t *a0, int32_t a);
+int32_t decompose(int32_t *a0, int32_t a);
+int32_t make_hint(int32_t a0, int32_t a1);
+int32_t use_hint(int32_t a, int32_t hint);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DILITHIUM_ROUNDING_H */
diff --git a/crypto/ml_dsa/dilithium_service_helpers.h b/crypto/ml_dsa/dilithium_service_helpers.h
new file mode 100644
index 000000000000..d671634e2040
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_service_helpers.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef DILITHIUM_SERVICE_HELPERS_H
+#define DILITHIUM_SERVICE_HELPERS_H
+
+/**
+ * @brief rej_uniform - Sample uniformly random coefficients in [0, Q-1] by
+ * performing rejection sampling on array of random bytes.
+ *
+ * @param [out] a pointer to output array (allocated)
+ * @param [in] len number of coefficients to be sampled
+ * @param [in] buf array of random bytes
+ * @param [in] buflen length of array of random bytes
+ *
+ * @return number of sampled coefficients. Can be smaller than len if not enough
+ * random bytes were given.
+ */
+static inline unsigned int rej_uniform(int32_t *a, unsigned int len,
+ const uint8_t *buf, unsigned int buflen)
+{
+ unsigned int ctr, pos;
+ uint32_t t;
+
+ ctr = pos = 0;
+ while (ctr < len && pos + 3 <= buflen) {
+ t = buf[pos++];
+ t |= (uint32_t)buf[pos++] << 8;
+ t |= (uint32_t)buf[pos++] << 16;
+ t &= 0x7FFFFF;
+
+ if (t < DILITHIUM_Q)
+ a[ctr++] = (int32_t)t;
+ }
+
+ return ctr;
+}
+
+/**
+ * @brief rej_eta - Sample uniformly random coefficients in [-ETA, ETA] by
+ * performing rejection sampling on array of random bytes.
+ *
+ * @param [out] a pointer to output array (allocated)
+ * @param [in] len number of coefficients to be sampled
+ * @param [in] buf array of random bytes
+ * @param [in] buflen length of array of random bytes
+ *
+ * @return number of sampled coefficients. Can be smaller than len if not enough
+ * random bytes were given.
+ */
+static inline unsigned int rej_eta(int32_t *a, unsigned int len,
+ const uint8_t *buf, unsigned int buflen)
+{
+ unsigned int ctr, pos;
+ int32_t t0, t1;
+
+ ctr = pos = 0;
+ while (ctr < len && pos < buflen) {
+ t0 = buf[pos] & 0x0F;
+ t1 = buf[pos++] >> 4;
+
+#if DILITHIUM_ETA == 2
+ if (t0 < 15) {
+ t0 = t0 - (205 * t0 >> 10) * 5;
+ a[ctr++] = 2 - t0;
+ }
+ if (t1 < 15 && ctr < len) {
+ t1 = t1 - (205 * t1 >> 10) * 5;
+ a[ctr++] = 2 - t1;
+ }
+#elif DILITHIUM_ETA == 4
+ if (t0 < 9)
+ a[ctr++] = 4 - t0;
+ if (t1 < 9 && ctr < len)
+ a[ctr++] = 4 - t1;
+#else
+#error "Undefined DILITHIUM_ETA"
+#endif
+ }
+
+ return ctr;
+}
+
+#endif /* DILITHIUM_SERVICE_HELPERS_H */
diff --git a/crypto/ml_dsa/dilithium_sig.c b/crypto/ml_dsa/dilithium_sig.c
new file mode 100644
index 000000000000..5495dd451de0
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_sig.c
@@ -0,0 +1,404 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/*
+ * Copyright (C) 2024 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <crypto/internal/sig.h>
+#include "dilithium.h"
+
+enum dilithium_kernel_key_type {
+ dilithium_kernel_key_unset = 0,
+ dilithium_kernel_key_sk = 1,
+ dilithium_kernel_key_pk = 2,
+};
+
+struct dilithium_kernel_ctx {
+ union {
+ struct dilithium_sk sk;
+ struct dilithium_pk pk;
+ };
+ enum dilithium_kernel_key_type key_type;
+};
+
+/* src -> message */
+/* dst -> signature */
+static int dilithium_kernel_sign(struct crypto_sig *tfm, const void *src,
+ unsigned int slen, void *dst,
+ unsigned int dlen)
+{
+ struct dilithium_kernel_ctx *ctx = crypto_sig_ctx(tfm);
+ struct dilithium_sig *sig;
+ enum dilithium_type type;
+ uint8_t *sig_ptr;
+ size_t sig_len;
+ int ret;
+
+ if (unlikely(ctx->key_type != dilithium_kernel_key_sk))
+ return -EINVAL;
+
+ type = dilithium_sk_type(&ctx->sk);
+ if (dlen != dilithium_sig_size(type))
+ return -EINVAL;
+
+ sig = kmalloc(sizeof(struct dilithium_sig), GFP_KERNEL);
+ if (!sig)
+ return -ENOMEM;
+
+ ret = dilithium_sign(sig, src, slen, &ctx->sk, lc_seeded_rng);
+ if (ret)
+ goto out;
+
+ ret = dilithium_sig_ptr(&sig_ptr, &sig_len, sig);
+ if (ret)
+ goto out;
+
+ memcpy(dst, sig_ptr, sig_len);
+ ret = sig_len;
+
+out:
+ kfree_sensitive(sig);
+ return ret;
+}
+
+/* src -> signature */
+/* msg -> message */
+static int dilithium_kernel_verify(struct crypto_sig *tfm, const void *src,
+ unsigned int slen, const void *msg,
+ unsigned int msg_len)
+{
+ struct dilithium_kernel_ctx *ctx = crypto_sig_ctx(tfm);
+ struct dilithium_sig *sig;
+ size_t sig_len;
+ enum dilithium_type type;
+ int ret;
+
+ if (unlikely(ctx->key_type != dilithium_kernel_key_pk))
+ return -EINVAL;
+
+ type = dilithium_pk_type(&ctx->pk);
+ sig_len = dilithium_sig_size(type);
+ if (slen < sig_len)
+ return -EINVAL;
+
+ sig = kmalloc(sizeof(struct dilithium_sig), GFP_KERNEL);
+ if (!sig)
+ return -ENOMEM;
+
+ ret = dilithium_sig_load(sig, src, sig_len);
+ if (ret)
+ goto out;
+
+ ret = dilithium_verify(sig, msg, msg_len, &ctx->pk);
+
+out:
+ kfree_sensitive(sig);
+ return ret;
+}
+
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+static unsigned int dilithium_kernel_87_key_size(struct crypto_sig *tfm)
+{
+ struct dilithium_kernel_ctx *ctx = crypto_sig_ctx(tfm);
+
+ switch (ctx->key_type) {
+ case dilithium_kernel_key_sk:
+ return sizeof(struct dilithium_87_sk);
+
+ case dilithium_kernel_key_unset:
+ case dilithium_kernel_key_pk:
+ default:
+ return sizeof(struct dilithium_87_pk);
+ }
+}
+#endif
+
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+static unsigned int dilithium_kernel_65_key_size(struct crypto_sig *tfm)
+{
+ struct dilithium_kernel_ctx *ctx = crypto_sig_ctx(tfm);
+
+ switch (ctx->key_type) {
+ case dilithium_kernel_key_sk:
+ return sizeof(struct dilithium_65_sk);
+
+ case dilithium_kernel_key_unset:
+ case dilithium_kernel_key_pk:
+ default:
+ return sizeof(struct dilithium_65_pk);
+ }
+}
+#endif
+
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+static unsigned int dilithium_kernel_44_key_size(struct crypto_sig *tfm)
+{
+ struct dilithium_kernel_ctx *ctx = crypto_sig_ctx(tfm);
+
+ switch (ctx->key_type) {
+ case dilithium_kernel_key_sk:
+ return sizeof(struct dilithium_44_sk);
+
+ case dilithium_kernel_key_unset:
+ case dilithium_kernel_key_pk:
+ default:
+ return sizeof(struct dilithium_44_pk);
+ }
+}
+#endif
+
+static int dilithium_kernel_set_pub_key_int(struct crypto_sig *tfm,
+ const void *key,
+ unsigned int keylen,
+ enum dilithium_type type)
+{
+ struct dilithium_kernel_ctx *ctx = crypto_sig_ctx(tfm);
+ int ret;
+
+ ctx->key_type = dilithium_kernel_key_unset;
+
+ ret = dilithium_pk_load(&ctx->pk, key, keylen);
+ if (!ret) {
+ if (dilithium_pk_type(&ctx->pk) != type)
+ ret = -EOPNOTSUPP;
+ else
+ ctx->key_type = dilithium_kernel_key_pk;
+ }
+
+ return ret;
+}
+
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+static int dilithium_kernel_44_set_pub_key(struct crypto_sig *tfm,
+ const void *key,
+ unsigned int keylen)
+{
+ return dilithium_kernel_set_pub_key_int(tfm, key, keylen, DILITHIUM_44);
+}
+#endif
+
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+static int dilithium_kernel_65_set_pub_key(struct crypto_sig *tfm,
+ const void *key,
+ unsigned int keylen)
+{
+ return dilithium_kernel_set_pub_key_int(tfm, key, keylen, DILITHIUM_65);
+}
+#endif
+
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+static int dilithium_kernel_87_set_pub_key(struct crypto_sig *tfm,
+ const void *key,
+ unsigned int keylen)
+{
+ return dilithium_kernel_set_pub_key_int(tfm, key, keylen, DILITHIUM_87);
+}
+#endif
+
+static int dilithium_kernel_set_priv_key_int(struct crypto_sig *tfm,
+ const void *key,
+ unsigned int keylen,
+ enum dilithium_type type)
+{
+ struct dilithium_kernel_ctx *ctx = crypto_sig_ctx(tfm);
+ int ret;
+
+ ctx->key_type = dilithium_kernel_key_unset;
+
+ ret = dilithium_sk_load(&ctx->sk, key, keylen);
+
+ if (!ret) {
+ if (dilithium_sk_type(&ctx->sk) != type)
+ ret = -EOPNOTSUPP;
+ else
+ ctx->key_type = dilithium_kernel_key_sk;
+ }
+
+ return ret;
+}
+
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+static int dilithium_kernel_44_set_priv_key(struct crypto_sig *tfm,
+ const void *key,
+ unsigned int keylen)
+{
+ return dilithium_kernel_set_priv_key_int(tfm, key, keylen,
+ DILITHIUM_44);
+}
+#endif
+
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+static int dilithium_kernel_65_set_priv_key(struct crypto_sig *tfm,
+ const void *key,
+ unsigned int keylen)
+{
+ return dilithium_kernel_set_priv_key_int(tfm, key, keylen,
+ DILITHIUM_65);
+}
+#endif
+
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+static int dilithium_kernel_87_set_priv_key(struct crypto_sig *tfm,
+ const void *key,
+ unsigned int keylen)
+{
+ return dilithium_kernel_set_priv_key_int(tfm, key, keylen,
+ DILITHIUM_87);
+}
+#endif
+
+static unsigned int dilithium_kernel_max_size(struct crypto_sig *tfm)
+{
+ struct dilithium_kernel_ctx *ctx = crypto_sig_ctx(tfm);
+ enum dilithium_type type;
+
+ switch (ctx->key_type) {
+ case dilithium_kernel_key_sk:
+ type = dilithium_sk_type(&ctx->sk);
+ /* When SK is set -> generate a signature */
+ return dilithium_sig_size(type);
+ case dilithium_kernel_key_pk:
+ type = dilithium_pk_type(&ctx->pk);
+ /* When PK is set, this is a safety valve, result is boolean */
+ return dilithium_sig_size(type);
+ default:
+ return 0;
+ }
+}
+
+static int dilithium_kernel_alg_init(struct crypto_sig *tfm)
+{
+ return 0;
+}
+
+static void dilithium_kernel_alg_exit(struct crypto_sig *tfm)
+{
+ struct dilithium_kernel_ctx *ctx = crypto_sig_ctx(tfm);
+
+ ctx->key_type = dilithium_kernel_key_unset;
+}
+
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+static struct sig_alg dilithium_kernel_87 = {
+ .sign = dilithium_kernel_sign,
+ .verify = dilithium_kernel_verify,
+ .set_pub_key = dilithium_kernel_87_set_pub_key,
+ .set_priv_key = dilithium_kernel_87_set_priv_key,
+ .key_size = dilithium_kernel_87_key_size,
+ .max_size = dilithium_kernel_max_size,
+ .init = dilithium_kernel_alg_init,
+ .exit = dilithium_kernel_alg_exit,
+ .base.cra_name = "ml-dsa87",
+ .base.cra_driver_name = "ml-dsa87-leancrypto",
+ .base.cra_ctxsize = sizeof(struct dilithium_kernel_ctx),
+ .base.cra_module = THIS_MODULE,
+ .base.cra_priority = 5000,
+};
+#endif
+
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+static struct sig_alg dilithium_kernel_65 = {
+ .sign = dilithium_kernel_sign,
+ .verify = dilithium_kernel_verify,
+ .set_pub_key = dilithium_kernel_65_set_pub_key,
+ .set_priv_key = dilithium_kernel_65_set_priv_key,
+ .key_size = dilithium_kernel_65_key_size,
+ .max_size = dilithium_kernel_max_size,
+ .init = dilithium_kernel_alg_init,
+ .exit = dilithium_kernel_alg_exit,
+ .base.cra_name = "ml-dsa65",
+ .base.cra_driver_name = "ml-dsa65-leancrypto",
+ .base.cra_ctxsize = sizeof(struct dilithium_kernel_ctx),
+ .base.cra_module = THIS_MODULE,
+ .base.cra_priority = 5000,
+};
+#endif
+
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+static struct sig_alg dilithium_kernel_44 = {
+ .sign = dilithium_kernel_sign,
+ .verify = dilithium_kernel_verify,
+ .set_pub_key = dilithium_kernel_44_set_pub_key,
+ .set_priv_key = dilithium_kernel_44_set_priv_key,
+ .key_size = dilithium_kernel_44_key_size,
+ .max_size = dilithium_kernel_max_size,
+ .init = dilithium_kernel_alg_init,
+ .exit = dilithium_kernel_alg_exit,
+ .base.cra_name = "ml-dsa44",
+ .base.cra_driver_name = "ml-dsa44-leancrypto",
+ .base.cra_ctxsize = sizeof(struct dilithium_kernel_ctx),
+ .base.cra_module = THIS_MODULE,
+ .base.cra_priority = 5000,
+};
+#endif
+
+static int __init dilithium_init(void)
+{
+ int ret;
+
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ ret = crypto_register_sig(&dilithium_kernel_44);
+ if (ret < 0)
+ goto error_44;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ ret = crypto_register_sig(&dilithium_kernel_65);
+ if (ret < 0)
+ goto error_65;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ ret = crypto_register_sig(&dilithium_kernel_87);
+ if (ret < 0)
+ goto error_87;
+#endif
+ return 0;
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+error_87:
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ crypto_unregister_sig(&dilithium_kernel_65);
+error_65:
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ crypto_unregister_sig(&dilithium_kernel_44);
+error_44:
+#endif
+ pr_err("Failed to register (%d)\n", ret);
+ return ret;
+}
+module_init(dilithium_init);
+
+static void dilithium_exit(void)
+{
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ crypto_unregister_sig(&dilithium_kernel_87);
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ crypto_unregister_sig(&dilithium_kernel_65);
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ crypto_unregister_sig(&dilithium_kernel_44);
+#endif
+}
+module_exit(dilithium_exit);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("Leancrypto ML-DSA/Dilithium");
+MODULE_AUTHOR("Stephan Mueller <smueller@...onox.de>");
+MODULE_ALIAS_CRYPTO("ml-dsa44");
+MODULE_ALIAS_CRYPTO("ml-dsa65");
+MODULE_ALIAS_CRYPTO("ml-dsa87");
diff --git a/crypto/ml_dsa/dilithium_signature_c.c b/crypto/ml_dsa/dilithium_signature_c.c
new file mode 100644
index 000000000000..a4a9d0bfdd37
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_signature_c.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#include "dilithium_signature_c.h"
+
+/* We need once the buffer size to handle the hashing */
+#define LC_POLY_UNIFOR_BUF_SIZE_MULTIPLIER 1
+
+#include "dilithium_poly.h"
+#include "dilithium_poly_common.h"
+#include "dilithium_poly_c.h"
+#include "dilithium_polyvec.h"
+#include "dilithium_polyvec_c.h"
+#include "dilithium_pack.h"
+#include "dilithium_signature_impl.h"
+
+int dilithium_sign_c(struct dilithium_sig *sig,
+ const uint8_t *m, size_t mlen,
+ const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx)
+{
+ return dilithium_sign_impl(sig, m, mlen, sk, rng_ctx);
+}
+
+int dilithium_sign_ctx_c(struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen, const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx)
+{
+ return dilithium_sign_ctx_impl(sig, ctx, m, mlen, sk, rng_ctx);
+}
+
+int dilithium_sign_init_c(struct dilithium_ctx *ctx,
+ const struct dilithium_sk *sk)
+{
+ return dilithium_sign_init_impl(ctx, sk);
+}
+
+int dilithium_sign_update_c(struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen)
+{
+ return dilithium_sign_update_impl(ctx, m, mlen);
+}
+
+int dilithium_sign_final_c(struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx)
+{
+ return dilithium_sign_final_impl(sig, ctx, sk, rng_ctx);
+}
+
+int dilithium_verify_c(const struct dilithium_sig *sig, const uint8_t *m,
+ size_t mlen, const struct dilithium_pk *pk)
+{
+ return dilithium_verify_impl(sig, m, mlen, pk);
+}
+
+int dilithium_verify_ctx_c(const struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen, const struct dilithium_pk *pk)
+{
+ return dilithium_verify_ctx_impl(sig, ctx, m, mlen, pk);
+}
+
+int dilithium_verify_init_c(struct dilithium_ctx *ctx,
+ const struct dilithium_pk *pk)
+{
+ return dilithium_verify_init_impl(ctx, pk);
+}
+
+int dilithium_verify_update_c(struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen)
+{
+ return dilithium_verify_update_impl(ctx, m, mlen);
+}
+
+int dilithium_verify_final_c(const struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_pk *pk)
+{
+ return dilithium_verify_final_impl(sig, ctx, pk);
+}
+
+int dilithium_sign(struct dilithium_sig *sig,
+ const uint8_t *m, size_t mlen,
+ const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx)
+{
+ return dilithium_sign_c(sig, m, mlen, sk, rng_ctx);
+}
+
+int dilithium_sign_ctx(struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen, const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx)
+{
+ return dilithium_sign_ctx_c(sig, ctx, m, mlen, sk, rng_ctx);
+}
+
+int dilithium_sign_init(struct dilithium_ctx *ctx,
+ const struct dilithium_sk *sk)
+{
+ return dilithium_sign_init_c(ctx, sk);
+}
+
+int dilithium_sign_update(struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen)
+{
+ return dilithium_sign_update_c(ctx, m, mlen);
+}
+
+int dilithium_sign_final(struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx)
+{
+ return dilithium_sign_final_c(sig, ctx, sk, rng_ctx);
+}
+
+int dilithium_verify(const struct dilithium_sig *sig, const uint8_t *m,
+ size_t mlen, const struct dilithium_pk *pk)
+{
+ return dilithium_verify_c(sig, m, mlen, pk);
+}
+
+int dilithium_verify_ctx(const struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen, const struct dilithium_pk *pk)
+{
+ return dilithium_verify_ctx_c(sig, ctx, m, mlen, pk);
+}
+
+int dilithium_verify_init(struct dilithium_ctx *ctx,
+ const struct dilithium_pk *pk)
+{
+ return dilithium_verify_init_c(ctx, pk);
+}
+
+int dilithium_verify_update(struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen)
+{
+ return dilithium_verify_update_c(ctx, m, mlen);
+}
+
+int dilithium_verify_final(const struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_pk *pk)
+{
+ return dilithium_verify_final_c(sig, ctx, pk);
+}
diff --git a/crypto/ml_dsa/dilithium_signature_c.h b/crypto/ml_dsa/dilithium_signature_c.h
new file mode 100644
index 000000000000..a7d20cd49672
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_signature_c.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef DILITHIUM_SIGNATURE_C_H
+#define DILITHIUM_SIGNATURE_C_H
+
+#include "dilithium_type.h"
+
+int dilithium_sign_c(struct dilithium_sig *sig, const uint8_t *m,
+ size_t mlen, const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx);
+int dilithium_sign_ctx_c(struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen, const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx);
+int dilithium_sign_init_c(struct dilithium_ctx *ctx,
+ const struct dilithium_sk *sk);
+int dilithium_sign_update_c(struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen);
+int dilithium_sign_final_c(struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx);
+
+int dilithium_verify_c(const struct dilithium_sig *sig, const uint8_t *m,
+ size_t mlen, const struct dilithium_pk *pk);
+int dilithium_verify_ctx_c(const struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen, const struct dilithium_pk *pk);
+int dilithium_verify_init_c(struct dilithium_ctx *ctx,
+ const struct dilithium_pk *pk);
+int dilithium_verify_update_c(struct dilithium_ctx *ctx, const uint8_t *m,
+ size_t mlen);
+int dilithium_verify_final_c(const struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_pk *pk);
+
+#endif /* DILITHIUM_SIGNATURE_C_H */
diff --git a/crypto/ml_dsa/dilithium_signature_helper.c b/crypto/ml_dsa/dilithium_signature_helper.c
new file mode 100644
index 000000000000..3f6b9d02602f
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_signature_helper.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/*
+ * Copyright (C) 2024 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#include <linux/export.h>
+#include <linux/math.h>
+#include <linux/slab.h>
+#include "dilithium_type.h"
+
+#define cround(x) round_up((x), umax(DILITHIUM_AHAT_ALIGNMENT, CRYPTO_MINALIGN))
+
+struct dilithium_ctx *dilithium_ctx_alloc(void)
+{
+ struct dilithium_ctx *ctx;
+ struct crypto_shash *shash;
+ void *p;
+
+ shash = crypto_alloc_shash("shake256", 0, 0);
+ if (IS_ERR(shash)) {
+ pr_warn("no shake256: %ld\n", PTR_ERR(shash));
+ return ERR_CAST(shash);
+ }
+
+ p = kzalloc(cround(sizeof(struct dilithium_ctx)) +
+ cround(crypto_shash_descsize(shash)),
+ GFP_KERNEL);
+ if (!p) {
+ crypto_free_shash(shash);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ ctx = p;
+ //ctx->dilithium_hash_ctx = p + cround(sizeof(struct dilithium_ctx));
+ ctx->dilithium_prehash_type = shash;
+ return ctx;
+}
+EXPORT_SYMBOL(dilithium_ctx_alloc);
+
+struct dilithium_ctx *dilithium_ctx_alloc_ahat(enum dilithium_type type)
+{
+ struct dilithium_ctx *ctx;
+ struct crypto_shash *shash;
+ size_t ahat_size;
+ void *p;
+
+ shash = crypto_alloc_shash("shake256", 0, 0);
+ if (IS_ERR(shash))
+ return ERR_CAST(shash);
+
+ switch (type) {
+#ifdef CONFIG_CRYPTO_DILITHIUM_44
+ case DILITHIUM_44:
+ ahat_size = DILITHIUM_44_AHAT_SIZE;
+ break;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_65
+ case DILITHIUM_65:
+ ahat_size = DILITHIUM_65_AHAT_SIZE;
+ break;
+#endif
+#ifdef CONFIG_CRYPTO_DILITHIUM_87
+ case DILITHIUM_87:
+ ahat_size = DILITHIUM_87_AHAT_SIZE;
+ break;
+#endif
+ default:
+ WARN_ON(1);
+ return ERR_PTR(-EINVAL);
+ }
+
+ p = kzalloc(cround(sizeof(struct dilithium_ctx)) +
+ cround(ahat_size) +
+ cround(crypto_shash_descsize(shash)),
+ GFP_KERNEL);
+ if (!p) {
+ crypto_free_shash(shash);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ ctx = p;
+ p += cround(sizeof(struct dilithium_ctx));
+ ctx->ahat = p;
+ ctx->ahat_size = ahat_size;
+ p += cround(ahat_size);
+ //ctx->dilithium_hash_ctx = p;
+ ctx->dilithium_prehash_type = shash;
+ return ctx;
+}
+EXPORT_SYMBOL(dilithium_ctx_alloc_ahat);
+
+void dilithium_ctx_zero_free(struct dilithium_ctx *ctx)
+{
+ crypto_free_shash(ctx->dilithium_prehash_type);
+ kfree_sensitive(ctx);
+}
+EXPORT_SYMBOL(dilithium_ctx_zero_free);
diff --git a/crypto/ml_dsa/dilithium_signature_impl.h b/crypto/ml_dsa/dilithium_signature_impl.h
new file mode 100644
index 000000000000..1f0ec0f7c27c
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_signature_impl.h
@@ -0,0 +1,838 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#ifndef DILITHIUM_SIGNATURE_IMPL_H
+#define DILITHIUM_SIGNATURE_IMPL_H
+
+#include "dilithium.h"
+#include "dilithium_type.h"
+#include "dilithium_debug.h"
+#include "dilithium_pack.h"
+#include "dilithium_signature_impl.h"
+#include "signature_domain_separation.h"
+#include "small_stack_support.h"
+
+/*
+ * Enable this macro to report the rejection code paths taken with the
+ * signature generation operation. When disabled, the compiler should
+ * eliminate this code which means that the counting code is folded away.
+ */
+#undef REJECTION_TEST_SAMPLING
+
+#define _WS_POLY_UNIFORM_BUF_SIZE \
+ (POLY_UNIFORM_NBLOCKS * SHAKE128_BLOCK_SIZE + 2)
+
+#ifndef LC_POLY_UNIFOR_BUF_SIZE_MULTIPLIER
+#error "LC_POLY_UNIFOR_BUF_SIZE_MULTIPLIER is not defined"
+#endif
+
+#define WS_POLY_UNIFORM_BUF_SIZE \
+ (_WS_POLY_UNIFORM_BUF_SIZE * LC_POLY_UNIFOR_BUF_SIZE_MULTIPLIER)
+
+static int dilithium_sign_internal_ahat(struct dilithium_sig *sig,
+ const struct dilithium_sk *sk,
+ struct dilithium_ctx *ctx,
+ struct crypto_rng *rng_ctx)
+{
+ struct workspace_sign {
+ polyvecl s1, y, z;
+ polyveck t0, s2, w1, w0, h;
+ poly cp;
+ uint8_t seedbuf[DILITHIUM_SEEDBYTES + DILITHIUM_RNDBYTES +
+ DILITHIUM_CRHBYTES];
+ union {
+ uint8_t poly_uniform_gamma1_buf[WS_POLY_UNIFORM_BUF_SIZE];
+ uint8_t poly_challenge_buf[POLY_CHALLENGE_BYTES];
+ } tmp;
+ };
+ unsigned int n;
+ uint8_t *key, *mu, *rhoprime, *rnd;
+ const polyvecl *mat = ctx->ahat;
+ uint16_t nonce = 0;
+ int ret = 0;
+ uint8_t __maybe_unused rej_total = 0;
+ LC_DECLARE_MEM(ws, struct workspace_sign, sizeof(uint64_t));
+
+ /* AHat must be present at this time */
+ if (WARN_ON_ONCE(!mat)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ key = ws->seedbuf;
+ rnd = key + DILITHIUM_SEEDBYTES;
+ mu = rnd + DILITHIUM_RNDBYTES;
+
+ /*
+ * If the external mu is provided, use this verbatim, otherwise
+ * calculate the mu value.
+ */
+ if (ctx->external_mu) {
+ if (ctx->external_mu_len != DILITHIUM_CRHBYTES)
+ return -EINVAL;
+ memcpy(mu, ctx->external_mu, DILITHIUM_CRHBYTES);
+ } else {
+ /*
+ * Set the digestsize - for SHA512 this is a noop, for SHAKE256,
+ * it sets the value. The BUILD_BUG_ON is to check that the
+ * SHA-512 output size is identical to the expected length.
+ */
+ BUILD_BUG_ON(DILITHIUM_CRHBYTES != SHA3_512_DIGEST_SIZE);
+ if (!dilithium_hash_check_digestsize(ctx, DILITHIUM_CRHBYTES)) {
+ ret = -EINVAL;
+ goto out;
+ }
+ dilithium_hash_final(ctx, mu, DILITHIUM_CRHBYTES);
+ }
+ dilithium_print_buffer(mu, DILITHIUM_CRHBYTES, "Siggen - MU:");
+
+ if (rng_ctx) {
+ ret = crypto_rng_generate(rng_ctx, NULL, 0, rnd,
+ DILITHIUM_RNDBYTES);
+ if (ret < 0)
+ goto out;
+ } else {
+ memset(rnd, 0, DILITHIUM_RNDBYTES);
+ }
+ dilithium_print_buffer(rnd, DILITHIUM_RNDBYTES, "Siggen - RND:");
+
+ unpack_sk_key(key, sk);
+
+ /* Timecop: key is secret */
+ poison(key, DILITHIUM_SEEDBYTES);
+
+ /* Re-use the ws->seedbuf, but making sure that mu is unchanged */
+ BUILD_BUG_ON(DILITHIUM_CRHBYTES >
+ DILITHIUM_SEEDBYTES + DILITHIUM_RNDBYTES);
+ rhoprime = key;
+
+ shake256(key,
+ DILITHIUM_SEEDBYTES + DILITHIUM_RNDBYTES +
+ DILITHIUM_CRHBYTES,
+ rhoprime, DILITHIUM_CRHBYTES);
+ dilithium_print_buffer(rhoprime, DILITHIUM_CRHBYTES,
+ "Siggen - RHOPrime:");
+
+ /*
+ * Timecop: RHO' is the hash of the secret value of key which is
+ * enlarged to sample the intermediate vector y from. Due to the hashing
+ * any side channel on RHO' cannot allow the deduction of the original
+ * key.
+ */
+ unpoison(rhoprime, DILITHIUM_CRHBYTES);
+
+ unpack_sk_s1(&ws->s1, sk);
+
+ /* Timecop: s1 is secret */
+ poison(&ws->s1, sizeof(polyvecl));
+
+ polyvecl_ntt(&ws->s1);
+ dilithium_print_polyvecl(&ws->s1,
+ "Siggen - S1 L x N matrix after NTT:");
+
+ unpack_sk_s2(&ws->s2, sk);
+
+ /* Timecop: s2 is secret */
+ poison(&ws->s2, sizeof(polyveck));
+
+ polyveck_ntt(&ws->s2);
+ dilithium_print_polyveck(&ws->s2,
+ "Siggen - S2 K x N matrix after NTT:");
+
+ unpack_sk_t0(&ws->t0, sk);
+ polyveck_ntt(&ws->t0);
+ dilithium_print_polyveck(&ws->t0,
+ "Siggen - T0 K x N matrix after NTT:");
+
+rej:
+ /* Sample intermediate vector y */
+ polyvecl_uniform_gamma1(&ws->y, rhoprime, nonce++,
+ ws->tmp.poly_uniform_gamma1_buf);
+ dilithium_print_polyvecl(
+ &ws->y,
+ "Siggen - Y L x N matrix after ExpandMask - start of loop");
+
+ /* Timecop: s2 is secret */
+ poison(&ws->y, sizeof(polyvecl));
+
+ /* Matrix-vector multiplication */
+ ws->z = ws->y;
+ polyvecl_ntt(&ws->z);
+
+ /* Use the cp for this operation as it is not used here so far. */
+ polyvec_matrix_pointwise_montgomery(&ws->w1, mat, &ws->z, &ws->cp);
+ polyveck_reduce(&ws->w1);
+ polyveck_invntt_tomont(&ws->w1);
+ dilithium_print_polyveck(&ws->w1,
+ "Siggen - W K x N matrix after NTT-1");
+
+ /* Decompose w and call the random oracle */
+ polyveck_caddq(&ws->w1);
+ polyveck_decompose(&ws->w1, &ws->w0, &ws->w1);
+
+ /* Timecop: the signature component w1 is not sensitive any more. */
+ unpoison(&ws->w1, sizeof(polyveck));
+ polyveck_pack_w1(sig->sig, &ws->w1);
+ dilithium_print_buffer(sig->sig,
+ DILITHIUM_K * DILITHIUM_POLYW1_PACKEDBYTES,
+ "Siggen - w1Encode of W1");
+
+ dilithium_hash_init(ctx);
+ dilithium_hash_update(ctx, mu, DILITHIUM_CRHBYTES);
+ dilithium_hash_finup(ctx, sig->sig,
+ DILITHIUM_K * DILITHIUM_POLYW1_PACKEDBYTES,
+ sig->sig, DILITHIUM_CTILDE_BYTES);
+
+ dilithium_print_buffer(sig->sig, DILITHIUM_CTILDE_BYTES,
+ "Siggen - ctilde");
+
+ poly_challenge(&ws->cp, sig->sig, ws->tmp.poly_challenge_buf);
+ dilithium_print_poly(&ws->cp, "Siggen - c after SampleInBall");
+ poly_ntt(&ws->cp);
+ dilithium_print_poly(&ws->cp, "Siggen - c after NTT");
+
+ /* Compute z, reject if it reveals secret */
+ polyvecl_pointwise_poly_montgomery(&ws->z, &ws->cp, &ws->s1);
+ polyvecl_invntt_tomont(&ws->z);
+ polyvecl_add(&ws->z, &ws->z, &ws->y);
+ dilithium_print_polyvecl(&ws->z, "Siggen - z <- y + cs1");
+
+ polyvecl_reduce(&ws->z);
+ dilithium_print_polyvecl(&ws->z, "Siggen - z reduction");
+
+ /* Timecop: the signature component z is not sensitive any more. */
+ unpoison(&ws->z, sizeof(polyvecl));
+
+ if (polyvecl_chknorm(&ws->z, DILITHIUM_GAMMA1 - DILITHIUM_BETA)) {
+ dilithium_print_polyvecl(&ws->z, "Siggen - z rejection");
+ rej_total |= 1 << 0;
+ goto rej;
+ }
+
+ /*
+ * Check that subtracting cs2 does not change high bits of w and low
+ * bits do not reveal secret information.
+ */
+ polyveck_pointwise_poly_montgomery(&ws->h, &ws->cp, &ws->s2);
+ polyveck_invntt_tomont(&ws->h);
+ polyveck_sub(&ws->w0, &ws->w0, &ws->h);
+ polyveck_reduce(&ws->w0);
+
+ /* Timecop: verification data w0 is not sensitive any more. */
+ unpoison(&ws->w0, sizeof(polyveck));
+
+ if (polyveck_chknorm(&ws->w0,
+ DILITHIUM_GAMMA2 - DILITHIUM_BETA)) {
+ dilithium_print_polyveck(&ws->w0, "Siggen - r0 rejection");
+ rej_total |= 1 << 1;
+ goto rej;
+ }
+
+ /* Compute hints for w1 */
+ polyveck_pointwise_poly_montgomery(&ws->h, &ws->cp, &ws->t0);
+ polyveck_invntt_tomont(&ws->h);
+ polyveck_reduce(&ws->h);
+
+ /* Timecop: the signature component h is not sensitive any more. */
+ unpoison(&ws->h, sizeof(polyveck));
+
+ if (polyveck_chknorm(&ws->h, DILITHIUM_GAMMA2)) {
+ dilithium_print_polyveck(&ws->h, "Siggen - ct0 rejection");
+ rej_total |= 1 << 2;
+ goto rej;
+ }
+
+ polyveck_add(&ws->w0, &ws->w0, &ws->h);
+
+ n = polyveck_make_hint(&ws->h, &ws->w0, &ws->w1);
+ if (n > DILITHIUM_OMEGA) {
+ dilithium_print_polyveck(&ws->w0, "Siggen - h rejection");
+ rej_total |= 1 << 3;
+ goto rej;
+ }
+
+ /* Write signature */
+ dilithium_print_buffer(sig->sig, DILITHIUM_CTILDE_BYTES,
+ "Siggen - Ctilde:");
+ dilithium_print_polyvecl(&ws->z, "Siggen - Z L x N matrix:");
+ dilithium_print_polyveck(&ws->h, "Siggen - H K x N matrix:");
+
+ pack_sig(sig, &ws->z, &ws->h);
+
+ dilithium_print_buffer(sig->sig, DILITHIUM_CRYPTO_BYTES,
+ "Siggen - Signature:");
+
+out:
+ LC_RELEASE_MEM(ws);
+#ifdef REJECTION_TEST_SAMPLING
+ return ret ? ret : rej_total;
+#else
+ return ret;
+#endif
+}
+
+static int dilithium_sign_internal_noahat(struct dilithium_sig *sig,
+ const struct dilithium_sk *sk,
+ struct dilithium_ctx *ctx,
+ struct crypto_rng *rng_ctx)
+{
+ struct workspace_sign {
+ polyvecl mat[DILITHIUM_K];
+ uint8_t poly_uniform_buf[WS_POLY_UNIFORM_BUF_SIZE];
+ };
+ /* The first bytes of the key is rho. */
+ const uint8_t *rho = sk->sk;
+ int ret = 0;
+ LC_DECLARE_MEM(ws, struct workspace_sign, DILITHIUM_AHAT_ALIGNMENT);
+
+ polyvec_matrix_expand(ws->mat, rho, ws->poly_uniform_buf);
+
+ /* Temporarily set the pointer */
+ ctx->ahat = ws->mat;
+
+ ret = dilithium_sign_internal_ahat(sig, sk, ctx, rng_ctx);
+
+ ctx->ahat = NULL;
+ LC_RELEASE_MEM(ws);
+ return ret;
+}
+
+static int dilithium_sk_expand_impl(const struct dilithium_sk *sk,
+ struct dilithium_ctx *ctx)
+{
+ struct workspace_sign {
+ uint8_t poly_uniform_buf[WS_POLY_UNIFORM_BUF_SIZE];
+ };
+ /* The first bytes of the key is rho. */
+ const uint8_t *rho = sk->sk;
+ polyvecl *mat = ctx->ahat;
+ int ret = 0;
+ LC_DECLARE_MEM(ws, struct workspace_sign, sizeof(uint64_t));
+
+ /*
+ * The compile time sanity check links API header file with
+ * Dilithium-internal definitions.
+ *
+ * Runtime sanity check ensures that the allocated context has
+ * sufficient size (e.g. not that caller used, say,
+ * DILITHIUM_44_CTX_ON_STACK_AHAT with a ML-DSA 65 or 87 key)
+ */
+#if DILITHIUM_MODE == 2
+ BUILD_BUG_ON(DILITHIUM_44_AHAT_SIZE !=
+ sizeof(polyvecl) * DILITHIUM44_K);
+ if (ctx->ahat_size < DILITHIUM_44_AHAT_SIZE) {
+ ret = -EOVERFLOW;
+ goto out;
+ }
+#elif DILITHIUM_MODE == 3
+ BUILD_BUG_ON(DILITHIUM_65_AHAT_SIZE !=
+ sizeof(polyvecl) * DILITHIUM65_K);
+ if (ctx->ahat_size < DILITHIUM_65_AHAT_SIZE) {
+ ret = -EOVERFLOW;
+ goto out;
+ }
+#elif DILITHIUM_MODE == 5
+ BUILD_BUG_ON(DILITHIUM_87_AHAT_SIZE !=
+ sizeof(polyvecl) * DILITHIUM87_K);
+ if (ctx->ahat_size < DILITHIUM_87_AHAT_SIZE) {
+ ret = -EOVERFLOW;
+ goto out;
+ }
+#else
+#error "Undefined DILITHIUM_MODE"
+#endif
+
+ polyvec_matrix_expand(mat, rho, ws->poly_uniform_buf);
+ dilithium_print_polyvecl_k(mat,
+ "AHAT - A K x L x N matrix after ExpandA:");
+
+ ctx->ahat_expanded = 1;
+
+out:
+ LC_RELEASE_MEM(ws);
+ return ret;
+}
+
+static int dilithium_sign_internal(struct dilithium_sig *sig,
+ const struct dilithium_sk *sk,
+ struct dilithium_ctx *ctx,
+ struct crypto_rng *rng_ctx)
+{
+ int ret;
+
+ if (!ctx->ahat)
+ return dilithium_sign_internal_noahat(sig, sk, ctx, rng_ctx);
+
+ if (!ctx->ahat_expanded) {
+ ret = dilithium_sk_expand_impl(sk, ctx);
+ if (ret < 0)
+ goto out;
+ }
+
+ ret = dilithium_sign_internal_ahat(sig, sk, ctx, rng_ctx);
+
+out:
+ return ret;
+}
+
+static int dilithium_sign_ctx_impl(struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx,
+ const uint8_t *m, size_t mlen,
+ const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx)
+{
+ uint8_t tr[DILITHIUM_TRBYTES];
+ int ret = 0;
+
+ /* rng_ctx is allowed to be NULL as handled below */
+ if (!sig || !sk || !ctx)
+ return -EINVAL;
+ /* Either the message or the external mu must be provided */
+ if (!m && !ctx->external_mu)
+ return -EINVAL;
+
+ dilithium_print_buffer(m, mlen, "Siggen - Message");
+
+ unpack_sk_tr(tr, sk);
+
+ if (m) {
+ /* Compute mu = CRH(tr, msg) */
+ dilithium_hash_init(ctx);
+ dilithium_hash_update(ctx, tr, DILITHIUM_TRBYTES);
+
+ ret = signature_domain_separation(
+ &ctx->dilithium_hash_ctx, ctx->ml_dsa_internal,
+ ctx->userctx, ctx->userctxlen,
+ m, mlen,
+ ctx->randomizer, ctx->randomizerlen,
+ DILITHIUM_NIST_CATEGORY);
+ if (ret < 0)
+ goto out;
+ }
+
+ ret = dilithium_sign_internal(sig, sk, ctx, rng_ctx);
+
+out:
+ memzero_explicit(tr, sizeof(tr));
+ return ret;
+}
+
+static int dilithium_sign_impl(struct dilithium_sig *sig,
+ const uint8_t *m, size_t mlen,
+ const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx)
+{
+ struct dilithium_ctx *ctx;
+ int ret;
+
+ ctx = dilithium_ctx_alloc();
+ if (IS_ERR(ctx))
+ return PTR_ERR(ctx);
+
+ ret = dilithium_sign_ctx_impl(sig, ctx, m, mlen, sk, rng_ctx);
+
+ dilithium_ctx_zero_free(ctx);
+ return ret;
+}
+
+static int dilithium_sign_init_impl(struct dilithium_ctx *ctx,
+ const struct dilithium_sk *sk)
+{
+ uint8_t tr[DILITHIUM_TRBYTES];
+
+ /* rng_ctx is allowed to be NULL as handled below */
+ if (!ctx || !sk)
+ return -EINVAL;
+
+ /* Require the use of SHAKE256 */
+ if (!dilithium_hash_check_blocksize(ctx, SHAKE256_BLOCK_SIZE))
+ return -EOPNOTSUPP;
+
+ unpack_sk_tr(tr, sk);
+
+ /* Compute mu = CRH(tr, msg) */
+ dilithium_hash_init(ctx);
+ dilithium_hash_update(ctx, tr, DILITHIUM_TRBYTES);
+ memzero_explicit(tr, sizeof(tr));
+
+ return signature_domain_separation(
+ &ctx->dilithium_hash_ctx, ctx->ml_dsa_internal,
+ ctx->userctx, ctx->userctxlen,
+ NULL, 0,
+ ctx->randomizer, ctx->randomizerlen,
+ DILITHIUM_NIST_CATEGORY);
+}
+
+static int dilithium_sign_update_impl(struct dilithium_ctx *ctx,
+ const uint8_t *m, size_t mlen)
+{
+ if (!ctx || !m)
+ return -EINVAL;
+
+ /* Compute CRH(tr, msg) */
+ dilithium_hash_update(ctx, m, mlen);
+
+ return 0;
+}
+
+static int dilithium_sign_final_impl(struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_sk *sk,
+ struct crypto_rng *rng_ctx)
+{
+ int ret = 0;
+
+ /* rng_ctx is allowed to be NULL as handled below */
+ if (!sig || !ctx || !sk) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = dilithium_sign_internal(sig, sk, ctx, rng_ctx);
+
+out:
+ dilithium_ctx_zero(ctx);
+ return ret;
+}
+
+static int dilithium_verify_internal_ahat(const struct dilithium_sig *sig,
+ const struct dilithium_pk *pk,
+ struct dilithium_ctx *ctx)
+{
+ struct workspace_verify {
+ union {
+ poly cp;
+ } matrix;
+ polyveck w1;
+ union {
+ polyveck t1, h;
+ polyvecl z;
+ uint8_t mu[DILITHIUM_CRHBYTES];
+ union {
+ uint8_t coeffs[round_up(DILITHIUM_CTILDE_BYTES, 8)];
+ } __aligned(8) c2;
+ } buf;
+
+ union {
+ poly polyvecl_pointwise_acc_montgomery_buf;
+ uint8_t buf[DILITHIUM_K *
+ DILITHIUM_POLYW1_PACKEDBYTES];
+ uint8_t poly_challenge_buf[POLY_CHALLENGE_BYTES];
+ } tmp;
+ };
+ /* The first bytes of the signature is c~ and thus contains c1. */
+ const uint8_t *c1 = sig->sig;
+ const polyvecl *mat = ctx->ahat;
+ int ret = 0;
+ LC_DECLARE_MEM(ws, struct workspace_verify, sizeof(uint64_t));
+
+ /* AHat must be present at this time */
+ if (!mat) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ unpack_sig_z(&ws->buf.z, sig);
+ if (polyvecl_chknorm(&ws->buf.z,
+ DILITHIUM_GAMMA1 - DILITHIUM_BETA)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ polyvecl_ntt(&ws->buf.z);
+ polyvec_matrix_pointwise_montgomery(
+ &ws->w1, mat, &ws->buf.z,
+ &ws->tmp.polyvecl_pointwise_acc_montgomery_buf);
+
+ /* Matrix-vector multiplication; compute Az - c2^dt1 */
+ poly_challenge(&ws->matrix.cp, c1, ws->tmp.poly_challenge_buf);
+ poly_ntt(&ws->matrix.cp);
+
+ unpack_pk_t1(&ws->buf.t1, pk);
+ polyveck_shiftl(&ws->buf.t1);
+ polyveck_ntt(&ws->buf.t1);
+ polyveck_pointwise_poly_montgomery(&ws->buf.t1, &ws->matrix.cp,
+ &ws->buf.t1);
+
+ polyveck_sub(&ws->w1, &ws->w1, &ws->buf.t1);
+ polyveck_reduce(&ws->w1);
+ polyveck_invntt_tomont(&ws->w1);
+
+ /* Reconstruct w1 */
+ polyveck_caddq(&ws->w1);
+ dilithium_print_polyveck(&ws->w1,
+ "Sigver - W K x N matrix before hint:");
+
+ if (unpack_sig_h(&ws->buf.h, sig))
+ return -EINVAL;
+ dilithium_print_polyveck(&ws->buf.h, "Siggen - H K x N matrix:");
+
+ polyveck_use_hint(&ws->w1, &ws->w1, &ws->buf.h);
+ dilithium_print_polyveck(&ws->w1,
+ "Sigver - W K x N matrix after hint:");
+ polyveck_pack_w1(ws->tmp.buf, &ws->w1);
+ dilithium_print_buffer(ws->tmp.buf,
+ DILITHIUM_K * DILITHIUM_POLYW1_PACKEDBYTES,
+ "Sigver - W after w1Encode");
+
+ if (ctx->external_mu) {
+ if (ctx->external_mu_len != DILITHIUM_CRHBYTES)
+ return -EINVAL;
+
+ /* Call random oracle and verify challenge */
+ dilithium_hash_init(ctx);
+ dilithium_hash_update(ctx, ctx->external_mu, DILITHIUM_CRHBYTES);
+ } else {
+ dilithium_hash_final(ctx, ws->buf.mu, DILITHIUM_CRHBYTES);
+
+ /* Call random oracle and verify challenge */
+ dilithium_hash_init(ctx);
+ dilithium_hash_update(ctx, ws->buf.mu, DILITHIUM_CRHBYTES);
+ }
+
+ dilithium_hash_finup(ctx,
+ ws->tmp.buf, DILITHIUM_K * DILITHIUM_POLYW1_PACKEDBYTES,
+ ws->buf.c2.coeffs, DILITHIUM_CTILDE_BYTES);
+
+ /* Signature verification operation */
+ if (memcmp(c1, ws->buf.c2.coeffs, DILITHIUM_CTILDE_BYTES) != 0)
+ ret = -EBADMSG;
+
+out:
+ LC_RELEASE_MEM(ws);
+ return ret;
+}
+
+static int
+dilithium_verify_internal_noahat(const struct dilithium_sig *sig,
+ const struct dilithium_pk *pk,
+ struct dilithium_ctx *ctx)
+{
+ struct workspace_verify {
+ polyvecl mat[DILITHIUM_K];
+ uint8_t poly_uniform_buf[WS_POLY_UNIFORM_BUF_SIZE];
+ };
+ /* The first bytes of the key is rho. */
+ const uint8_t *rho = pk->pk;
+ int ret = 0;
+ LC_DECLARE_MEM(ws, struct workspace_verify, sizeof(uint64_t));
+
+ polyvec_matrix_expand(ws->mat, rho, ws->poly_uniform_buf);
+
+ /* Temporarily set the pointer */
+ ctx->ahat = ws->mat;
+
+ ret = dilithium_verify_internal_ahat(sig, pk, ctx);
+
+ ctx->ahat = NULL;
+ LC_RELEASE_MEM(ws);
+ return ret;
+}
+
+static int dilithium_pk_expand_impl(const struct dilithium_pk *pk,
+ struct dilithium_ctx *ctx)
+{
+ struct workspace_verify {
+ uint8_t poly_uniform_buf[WS_POLY_UNIFORM_BUF_SIZE];
+ };
+ /* The first bytes of the key is rho. */
+ const uint8_t *rho = pk->pk;
+ polyvecl *mat = ctx->ahat;
+ int ret = 0;
+ LC_DECLARE_MEM(ws, struct workspace_verify, sizeof(uint64_t));
+
+ /*
+ * Runtime sanity check ensures that the allocated context has
+ * sufficient size (e.g. not that caller used, say,
+ * DILITHIUM_44_CTX_ON_STACK_AHAT with a ML-DSA 65 or 87 key)
+ */
+#if DILITHIUM_MODE == 2
+ if (ctx->ahat_size < DILITHIUM_44_AHAT_SIZE) {
+ ret = -EOVERFLOW;
+ goto out;
+ }
+#elif DILITHIUM_MODE == 3
+ if (ctx->ahat_size < DILITHIUM_65_AHAT_SIZE) {
+ ret = -EOVERFLOW;
+ goto out;
+ }
+#elif DILITHIUM_MODE == 5
+ if (ctx->ahat_size < DILITHIUM_87_AHAT_SIZE) {
+ ret = -EOVERFLOW;
+ goto out;
+ }
+#else
+#error "Undefined DILITHIUM_MODE"
+#endif
+
+ polyvec_matrix_expand(mat, rho, ws->poly_uniform_buf);
+ ctx->ahat_expanded = 1;
+
+out:
+ LC_RELEASE_MEM(ws);
+ return ret;
+}
+
+static int dilithium_verify_internal(const struct dilithium_sig *sig,
+ const struct dilithium_pk *pk,
+ struct dilithium_ctx *ctx)
+{
+ int ret;
+
+ if (!ctx->ahat)
+ return dilithium_verify_internal_noahat(sig, pk, ctx);
+
+ if (!ctx->ahat_expanded) {
+ ret = dilithium_pk_expand_impl(pk, ctx);
+ if (ret < 0)
+ goto out;
+ }
+
+ ret = dilithium_verify_internal_ahat(sig, pk, ctx);
+
+out:
+ return ret;
+}
+
+static int dilithium_verify_ctx_impl(const struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx,
+ const uint8_t *m, size_t mlen,
+ const struct dilithium_pk *pk)
+{
+ uint8_t tr[DILITHIUM_TRBYTES];
+ int ret = 0;
+
+ if (!sig || !pk || !ctx)
+ return -EINVAL;
+
+ /* Either the message or the external mu must be provided */
+ if (!m && !ctx->external_mu)
+ return -EINVAL;
+
+ /* Make sure that ->mu is large enough for ->tr */
+ BUILD_BUG_ON(DILITHIUM_TRBYTES > DILITHIUM_CRHBYTES);
+
+ /* Compute CRH(H(rho, t1), msg) */
+ shake256(pk->pk, DILITHIUM_PUBLICKEYBYTES, tr,
+ DILITHIUM_TRBYTES);
+
+ if (m) {
+ dilithium_hash_init(ctx);
+ dilithium_hash_update(ctx, tr, DILITHIUM_TRBYTES);
+ ret = signature_domain_separation(
+ &ctx->dilithium_hash_ctx, ctx->ml_dsa_internal,
+ ctx->userctx, ctx->userctxlen,
+ m, mlen,
+ ctx->randomizer, ctx->randomizerlen,
+ DILITHIUM_NIST_CATEGORY);
+ if (ret < 0)
+ goto out;
+ }
+
+ ret = dilithium_verify_internal(sig, pk, ctx);
+
+out:
+ memzero_explicit(tr, sizeof(tr));
+ return ret;
+}
+
+static int dilithium_verify_impl(const struct dilithium_sig *sig,
+ const uint8_t *m, size_t mlen,
+ const struct dilithium_pk *pk)
+{
+ struct dilithium_ctx *ctx;
+ int ret;
+
+ ctx = dilithium_ctx_alloc();
+ if (IS_ERR(ctx))
+ return PTR_ERR(ctx);
+
+ ret = dilithium_verify_ctx_impl(sig, ctx, m, mlen, pk);
+
+ dilithium_ctx_zero_free(ctx);
+ return ret;
+}
+
+static int dilithium_verify_init_impl(struct dilithium_ctx *ctx,
+ const struct dilithium_pk *pk)
+{
+ uint8_t mu[DILITHIUM_TRBYTES];
+
+ /* rng_ctx is allowed to be NULL as handled below */
+ if (!ctx || !pk)
+ return -EINVAL;
+
+ /* Require the use of SHAKE256 */
+ if (!dilithium_hash_check_blocksize(ctx, SHAKE256_BLOCK_SIZE))
+ return -EOPNOTSUPP;
+
+ /* Compute CRH(H(rho, t1), msg) */
+ shake256(pk->pk, DILITHIUM_PUBLICKEYBYTES, mu,
+ DILITHIUM_TRBYTES);
+
+ dilithium_hash_init(ctx);
+ dilithium_hash_update(ctx, mu, DILITHIUM_TRBYTES);
+ memzero_explicit(mu, sizeof(mu));
+
+ return signature_domain_separation(
+ &ctx->dilithium_hash_ctx, ctx->ml_dsa_internal,
+ ctx->userctx, ctx->userctxlen,
+ NULL, 0,
+ ctx->randomizer, ctx->randomizerlen,
+ DILITHIUM_NIST_CATEGORY);
+}
+
+static int dilithium_verify_update_impl(struct dilithium_ctx *ctx,
+ const uint8_t *m, size_t mlen)
+{
+ if (!ctx || !m)
+ return -EINVAL;
+
+ /* Compute CRH(H(rho, t1), msg) */
+ dilithium_hash_update(ctx, m, mlen);
+
+ return 0;
+}
+
+static int dilithium_verify_final_impl(const struct dilithium_sig *sig,
+ struct dilithium_ctx *ctx,
+ const struct dilithium_pk *pk)
+{
+ int ret = 0;
+
+ if (!sig || !ctx || !pk) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = dilithium_verify_internal(sig, pk, ctx);
+
+out:
+ dilithium_ctx_zero(ctx);
+ return ret;
+}
+
+#endif /* DILITHIUM_SIGNATURE_IMPL_H */
diff --git a/crypto/ml_dsa/dilithium_type.h b/crypto/ml_dsa/dilithium_type.h
new file mode 100644
index 000000000000..f9f7ffa2cd38
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_type.h
@@ -0,0 +1,259 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2024 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef DILITHIUM_TYPE_H
+#define DILITHIUM_TYPE_H
+
+#include "dilithium.h"
+
+/*
+ * This define replaces all symbol names accordingly to allow double compilation
+ * of the same code base.
+ *
+ * Due to the replacement operation, this header file must be included as the
+ * first header file in the entire stack.
+ *
+ * This file can easily be replaced with dilithium.h to achieve the common
+ * functionality without symbol duplication. But in this case, only the
+ * Dilithium security strength is compiled defined in dilithium.h. Duplicate
+ * compilation different sizes would not be possible.
+ */
+#ifdef DILITHIUM_TYPE_65
+#define DILITHIUM_F(name) dilithium_65_##name
+#define dilithium_pk dilithium_65_pk
+#define dilithium_sk dilithium_65_sk
+#define dilithium_sig dilithium_65_sig
+
+#include "dilithium_65.h"
+
+#elif defined DILITHIUM_TYPE_44
+#define DILITHIUM_F(name) dilithium_44_##name
+#define dilithium_pk dilithium_44_pk
+#define dilithium_sk dilithium_44_sk
+#define dilithium_sig dilithium_44_sig
+
+#include "dilithium_44.h"
+
+#else
+#define DILITHIUM_F(name) dilithium_87_##name
+#define dilithium_pk dilithium_87_pk
+#define dilithium_sk dilithium_87_sk
+#define dilithium_sig dilithium_87_sig
+
+#include "dilithium_87.h"
+
+#endif
+
+/*
+ * The following defines simply allow duplicate compilation of the
+ * respective functions.
+ */
+#define dilithium_sign DILITHIUM_F(sign)
+#define dilithium_sign_ctx DILITHIUM_F(sign_ctx)
+#define dilithium_sign_init DILITHIUM_F(sign_init)
+#define dilithium_sign_update DILITHIUM_F(sign_update)
+#define dilithium_sign_final DILITHIUM_F(sign_final)
+#define dilithium_verify DILITHIUM_F(verify)
+#define dilithium_verify_ctx DILITHIUM_F(verify_ctx)
+#define dilithium_verify_init DILITHIUM_F(verify_init)
+#define dilithium_verify_update DILITHIUM_F(verify_update)
+#define dilithium_verify_final DILITHIUM_F(verify_final)
+
+#define dilithium_sign_c DILITHIUM_F(sign_c)
+#define dilithium_sign_ctx_c DILITHIUM_F(sign_ctx_c)
+#define dilithium_sign_init_c DILITHIUM_F(sign_init_c)
+#define dilithium_sign_update_c DILITHIUM_F(sign_update_c)
+#define dilithium_sign_final_c DILITHIUM_F(sign_final_c)
+#define dilithium_verify_c DILITHIUM_F(verify_c)
+#define dilithium_verify_ctx_c DILITHIUM_F(verify_ctx_c)
+#define dilithium_verify_init_c DILITHIUM_F(verify_init_c)
+#define dilithium_verify_update_c DILITHIUM_F(verify_update_c)
+#define dilithium_verify_final_c DILITHIUM_F(verify_final_c)
+
+#define ntt DILITHIUM_F(ntt)
+#define invntt_tomont DILITHIUM_F(invntt_tomont)
+#define poly_chknorm DILITHIUM_F(poly_chknorm)
+#define poly_uniform DILITHIUM_F(poly_uniform)
+#define poly_uniform_eta DILITHIUM_F(poly_uniform_eta)
+#define poly_uniform_gamma1 DILITHIUM_F(poly_uniform_gamma1)
+#define polyz_unpack DILITHIUM_F(polyz_unpack)
+#define poly_challenge DILITHIUM_F(poly_challenge)
+#define polyeta_pack DILITHIUM_F(polyeta_pack)
+#define polyeta_unpack DILITHIUM_F(polyeta_unpack)
+#define polyt1_pack DILITHIUM_F(polyt1_pack)
+#define polyt0_pack DILITHIUM_F(polyt0_pack)
+#define polyt0_unpack DILITHIUM_F(polyt0_unpack)
+#define polyz_pack DILITHIUM_F(polyz_pack)
+#define polyw1_pack DILITHIUM_F(polyw1_pack)
+#define power2round DILITHIUM_F(power2round)
+#define decompose DILITHIUM_F(decompose)
+#define make_hint DILITHIUM_F(make_hint)
+#define use_hint DILITHIUM_F(use_hint)
+
+#define dilithium_print_buffer DILITHIUM_F(print_buffer)
+#define dilithium_print_polyvecl_k DILITHIUM_F(print_polyvecl_k)
+#define dilithium_print_polyvecl DILITHIUM_F(print_polyvecl)
+#define dilithium_print_polyveck DILITHIUM_F(print_polyveck)
+#define dilithium_print_poly DILITHIUM_F(print_poly)
+
+/* AVX2 Implementation */
+#define dilithium_invntt_avx DILITHIUM_F(invntt_avx)
+#define dilithium_ntt_avx DILITHIUM_F(ntt_avx)
+#define dilithium_nttunpack_avx DILITHIUM_F(nttunpack_avx)
+#define dilithium_pointwise_avx DILITHIUM_F(pointwise_avx)
+#define dilithium_pointwise_acc_avx DILITHIUM_F(pointwise_acc_avx)
+#define poly_reduce_avx DILITHIUM_F(poly_reduce_avx)
+#define poly_caddq_avx DILITHIUM_F(poly_caddq_avx)
+#define poly_add_avx DILITHIUM_F(poly_add_avx)
+#define poly_sub_avx DILITHIUM_F(poly_sub_avx)
+#define poly_shiftl_avx DILITHIUM_F(poly_shiftl_avx)
+#define poly_chknorm_avx DILITHIUM_F(poly_chknorm_avx)
+#define poly_uniform_4x_avx DILITHIUM_F(poly_uniform_4x_avx)
+#define poly_uniform_eta_4x_avx DILITHIUM_F(poly_uniform_eta_4x_avx)
+#define poly_uniform_gamma1_4x_avx DILITHIUM_F(poly_uniform_gamma1_4x_avx)
+#define polyz_unpack_avx DILITHIUM_F(polyz_unpack_avx)
+#define poly_challenge_avx DILITHIUM_F(poly_challenge_avx)
+#define polyeta_pack_avx DILITHIUM_F(polyeta_pack_avx)
+#define polyeta_unpack_avx DILITHIUM_F(polyeta_unpack_avx)
+#define polyt1_pack_avx DILITHIUM_F(polyt1_pack_avx)
+#define polyt1_unpack_avx DILITHIUM_F(polyt1_unpack_avx)
+#define polyt0_pack_avx DILITHIUM_F(polyt0_pack_avx)
+#define polyt0_unpack_avx DILITHIUM_F(polyt0_unpack_avx)
+#define polyz_pack_avx DILITHIUM_F(polyz_pack_avx)
+#define polyw1_pack_avx DILITHIUM_F(polyw1_pack_avx)
+#define polyvec_matrix_expand DILITHIUM_F(polyvec_matrix_expand)
+#define polyvec_matrix_expand_row0 DILITHIUM_F(polyvec_matrix_expand_row0)
+#define polyvec_matrix_expand_row1 DILITHIUM_F(polyvec_matrix_expand_row1)
+#define polyvec_matrix_expand_row2 DILITHIUM_F(polyvec_matrix_expand_row2)
+#define polyvec_matrix_expand_row3 DILITHIUM_F(polyvec_matrix_expand_row3)
+#define polyvec_matrix_expand_row4 DILITHIUM_F(polyvec_matrix_expand_row4)
+#define polyvec_matrix_expand_row5 DILITHIUM_F(polyvec_matrix_expand_row5)
+#define polyvec_matrix_expand_row6 DILITHIUM_F(polyvec_matrix_expand_row6)
+#define polyvec_matrix_expand_row7 DILITHIUM_F(polyvec_matrix_expand_row7)
+#define rej_uniform_avx DILITHIUM_F(rej_uniform_avx)
+#define rej_eta_avx DILITHIUM_F(rej_eta_avx)
+#define idxlut DILITHIUM_F(idxlut)
+#define power2round_avx DILITHIUM_F(power2round_avx)
+#define decompose_avx DILITHIUM_F(decompose_avx)
+#define make_hint_avx DILITHIUM_F(make_hint_avx)
+#define use_hint_avx DILITHIUM_F(use_hint_avx)
+#define dilithium_sign_avx2 DILITHIUM_F(sign_avx2)
+#define dilithium_sign_ctx_avx2 DILITHIUM_F(sign_ctx_avx2)
+#define dilithium_sign_init_avx2 DILITHIUM_F(sign_init_avx2)
+#define dilithium_sign_update_avx2 DILITHIUM_F(sign_update_avx2)
+#define dilithium_sign_final_avx2 DILITHIUM_F(sign_final_avx2)
+#define dilithium_verify_avx2 DILITHIUM_F(verify_avx2)
+#define dilithium_verify_ctx_avx2 DILITHIUM_F(verify_ctx_avx2)
+#define dilithium_verify_init_avx2 DILITHIUM_F(verify_init_avx2)
+#define dilithium_verify_update_avx2 DILITHIUM_F(verify_update_avx2)
+#define dilithium_verify_final_avx2 DILITHIUM_F(verify_final_avx2)
+
+/* ARMv8 Implementation */
+#define intt_SIMD_top_armv8 DILITHIUM_F(intt_SIMD_top_armv8)
+#define intt_SIMD_bot_armv8 DILITHIUM_F(intt_SIMD_bot_armv8)
+#define ntt_SIMD_top_armv8 DILITHIUM_F(ntt_SIMD_top_armv8)
+#define ntt_SIMD_bot_armv8 DILITHIUM_F(ntt_SIMD_bot_armv8)
+#define poly_uniformx2 DILITHIUM_F(poly_uniformx2)
+#define poly_uniform_etax2 DILITHIUM_F(poly_uniform_etax2)
+#define poly_uniform_gamma1x2 DILITHIUM_F(poly_uniform_gamma1x2)
+#define armv8_10_to_32 DILITHIUM_F(armv8_10_to_32)
+#define poly_reduce_armv8 DILITHIUM_F(poly_reduce_armv8)
+#define poly_caddq_armv8 DILITHIUM_F(poly_caddq_armv8)
+#define poly_power2round_armv8 DILITHIUM_F(poly_power2round_armv8)
+#define poly_pointwise_montgomery_armv8 \
+ DILITHIUM_F(poly_pointwise_montgomery_armv8)
+#define polyvecl_pointwise_acc_montgomery_armv8 \
+ DILITHIUM_F(polyvecl_pointwise_acc_montgomery_armv8)
+#define dilithium_sign_armv8 DILITHIUM_F(sign_armv8)
+#define dilithium_sign_ctx_armv8 DILITHIUM_F(sign_ctx_armv8)
+#define dilithium_sign_init_armv8 DILITHIUM_F(sign_init_armv8)
+#define dilithium_sign_update_armv8 DILITHIUM_F(sign_update_armv8)
+#define dilithium_sign_final_armv8 DILITHIUM_F(sign_final_armv8)
+#define dilithium_verify_armv8 DILITHIUM_F(verify_armv8)
+#define dilithium_verify_ctx_armv8 DILITHIUM_F(verify_ctx_armv8)
+#define dilithium_verify_init_armv8 DILITHIUM_F(verify_init_armv8)
+#define dilithium_verify_update_armv8 DILITHIUM_F(verify_update_armv8)
+#define dilithium_verify_final_armv8 DILITHIUM_F(verify_final_armv8)
+
+/* ARMv7 Implementation */
+#define armv7_ntt_asm_smull DILITHIUM_F(armv7_ntt_asm_smull)
+#define armv7_inv_ntt_asm_smull DILITHIUM_F(armv7_inv_ntt_asm_smull)
+#define armv7_poly_pointwise_invmontgomery_asm_smull \
+ DILITHIUM_F(armv7_poly_pointwise_invmontgomery_asm_smull)
+#define armv7_poly_pointwise_acc_invmontgomery_asm_smull \
+ DILITHIUM_F(armv7_poly_pointwise_acc_invmontgomery_asm_smull)
+#define poly_uniform_armv7 DILITHIUM_F(poly_uniform_armv7)
+#define armv7_poly_reduce_asm DILITHIUM_F(armv7_poly_reduce_asm)
+#define armv7_rej_uniform_asm DILITHIUM_F(armv7_rej_uniform_asm)
+#define dilithium_sign_armv7 DILITHIUM_F(sign_armv7)
+#define dilithium_sign_ctx_armv7 DILITHIUM_F(sign_ctx_armv7)
+#define dilithium_sign_init_armv7 DILITHIUM_F(sign_init_armv7)
+#define dilithium_sign_update_armv7 DILITHIUM_F(sign_update_armv7)
+#define dilithium_sign_final_armv7 DILITHIUM_F(sign_final_armv7)
+#define dilithium_verify_armv7 DILITHIUM_F(verify_armv7)
+#define dilithium_verify_ctx_armv7 DILITHIUM_F(verify_ctx_armv7)
+#define dilithium_verify_init_armv7 DILITHIUM_F(verify_init_armv7)
+#define dilithium_verify_update_armv7 DILITHIUM_F(verify_update_armv7)
+#define dilithium_verify_final_armv7 DILITHIUM_F(verify_final_armv7)
+
+/* RISCV 64 ASM Implementation */
+#define dilithium_sign_riscv64 DILITHIUM_F(sign_riscv64)
+#define dilithium_sign_ctx_riscv64 DILITHIUM_F(sign_ctx_riscv64)
+#define dilithium_sign_init_riscv64 DILITHIUM_F(sign_init_riscv64)
+#define dilithium_sign_update_riscv64 DILITHIUM_F(sign_update_riscv64)
+#define dilithium_sign_final_riscv64 DILITHIUM_F(sign_final_riscv64)
+#define dilithium_verify_riscv64 DILITHIUM_F(verify_riscv64)
+#define dilithium_verify_ctx_riscv64 DILITHIUM_F(verify_ctx_riscv64)
+#define dilithium_verify_init_riscv64 DILITHIUM_F(verify_init_riscv64)
+#define dilithium_verify_update_riscv64 DILITHIUM_F(verify_update_riscv64)
+#define dilithium_verify_final_riscv64 DILITHIUM_F(verify_final_riscv64)
+#define dilithium_ntt_8l_rv64im DILITHIUM_F(ntt_8l_rv64im)
+#define dilithium_intt_8l_rv64im DILITHIUM_F(intt_8l_rv64im)
+#define dilithium_poly_basemul_8l_init_rv64im \
+ DILITHIUM_F(poly_basemul_8l_init_rv64im)
+#define dilithium_poly_basemul_8l_acc_rv64im \
+ DILITHIUM_F(poly_basemul_8l_acc_rv64im)
+#define dilithium_poly_basemul_8l_acc_end_rv64im \
+ DILITHIUM_F(poly_basemul_8l_acc_end_rv64im)
+#define dilithium_poly_basemul_8l_rv64im DILITHIUM_F(poly_basemul_8l_rv64im)
+#define dilithium_poly_reduce_rv64im DILITHIUM_F(poly_reduce_rv64im)
+
+/* RISCV 64 RVV Implementation */
+#define dilithium_sign_riscv64_rvv DILITHIUM_F(sign_riscv64_rvv)
+#define dilithium_sign_ctx_riscv64_rvv DILITHIUM_F(sign_ctx_riscv64_rvv)
+#define dilithium_sign_init_riscv64_rvv DILITHIUM_F(sign_init_riscv64_rvv)
+#define dilithium_sign_update_riscv64_rvv \
+ DILITHIUM_F(sign_update_riscv64_rvv)
+#define dilithium_sign_final_riscv64_rvv DILITHIUM_F(sign_final_riscv64_rvv)
+#define dilithium_verify_riscv64_rvv DILITHIUM_F(verify_riscv64_rvv)
+#define dilithium_verify_ctx_riscv64_rvv DILITHIUM_F(verify_ctx_riscv64_rvv)
+#define dilithium_verify_init_riscv64_rvv \
+ DILITHIUM_F(verify_init_riscv64_rvv)
+#define dilithium_verify_update_riscv64_rvv \
+ DILITHIUM_F(verify_update_riscv64_rvv)
+#define dilithium_verify_final_riscv64_rvv \
+ DILITHIUM_F(verify_final_riscv64_rvv)
+#define dilithium_ntt_8l_rvv DILITHIUM_F(ntt_8l_rvv)
+#define dilithium_intt_8l_rvv DILITHIUM_F(intt_8l_rvv)
+#define dilithium_poly_basemul_8l_rvv DILITHIUM_F(poly_basemul_8l_rvv)
+#define dilithium_poly_basemul_acc_8l_rvv DILITHIUM_F(poly_basemul_acc_8l_rvv)
+#define dilithium_ntt2normal_order_8l_rvv DILITHIUM_F(ntt2normal_order_8l_rvv)
+#define dilithium_normal2ntt_order_8l_rvv DILITHIUM_F(normal2ntt_order_8l_rvv)
+#define dilithium_poly_reduce_rvv DILITHIUM_F(poly_reduce_rvv)
+
+#endif /* DILITHIUM_TYPE_H */
diff --git a/crypto/ml_dsa/dilithium_zetas.c b/crypto/ml_dsa/dilithium_zetas.c
new file mode 100644
index 000000000000..f0e9203f35dd
--- /dev/null
+++ b/crypto/ml_dsa/dilithium_zetas.c
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+/*
+ * This code is derived in parts from the code distribution provided with
+ * https://github.com/pq-crystals/dilithium
+ *
+ * That code is released under Public Domain
+ * (https://creativecommons.org/share-your-work/public-domain/cc0/);
+ * or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html).
+ */
+
+#include "dilithium.h"
+
+const int32_t dilithium_zetas[DILITHIUM_N] = {
+ 0, 25847, -2608894, -518909, 237124, -777960, -876248,
+ 466468, 1826347, 2353451, -359251, -2091905, 3119733, -2884855,
+ 3111497, 2680103, 2725464, 1024112, -1079900, 3585928, -549488,
+ -1119584, 2619752, -2108549, -2118186, -3859737, -1399561, -3277672,
+ 1757237, -19422, 4010497, 280005, 2706023, 95776, 3077325,
+ 3530437, -1661693, -3592148, -2537516, 3915439, -3861115, -3043716,
+ 3574422, -2867647, 3539968, -300467, 2348700, -539299, -1699267,
+ -1643818, 3505694, -3821735, 3507263, -2140649, -1600420, 3699596,
+ 811944, 531354, 954230, 3881043, 3900724, -2556880, 2071892,
+ -2797779, -3930395, -1528703, -3677745, -3041255, -1452451, 3475950,
+ 2176455, -1585221, -1257611, 1939314, -4083598, -1000202, -3190144,
+ -3157330, -3632928, 126922, 3412210, -983419, 2147896, 2715295,
+ -2967645, -3693493, -411027, -2477047, -671102, -1228525, -22981,
+ -1308169, -381987, 1349076, 1852771, -1430430, -3343383, 264944,
+ 508951, 3097992, 44288, -1100098, 904516, 3958618, -3724342,
+ -8578, 1653064, -3249728, 2389356, -210977, 759969, -1316856,
+ 189548, -3553272, 3159746, -1851402, -2409325, -177440, 1315589,
+ 1341330, 1285669, -1584928, -812732, -1439742, -3019102, -3881060,
+ -3628969, 3839961, 2091667, 3407706, 2316500, 3817976, -3342478,
+ 2244091, -2446433, -3562462, 266997, 2434439, -1235728, 3513181,
+ -3520352, -3759364, -1197226, -3193378, 900702, 1859098, 909542,
+ 819034, 495491, -1613174, -43260, -522500, -655327, -3122442,
+ 2031748, 3207046, -3556995, -525098, -768622, -3595838, 342297,
+ 286988, -2437823, 4108315, 3437287, -3342277, 1735879, 203044,
+ 2842341, 2691481, -2590150, 1265009, 4055324, 1247620, 2486353,
+ 1595974, -3767016, 1250494, 2635921, -3548272, -2994039, 1869119,
+ 1903435, -1050970, -1333058, 1237275, -3318210, -1430225, -451100,
+ 1312455, 3306115, -1962642, -1279661, 1917081, -2546312, -1374803,
+ 1500165, 777191, 2235880, 3406031, -542412, -2831860, -1671176,
+ -1846953, -2584293, -3724270, 594136, -3776993, -2013608, 2432395,
+ 2454455, -164721, 1957272, 3369112, 185531, -1207385, -3183426,
+ 162844, 1616392, 3014001, 810149, 1652634, -3694233, -1799107,
+ -3038916, 3523897, 3866901, 269760, 2213111, -975884, 1717735,
+ 472078, -426683, 1723600, -1803090, 1910376, -1667432, -1104333,
+ -260646, -3833893, -2939036, -2235985, -420899, -2286327, 183443,
+ -976891, 1612842, -3545687, -554416, 3919660, -48306, -1362209,
+ 3937738, 1400424, -846154, 1976782
+};
diff --git a/crypto/ml_dsa/fips_mode.h b/crypto/ml_dsa/fips_mode.h
new file mode 100644
index 000000000000..f185f204366e
--- /dev/null
+++ b/crypto/ml_dsa/fips_mode.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef FIPS_MODE_H
+#define FIPS_MODE_H
+
+/**
+ * @brief Is FIPS 140 Mode enabled?
+ *
+ * return 0 == false, 1 == true
+ */
+static inline bool fips140_mode_enabled(void)
+{
+ return false;
+}
+
+#define FIPS140_PCT_LOOP(func) \
+ if (fips140_mode_enabled()) { \
+ unsigned int __i; \
+ int __ret; \
+ \
+ for (__i = 0; __i < 5; __i++) { \
+ __ret = func; \
+ if (!__ret) \
+ return __ret; \
+ } \
+ WARN_ON(0); \
+ }
+
+#endif /* FIPS_MODE_H */
diff --git a/crypto/ml_dsa/signature_domain_separation.c b/crypto/ml_dsa/signature_domain_separation.c
new file mode 100644
index 000000000000..f0a173797c96
--- /dev/null
+++ b/crypto/ml_dsa/signature_domain_separation.c
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/*
+ * Copyright (C) 2024 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#include <crypto/sha3.h>
+#include <crypto/hash.h>
+#include "signature_domain_separation.h"
+
+static const char *signature_prehash_type;
+
+/* RFC4055 2.16.840.1.101.3.4.2.1 */
+static const uint8_t sha256_oid_der[] __maybe_unused = { 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03,
+ 0x04, 0x02, 0x01 };
+/* RFC4055 2.16.840.1.101.3.4.2.2 */
+static const uint8_t sha384_oid_der[] __maybe_unused = { 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03,
+ 0x04, 0x02, 0x02 };
+/* RFC4055 2.16.840.1.101.3.4.2.3 */
+static const uint8_t sha512_oid_der[] __maybe_unused = { 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03,
+ 0x04, 0x02, 0x03 };
+
+/*
+ * https://lamps-wg.github.io/draft-composite-sigs/draft-ietf-lamps-pq-composite-sigs.html
+ */
+static const uint8_t sha3_256_oid_der[] __maybe_unused = {
+ 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x08
+};
+static const uint8_t sha3_384_oid_der[] __maybe_unused = {
+ 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x09
+};
+static const uint8_t sha3_512_oid_der[] __maybe_unused = {
+ 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0a
+};
+
+/* RFC8692 2.16.840.1.101.3.4.2.11 */
+static const uint8_t shake128_oid_der[] __maybe_unused = {
+ 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0B
+};
+
+/* RFC8692 2.16.840.1.101.3.4.2.11 */
+static const uint8_t shake256_oid_der[] __maybe_unused = {
+ 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0C
+};
+
+int signature_ph_oids(struct shake256_ctx *hash_ctx, size_t mlen,
+ unsigned int nist_category)
+{
+ /* If no hash is supplied, we have no HashML-DSA */
+ if (!signature_prehash_type)
+ return 0;
+
+ /*
+ * The signature init/update/final operation will not work with the
+ * check of mlen, as only when _final is invoked, the message length
+ * is known.
+ *
+ * As defined in FIPS 204, section 5.4 requires
+ * "... the digest that is signed needs to be generated using an
+ * approved hash function or XOF (e.g., from FIPS 180 or FIPS 202) that
+ * provides at least λ bits of classical security strength against both
+ * collision and second preimage attacks ... Obtaining at least λ bits
+ * of classical security strength against collision attacks requires
+ * that the digest to be signed be at least 2λ bits in length."
+ * This requirement implies in the following definitions.
+ */
+ (void)mlen;
+
+ switch (nist_category) {
+ case 1:
+ if (strcmp(signature_prehash_type, "sha256") == 0) {
+ // if (mlen != LC_SHA256_SIZE_DIGEST)
+ // return -EOPNOTSUPP;
+ shake256_update(hash_ctx, sha256_oid_der,
+ sizeof(sha256_oid_der));
+ return 0;
+ }
+ if (strcmp(signature_prehash_type, "sha3-256") == 0) {
+ // if (mlen != LC_SHA3_256_SIZE_DIGEST)
+ // return -EOPNOTSUPP;
+ shake256_update(hash_ctx, sha3_256_oid_der,
+ sizeof(sha3_256_oid_der));
+ return 0;
+ }
+ if (strcmp(signature_prehash_type, "shake128") == 0) {
+ /* FIPS 204 section 5.4.1 */
+ // if (mlen != 32)
+ // return -EOPNOTSUPP;
+ shake256_update(hash_ctx, shake128_oid_der,
+ sizeof(shake128_oid_der));
+ return 0;
+ }
+ /* FALLTHROUGH - Dilithium44 allows the following, too */
+ fallthrough;
+ case 3:
+ if (strcmp(signature_prehash_type, "sha3-384") == 0) {
+ // if (mlen != LC_SHA3_384_SIZE_DIGEST)
+ // return -EOPNOTSUPP;
+ shake256_update(hash_ctx, sha3_384_oid_der,
+ sizeof(sha3_384_oid_der));
+ return 0;
+ }
+ if (strcmp(signature_prehash_type, "sha384") == 0) {
+ // if (mlen != LC_SHA384_SIZE_DIGEST)
+ // return -EOPNOTSUPP;
+ shake256_update(hash_ctx, sha384_oid_der,
+ sizeof(sha384_oid_der));
+ return 0;
+ }
+ /* FALLTHROUGH - Dilithium[44|65] allows the following, too */
+ fallthrough;
+ case 5:
+ if (strcmp(signature_prehash_type, "sha512") == 0) {
+ // if (mlen != LC_SHA512_SIZE_DIGEST)
+ // return -EOPNOTSUPP;
+ shake256_update(hash_ctx, sha512_oid_der,
+ sizeof(sha512_oid_der));
+ return 0;
+ }
+ if (strcmp(signature_prehash_type, "sha3-512") == 0) {
+ // if (mlen != LC_SHA3_512_SIZE_DIGEST)
+ // return -EOPNOTSUPP;
+ shake256_update(hash_ctx, sha3_512_oid_der,
+ sizeof(sha3_512_oid_der));
+ return 0;
+ } else if (strcmp(signature_prehash_type, "shake256") == 0) {
+ /* FIPS 204 section 5.4.1 */
+ /*
+ * TODO: mlen must be >= 64 to comply with the
+ * aforementioned requirement - unfortunately we can
+ * only check mlen at the end of the signature
+ * operation - shall this be implemented?
+ */
+ // if (mlen != 64)
+ // return -EOPNOTSUPP;
+ shake256_update(hash_ctx, shake256_oid_der,
+ sizeof(shake256_oid_der));
+ return 0;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+/* FIPS 204 pre-hash ML-DSA domain separation, but without original message */
+static int standalone_signature_domain_separation(
+ struct shake256_ctx *hash_ctx, const uint8_t *userctx,
+ size_t userctxlen, size_t mlen, unsigned int nist_category)
+{
+ uint8_t domainseparation[2];
+
+ domainseparation[0] = signature_prehash_type ? 1 : 0;
+ domainseparation[1] = (uint8_t)userctxlen;
+
+ shake256_update(hash_ctx, domainseparation, sizeof(domainseparation));
+ shake256_update(hash_ctx, userctx, userctxlen);
+
+ return signature_ph_oids(hash_ctx, mlen, nist_category);
+}
+
+/*
+ * Domain separation as required by:
+ *
+ * FIPS 204 pre-hash ML-DSA: randomizer is NULL
+ * Composite ML-DSA draft 5: randomizer is set
+ */
+int signature_domain_separation(struct shake256_ctx *hash_ctx,
+ unsigned int ml_dsa_internal,
+ const uint8_t *userctx, size_t userctxlen,
+ const uint8_t *m, size_t mlen,
+ const uint8_t *randomizer, size_t randomizerlen,
+ unsigned int nist_category)
+{
+ int ret = 0;
+
+ /* The internal operation skips the domain separation code */
+ if (ml_dsa_internal)
+ goto out;
+
+ if (userctxlen > 255)
+ return -EINVAL;
+
+ /* If Composite ML-DSA is requested, use domain as userctx */
+ if (randomizer) {
+ return -EOPNOTSUPP;
+ } else {
+ ret = standalone_signature_domain_separation(
+ hash_ctx, userctx, userctxlen,
+ mlen, nist_category);
+ }
+
+out:
+ shake256_update(hash_ctx, m, mlen);
+ return ret;
+}
diff --git a/crypto/ml_dsa/signature_domain_separation.h b/crypto/ml_dsa/signature_domain_separation.h
new file mode 100644
index 000000000000..01dafaa851bf
--- /dev/null
+++ b/crypto/ml_dsa/signature_domain_separation.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2024 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef SIGNATURE_DOMAIN_SEPARATION_H
+#define SIGNATURE_DOMAIN_SEPARATION_H
+
+#include <crypto/sha3.h>
+
+int signature_domain_separation(struct shake256_ctx *hash_ctx,
+ unsigned int ml_dsa_internal,
+ const uint8_t *userctx, size_t userctxlen,
+ const uint8_t *m, size_t mlen,
+ const uint8_t *randomizer, size_t randomizerlen,
+ unsigned int nist_category);
+int signature_ph_oids(struct shake256_ctx *hash_ctx, size_t mlen,
+ unsigned int nist_category);
+
+#endif /* SIGNATURE_DOMAIN_SEPARATION_H */
diff --git a/crypto/ml_dsa/small_stack_support.h b/crypto/ml_dsa/small_stack_support.h
new file mode 100644
index 000000000000..9c1eba6c40bf
--- /dev/null
+++ b/crypto/ml_dsa/small_stack_support.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2022 - 2025, Stephan Mueller <smueller@...onox.de>
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef SMALL_STACK_SUPPORT_H
+#define SMALL_STACK_SUPPORT_H
+
+/* Allocate memory on heap */
+#define __LC_DECLARE_MEM_HEAP(name, type, alignment) \
+ type *name = kzalloc(round_up(sizeof(type), alignment), GFP_KERNEL); \
+ if (!name) \
+ return -ENOMEM; \
+
+#define __LC_RELEASE_MEM_HEAP(name) \
+ kfree_sensitive(name);
+
+#define noinline_stack noinline
+
+#define LC_DECLARE_MEM(name, type, alignment) \
+ _Pragma("GCC diagnostic push") \
+ _Pragma("GCC diagnostic ignored \"-Wcast-align\"") \
+ __LC_DECLARE_MEM_HEAP(name, type, alignment); \
+ _Pragma("GCC diagnostic pop")
+#define LC_RELEASE_MEM(name) __LC_RELEASE_MEM_HEAP(name)
+
+#endif /* SMALL_STACK_SUPPORT_H */
Powered by blists - more mailing lists