* configure.ac (available_pubkey_ciphers): Add dilithium.
(USE_DILITHIUM): New.
* cipher/Makefile.am (EXTRA_libcipher_la_SOURCES): Add
pubkey-dilithium.c.
* cipher/pubkey-dilithium.c: New.
* cipher/pubkey.c (pubkey_list): Add _gcry_pubkey_spec_mldsa.
* src/cipher.h (_gcry_pubkey_spec_mldsa): New.
* src/gcrypt-int.h (enum gcry_mldsa_algos): New.
* src/gcrypt.h.in (GCRY_PK_MLDSA): New.

--

GnuPG-bug-id: 7640
Signed-off-by: NIIBE Yutaka <gni...@fsij.org>
---
 cipher/Makefile.am        |   2 +-
 cipher/pubkey-dilithium.c | 383 ++++++++++++++++++++++++++++++++++++++
 cipher/pubkey.c           |   3 +
 configure.ac              |  10 +-
 src/cipher.h              |   1 +
 src/gcrypt-int.h          |   7 +
 src/gcrypt.h.in           |   1 +
 7 files changed, 405 insertions(+), 2 deletions(-)
 create mode 100644 cipher/pubkey-dilithium.c

diff --git a/cipher/Makefile.am b/cipher/Makefile.am
index e86d148b..d8a3752b 100644
--- a/cipher/Makefile.am
+++ b/cipher/Makefile.am
@@ -98,7 +98,7 @@ EXTRA_libcipher_la_SOURCES = \
 	crc-armv8-aarch64-ce.S \
 	crc-ppc.c \
 	des.c des-amd64.S \
-	dilithium.c dilithium.h \
+	dilithium.c dilithium.h pubkey-dilithium.c \
 	dsa.c \
 	elgamal.c \
 	ecc.c ecc-curves.c ecc-misc.c ecc-common.h \
diff --git a/cipher/pubkey-dilithium.c b/cipher/pubkey-dilithium.c
new file mode 100644
index 00000000..31910808
--- /dev/null
+++ b/cipher/pubkey-dilithium.c
@@ -0,0 +1,383 @@
+/* pubkey-dilithium.c - the Dilithium for libgcrypt
+ * Copyright (C) 2025 g10 Code GmbH
+ *
+ * This file was modified for use by Libgcrypt.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ */
+
+#include <config.h>
+
+#include "g10lib.h"
+#include "mpi.h"
+#include "cipher.h"
+#include "pubkey-internal.h"
+#include "dilithium.h"
+
+static const char *mldsa_names[] =
+  {
+    "dilithium2",
+    "dilithium3",
+    "dilithium5",
+    NULL,
+  };
+
+#define MAX_PUBKEY_LEN 2592
+#define MAX_SECKEY_LEN 4896
+#define MAX_SIG_LEN 4627
+struct mldsa_info
+{
+  const char *name;            /* Name of the algo.  */
+  unsigned int namelen;        /* Only here to avoid strlen calls.  */
+  int algo;                    /* ML-DSA algo number.   */
+  unsigned int nbits;  /* Number of bits (pubkey size in bits).  */
+  unsigned int fips:1; /* True if this is a FIPS140-4??? approved.  */
+  int pubkey_len;      /* Length of the public key.  */
+  int seckey_len;      /* Length of the secret key.  */
+  int sig_len;         /* Length of the signature.  */
+};
+/* Information about the the ML-DSA algoithms for use by the
+ * s-expression interface.  */
+static const struct mldsa_info mldsa_infos[] =
+  {
+    { "dilithium2", 10, GCRY_MLDSA44, 1312*8, 1, 1312, 2560, 2420 },
+    { "dilithium3", 10, GCRY_MLDSA65, 1952*8, 1, 1952, 4032, 3309 },
+    { "dilithium5", 10, GCRY_MLDSA87, 2592*8, 1, 2592, 4896, 4627 },
+    { NULL }
+  };
+
+static const struct mldsa_info *
+mldsa_get_info (gcry_sexp_t keyparam)
+{
+  const char *algo;
+  size_t algolen;
+  const char *name;
+  int i;
+
+  algo = sexp_nth_data (keyparam, 0, &algolen);
+  if (!algo || !algolen)
+    return NULL;
+  for (i=0; (name=mldsa_infos[i].name); i++)
+    if (mldsa_infos[i].namelen == algolen && !memcmp (name, algo, algolen))
+      break;
+  if (!name)
+    return NULL;
+
+  return &mldsa_infos[i];
+}
+
+static unsigned int
+mldsa_get_nbits (gcry_sexp_t keyparam)
+{
+  const struct mldsa_info *info = mldsa_get_info (keyparam);
+  if (!info)
+    return 0;  /* GPG_ERR_PUBKEY_ALGO */
+
+  return info->nbits;
+}
+
+static gpg_err_code_t
+mldsa_compute_keygrip (gcry_md_hd_t md, gcry_sexp_t keyparam)
+{
+  gcry_sexp_t l1;
+  const char *data;
+  size_t datalen;
+
+  const struct mldsa_info *info = mldsa_get_info (keyparam);
+  if (!info)
+    return GPG_ERR_WRONG_PUBKEY_ALGO;
+
+  _gcry_md_write (md, info->name, info->namelen+1); /* (also hash the nul) */
+
+  l1 = sexp_find_token (keyparam, "p", 1);
+  if (!l1)
+    return GPG_ERR_NO_OBJ;
+
+  data = sexp_nth_data (l1, 1, &datalen);
+  if (!data)
+    {
+      sexp_release (l1);
+      return GPG_ERR_NO_OBJ;
+    }
+
+  _gcry_md_write (md, data, datalen);
+  sexp_release (l1);
+
+  return 0;
+}
+
+
+static void
+randombytes (unsigned char *out, size_t outlen)
+{
+  _gcry_randomize (out, outlen, GCRY_VERY_STRONG_RANDOM);
+}
+
+static gcry_err_code_t
+mldsa_generate (const gcry_sexp_t genparms, gcry_sexp_t *r_skey)
+{
+  gpg_err_code_t rc = 0;
+  gcry_mpi_t seed_mpi = NULL;
+  unsigned char seed[SEEDBYTES];
+  unsigned char pk[MAX_PUBKEY_LEN];
+  unsigned char sk[MAX_SECKEY_LEN];
+  const struct mldsa_info *info = mldsa_get_info (genparms);
+
+  if (!info)
+    return GPG_ERR_PUBKEY_ALGO;
+
+  if (info->pubkey_len > MAX_PUBKEY_LEN)
+    return GPG_ERR_INTERNAL;
+
+  if (info->seckey_len > MAX_SECKEY_LEN)
+    return GPG_ERR_INTERNAL;
+
+  /*
+   * Extract the seed (if any).
+   */
+  rc = sexp_extract_param (genparms, NULL, "/S", &seed_mpi, NULL);
+  if (rc == GPG_ERR_NOT_FOUND)
+    {
+      randombytes (seed, SEEDBYTES);
+      rc = 0;
+    }
+  else if (rc)
+    goto leave;
+  else
+    {
+      const unsigned char *seed_supplied;
+      unsigned int n;
+
+      seed_supplied = mpi_get_opaque (seed_mpi, &n);
+      if (SEEDBYTES != (n + 7) / 8)
+        {
+          rc = GPG_ERR_INV_DATA;
+          goto leave;
+        }
+      memcpy (seed, seed_supplied, SEEDBYTES);
+    }
+
+  dilithium_keypair (info->algo, pk, sk, seed);
+
+  if (!rc)
+    rc = sexp_build (r_skey,
+                     NULL,
+                     "(key-data"
+                     " (public-key(%s(p%b)))"
+                     " (private-key(%s(p%b)(s%b)(S%b))))",
+                     info->name, info->pubkey_len, pk,
+                     info->name, info->pubkey_len, pk, info->seckey_len, sk,
+                     SEEDBYTES, seed,
+                     NULL);
+
+ leave:
+  _gcry_mpi_release (seed_mpi);
+  wipememory (seed, SEEDBYTES);
+  wipememory (sk, info->seckey_len);
+  return rc;
+}
+
+
+static gcry_err_code_t
+mldsa_sign (gcry_sexp_t *r_sig, gcry_sexp_t s_data, gcry_sexp_t keyparms)
+{
+  gpg_err_code_t rc = 0;
+  unsigned int n;
+  struct pk_encoding_ctx ctx;
+  gcry_mpi_t sk_mpi = NULL;
+  gcry_mpi_t data_mpi = NULL;
+  unsigned char sig[MAX_SIG_LEN];
+  unsigned char rnd[RNDBYTES];
+  const unsigned char *data;
+  size_t data_len;
+  const unsigned char *sk;
+  const struct mldsa_info *info = mldsa_get_info (keyparms);
+  int r;
+
+  if (!info)
+    return GPG_ERR_PUBKEY_ALGO;
+
+  if (info->sig_len > MAX_SIG_LEN)
+    return GPG_ERR_INTERNAL;
+
+  _gcry_pk_util_init_encoding_ctx (&ctx, PUBKEY_OP_SIGN, 0);
+
+  /* Dilithium requires the byte string for its DATA.  */
+  ctx.flags |= PUBKEY_FLAG_BYTE_STRING;
+
+  /*
+   * Extract the secret key.
+   */
+  rc = sexp_extract_param (keyparms, NULL, "/s", &sk_mpi, NULL);
+  if (rc)
+    goto leave;
+  sk = mpi_get_opaque (sk_mpi, &n);
+  if (!sk || info->seckey_len != (n + 7) / 8)
+    {
+      rc = GPG_ERR_BAD_SECKEY;
+      goto leave;
+    }
+
+  /* Extract the data.  */
+  rc = _gcry_pk_util_data_to_mpi (s_data, &data_mpi, &ctx);
+  if (rc)
+    goto leave;
+  if (DBG_CIPHER)
+    log_mpidump ("mldsa_sign    data", data_mpi);
+  if (!mpi_is_opaque (data_mpi))
+    {
+      rc = GPG_ERR_INV_DATA;
+      goto leave;
+    }
+  data = mpi_get_opaque (data_mpi, &n);
+  data_len = (n + 7) / 8;
+
+  if (ctx.rnd)
+    {
+      if (ctx.rndlen != RNDBYTES)
+        {
+          rc = GPG_ERR_INV_DATA;
+          goto leave;
+        }
+      memcpy (rnd, ctx.rnd, RNDBYTES);
+    }
+  else
+    randombytes (rnd, RNDBYTES);
+  r = dilithium_sign (info->algo, sig, info->sig_len, data, data_len,
+                      ctx.label, ctx.labellen, sk, rnd);
+  if (r < 0)
+    {
+      rc = GPG_ERR_INTERNAL;
+      goto leave;
+    }
+
+  rc = sexp_build (r_sig, NULL, "(sig-val(%s(s%b)))", info->name,
+                   info->sig_len, sig);
+  if (rc)
+    goto leave;
+
+leave:
+  _gcry_pk_util_free_encoding_ctx (&ctx);
+  _gcry_mpi_release (sk_mpi);
+  _gcry_mpi_release (data_mpi);
+  wipememory (rnd, RNDBYTES);
+  if (DBG_CIPHER)
+    log_debug ("mldsa_sign    => %s\n", gpg_strerror (rc));
+  return rc;
+}
+
+
+static gcry_err_code_t
+mldsa_verify (gcry_sexp_t s_sig, gcry_sexp_t s_data, gcry_sexp_t keyparms)
+{
+  gpg_err_code_t rc = 0;
+  unsigned int n;
+  struct pk_encoding_ctx ctx;
+  gcry_mpi_t sig_mpi = NULL;
+  gcry_mpi_t data_mpi = NULL;
+  gcry_mpi_t pk_mpi = NULL;
+  const unsigned char *sig;
+  const unsigned char *data;
+  size_t data_len;
+  const unsigned char *pk;
+  const struct mldsa_info *info = mldsa_get_info (keyparms);
+  int r;
+
+  if (!info)
+    return GPG_ERR_PUBKEY_ALGO;
+
+  _gcry_pk_util_init_encoding_ctx (&ctx, PUBKEY_OP_VERIFY, 0);
+
+  /* Dilithium requires the byte string for its DATA.  */
+  ctx.flags |= PUBKEY_FLAG_BYTE_STRING;
+
+  /*
+   * Extract the public key.
+   */
+  rc = sexp_extract_param (keyparms, NULL, "/p", &pk_mpi, NULL);
+  if (rc)
+    goto leave;
+  pk = mpi_get_opaque (pk_mpi, &n);
+  if (!pk || info->pubkey_len != (n + 7) / 8)
+    {
+      rc = GPG_ERR_BAD_PUBKEY;
+      goto leave;
+    }
+
+  rc = _gcry_pk_util_data_to_mpi (s_data, &data_mpi, &ctx);
+  if (rc)
+    goto leave;
+  if (DBG_CIPHER)
+    log_mpidump ("mldsa_verify  data", data_mpi);
+  if (!mpi_is_opaque (data_mpi))
+    {
+      rc = GPG_ERR_INV_DATA;
+      goto leave;
+    }
+  data = mpi_get_opaque (data_mpi, &n);
+  data_len = (n + 7) / 8;
+
+  /* Extract the signature.  */
+  rc = sexp_extract_param (s_sig, NULL, "/s", &sig_mpi, NULL);
+  if (rc)
+    goto leave;
+  if (DBG_CIPHER)
+    log_printmpi ("mldsa_verify  sig", sig_mpi);
+  sig = mpi_get_opaque (sig_mpi, &n);
+  if (!sig || info->sig_len != (n + 7) / 8)
+    {
+      rc = GPG_ERR_BAD_SIGNATURE;
+      goto leave;
+    }
+
+  r = dilithium_verify (info->algo, sig, info->sig_len, data, data_len,
+                        ctx.label, ctx.labellen, pk);
+  if (r < 0)
+    {
+      rc = GPG_ERR_BAD_SIGNATURE;
+      goto leave;
+    }
+
+leave:
+  _gcry_pk_util_free_encoding_ctx (&ctx);
+  _gcry_mpi_release (pk_mpi);
+  _gcry_mpi_release (data_mpi);
+  _gcry_mpi_release (sig_mpi);
+  if (DBG_CIPHER)
+    log_debug ("mldsa_verify  => %s\n", gpg_strerror (rc));
+  return rc;
+}
+
+gcry_pk_spec_t _gcry_pubkey_spec_mldsa =
+  {
+    GCRY_PK_MLDSA, {0, 1},
+    GCRY_PK_USAGE_SIGN,
+    "ML-DSA", mldsa_names,
+    "p",                        /* p: public */
+    "sS",                       /* s: secret, S: seed */
+    "",
+    "s",                        /* s: signature */
+    "p",                        /* p: public */
+    mldsa_generate,
+    NULL /* mldsa_check_secret_key */,
+    NULL,
+    NULL,
+    mldsa_sign,
+    mldsa_verify,
+    mldsa_get_nbits,
+    NULL, /*run_selftests*/
+    mldsa_compute_keygrip
+  };
diff --git a/cipher/pubkey.c b/cipher/pubkey.c
index ac806d3c..d6ebc73b 100644
--- a/cipher/pubkey.c
+++ b/cipher/pubkey.c
@@ -47,6 +47,9 @@ static gcry_pk_spec_t * const pubkey_list[] =
 #endif
 #if USE_ELGAMAL
     &_gcry_pubkey_spec_elg,
+#endif
+#if USE_DILITHIUM
+    &_gcry_pubkey_spec_mldsa,
 #endif
     &_gcry_pubkey_spec_kem,
     NULL
diff --git a/configure.ac b/configure.ac
index 7526d93a..b9b0e8a8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -213,7 +213,7 @@ available_ciphers="$available_ciphers sm4 aria"
 enabled_ciphers=""
 
 # Definitions for public-key ciphers.
-available_pubkey_ciphers="dsa elgamal rsa ecc kyber"
+available_pubkey_ciphers="dsa elgamal rsa ecc kyber dilithium"
 enabled_pubkey_ciphers=""
 
 # Definitions for message digests.
@@ -3567,6 +3567,14 @@ if test "$found" = "1" ; then
    AC_DEFINE(USE_KYBER, 1, [Defined if this module should be included])
 fi
 
+LIST_MEMBER(dilithium, $enabled_pubkey_ciphers)
+AM_CONDITIONAL(USE_DILITHIUM, [test "$found" = "1"])
+if test "$found" = "1" ; then
+   GCRYPT_PUBKEY_CIPHERS="$GCRYPT_PUBKEY_CIPHERS \
+                          dilithium.lo pubkey-dilithium.lo"
+   AC_DEFINE(USE_DILITHIUM, 1, [Defined if this module should be included])
+fi
+
 LIST_MEMBER(crc, $enabled_digests)
 if test "$found" = "1" ; then
    GCRYPT_DIGESTS="$GCRYPT_DIGESTS crc.lo"
diff --git a/src/cipher.h b/src/cipher.h
index 83c5c532..5e2e04e3 100644
--- a/src/cipher.h
+++ b/src/cipher.h
@@ -235,6 +235,7 @@ extern gcry_pk_spec_t _gcry_pubkey_spec_rsa;
 extern gcry_pk_spec_t _gcry_pubkey_spec_elg;
 extern gcry_pk_spec_t _gcry_pubkey_spec_dsa;
 extern gcry_pk_spec_t _gcry_pubkey_spec_ecc;
+extern gcry_pk_spec_t _gcry_pubkey_spec_mldsa;
 extern gcry_pk_spec_t _gcry_pubkey_spec_kem;
 
 
diff --git a/src/gcrypt-int.h b/src/gcrypt-int.h
index 1eb58cb9..71472a80 100644
--- a/src/gcrypt-int.h
+++ b/src/gcrypt-int.h
@@ -610,4 +610,11 @@ int _gcry_mpi_get_flag (gcry_mpi_t a, enum gcry_mpi_flag flag);
 #define mpi_get_flag(a,f)      _gcry_mpi_get_flag ((a), (f))
 
 
+enum gcry_mldsa_algos
+  {                             /* See FIPS 204, Table 1 */
+    GCRY_MLDSA44,               /* Category 2 */
+    GCRY_MLDSA65,               /* Category 3 */
+    GCRY_MLDSA87                /* Category 5 */
+  };
+
 #endif /*GCRY_GCRYPT_INT_H*/
diff --git a/src/gcrypt.h.in b/src/gcrypt.h.in
index d5ac0b33..db078667 100644
--- a/src/gcrypt.h.in
+++ b/src/gcrypt.h.in
@@ -1162,6 +1162,7 @@ enum gcry_pk_algos
     GCRY_PK_ECDSA = 301,    /* (only for external use).  */
     GCRY_PK_ECDH  = 302,    /* (only for external use).  */
     GCRY_PK_EDDSA = 303,    /* (only for external use).  */
+    GCRY_PK_MLDSA = 332,    /* Dilithium (ML-DSA).  */
     GCRY_PK_KEM   = 333     /* Pseudo ID for KEM algos.  */
   };
 
_______________________________________________
Gcrypt-devel mailing list
Gcrypt-devel@gnupg.org
https://lists.gnupg.org/mailman/listinfo/gcrypt-devel

Reply via email to