From: Eric Biggers <ebiggers@kernel.org>
To: linux-crypto@vger.kernel.org
Cc: linux-kernel@vger.kernel.org, Ard Biesheuvel <ardb@kernel.org>,
"Jason A . Donenfeld" <Jason@zx2c4.com>,
Herbert Xu <herbert@gondor.apana.org.au>,
Ryan Appel <ryan.appel.333@gmail.com>,
Chris Leech <cleech@redhat.com>,
Eric Biggers <ebiggers@kernel.org>
Subject: [PATCH 4/5] lib/crypto: xwing: Add support for X-Wing KEM
Date: Mon, 25 May 2026 13:44:02 -0500 [thread overview]
Message-ID: <20260525184403.101818-5-ebiggers@kernel.org> (raw)
In-Reply-To: <20260525184403.101818-1-ebiggers@kernel.org>
Add support for X-Wing, a general-purpose post-quantum/traditional
hybrid key encapsulation mechanism using X25519 and ML-KEM-768. X-Wing
is the recommended KEM for new applications. X-Wing is specified at
https://datatracker.ietf.org/doc/html/draft-connolly-cfrg-xwing-kem-10
This is a proof-of-concept that won't be merged until there's an
in-kernel user. Potential users include in-kernel users of classical
key agreement schemes (Bluetooth, NVMe auth, WireGuard).
Signed-off-by: Eric Biggers <ebiggers@kernel.org>
---
Documentation/crypto/libcrypto-asymmetric.rst | 7 +
include/crypto/xwing.h | 84 +++++++
lib/crypto/Kconfig | 9 +
lib/crypto/Makefile | 5 +
lib/crypto/xwing.c | 237 ++++++++++++++++++
5 files changed, 342 insertions(+)
create mode 100644 include/crypto/xwing.h
create mode 100644 lib/crypto/xwing.c
diff --git a/Documentation/crypto/libcrypto-asymmetric.rst b/Documentation/crypto/libcrypto-asymmetric.rst
index 6e71c5ce823f..d44ee74f6a46 100644
--- a/Documentation/crypto/libcrypto-asymmetric.rst
+++ b/Documentation/crypto/libcrypto-asymmetric.rst
@@ -16,5 +16,12 @@ ML-KEM
Support for the ML-KEM key encapsulation mechanism.
This shall be used as part of a hybrid scheme such as X-Wing, not by itself.
.. kernel-doc:: include/crypto/mlkem.h
+
+X-Wing
+------
+
+Support for the X-Wing key encapsulation mechanism.
+
+.. kernel-doc:: include/crypto/xwing.h
diff --git a/include/crypto/xwing.h b/include/crypto/xwing.h
new file mode 100644
index 000000000000..d1ad7b1e6596
--- /dev/null
+++ b/include/crypto/xwing.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright 2026 Google LLC
+ */
+
+/**
+ * DOC: X-Wing key encapsulation mechanism
+ *
+ * Implementation of X-Wing, a general-purpose post-quantum/traditional hybrid
+ * key encapsulation mechanism using X25519 and ML-KEM-768. X-Wing is the
+ * recommended KEM for new applications. X-Wing is specified at
+ * https://datatracker.ietf.org/doc/html/draft-connolly-cfrg-xwing-kem-10
+ */
+
+#ifndef _CRYPTO_XWING_H
+#define _CRYPTO_XWING_H
+
+#include <linux/types.h>
+
+#define XWING_SEED_BYTES 32 /* Length of seed for KeyGen */
+#define XWING_PUBLIC_KEY_BYTES 1216
+#define XWING_SECRET_KEY_BYTES 32
+#define XWING_ESEED_BYTES 64 /* Length of seed for Encaps */
+#define XWING_CIPHERTEXT_BYTES 1120
+#define XWING_SHARED_SECRET_BYTES 32
+
+/**
+ * xwing_keygen() - Generate an X-Wing key pair
+ * @pk: (output) The public key (encapsulation key)
+ * @sk: (output) The secret key (decapsulation key)
+ *
+ * Context: Might sleep
+ *
+ * Return: 0 on success, or -ENOMEM if out of memory.
+ */
+int xwing_keygen(u8 pk[XWING_PUBLIC_KEY_BYTES], u8 sk[XWING_SECRET_KEY_BYTES]);
+
+/**
+ * xwing_encaps() - Generate and encapsulate shared secret with X-Wing
+ * @ct: (output) The ciphertext
+ * @ss: (output) The generated shared secret
+ * @pk: The public key (encapsulation key)
+ *
+ * Context: Might sleep
+ *
+ * Return:
+ * * 0 on success
+ * * -EBADMSG if the public key is malformed
+ * * -ENOMEM if out of memory
+ */
+int xwing_encaps(u8 ct[XWING_CIPHERTEXT_BYTES],
+ u8 ss[XWING_SHARED_SECRET_BYTES],
+ const u8 pk[XWING_PUBLIC_KEY_BYTES]);
+
+/**
+ * xwing_decaps() - Decapsulate shared secret with X-Wing
+ * @ss: (output) The decapsulated shared secret
+ * @ct: The ciphertext
+ * @sk: The secret key (decapsulation key)
+ *
+ * Context: Might sleep
+ *
+ * Return:
+ * * 0 on success, including the implicit rejection cases where the ciphertext
+ * is invalid and a randomized shared secret is returned
+ * * -EBADMSG if the secret key is malformed
+ * * -ENOMEM if out of memory
+ */
+int xwing_decaps(u8 ss[XWING_SHARED_SECRET_BYTES],
+ const u8 ct[XWING_CIPHERTEXT_BYTES],
+ const u8 sk[XWING_SECRET_KEY_BYTES]);
+
+#if IS_ENABLED(CONFIG_KUNIT)
+/* Functions taking explicit seeds, only for KUnit testing */
+int xwing_keygen_internal(u8 pk[XWING_PUBLIC_KEY_BYTES],
+ u8 sk[XWING_SECRET_KEY_BYTES],
+ const u8 seed[XWING_SEED_BYTES]);
+int xwing_encaps_internal(u8 ct[XWING_CIPHERTEXT_BYTES],
+ u8 ss[XWING_SHARED_SECRET_BYTES],
+ const u8 pk[XWING_PUBLIC_KEY_BYTES],
+ const u8 eseed[XWING_ESEED_BYTES]);
+#endif /* CONFIG_KUNIT */
+
+#endif /* _CRYPTO_XWING_H */
diff --git a/lib/crypto/Kconfig b/lib/crypto/Kconfig
index acaa64af4c6d..2ce1867eeb9e 100644
--- a/lib/crypto/Kconfig
+++ b/lib/crypto/Kconfig
@@ -285,5 +285,14 @@ config CRYPTO_LIB_SM3_ARCH
depends on CRYPTO_LIB_SM3 && !UML
default y if ARM64
default y if RISCV && 64BIT && TOOLCHAIN_HAS_VECTOR_CRYPTO && \
RISCV_EFFICIENT_VECTOR_UNALIGNED_ACCESS
default y if X86_64
+
+config CRYPTO_LIB_XWING
+ tristate
+ select CRYPTO_LIB_CURVE25519
+ select CRYPTO_LIB_MLKEM
+ select CRYPTO_LIB_SHA3
+ help
+ The X-Wing library functions. Select this if your module uses any of
+ the functions from <crypto/xwing.h>.
diff --git a/lib/crypto/Makefile b/lib/crypto/Makefile
index 94cef4e574cd..a20ae074cbfa 100644
--- a/lib/crypto/Makefile
+++ b/lib/crypto/Makefile
@@ -384,10 +384,15 @@ libsm3-$(CONFIG_RISCV) += riscv/sm3-riscv64-zvksh-zvkb.o
libsm3-$(CONFIG_X86) += x86/sm3-avx-asm_64.o
endif # CONFIG_CRYPTO_LIB_SM3_ARCH
################################################################################
+obj-$(CONFIG_CRYPTO_LIB_XWING) += libxwing.o
+libxwing-y := xwing.o
+
+################################################################################
+
obj-$(CONFIG_MPILIB) += mpi/
obj-$(CONFIG_CRYPTO_SELFTESTS_FULL) += simd.o
# clean-files must be defined unconditionally
diff --git a/lib/crypto/xwing.c b/lib/crypto/xwing.c
new file mode 100644
index 000000000000..deffa46b900e
--- /dev/null
+++ b/lib/crypto/xwing.c
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * X-Wing key encapsulation mechanism
+ *
+ * See include/crypto/xwing.h for the documentation.
+ *
+ * Copyright 2026 Google LLC
+ */
+
+#include <crypto/curve25519.h>
+#include <crypto/mlkem.h>
+#include <crypto/sha3.h>
+#include <crypto/xwing.h>
+#include <kunit/visibility.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+/* pk = pk_mlkem || pk_x25519 */
+static_assert(XWING_PUBLIC_KEY_BYTES ==
+ MLKEM768_PUBLIC_KEY_BYTES + CURVE25519_KEY_SIZE);
+
+/* sk = seed */
+static_assert(XWING_SECRET_KEY_BYTES == XWING_SEED_BYTES);
+
+/* ct = ct_mlkem || ct_x25519 */
+static_assert(XWING_CIPHERTEXT_BYTES ==
+ MLKEM768_CIPHERTEXT_BYTES + CURVE25519_KEY_SIZE);
+
+/* expanded_sk = sk_mlkem || sk_x25519 || pk_mlkem || pk_x25519 */
+#define XWING_EXPANDED_SECRET_KEY_BYTES \
+ (MLKEM768_SECRET_KEY_BYTES + CURVE25519_KEY_SIZE + \
+ MLKEM768_PUBLIC_KEY_BYTES + CURVE25519_KEY_SIZE)
+
+static int xwing_expand_sk(u8 expanded_sk[XWING_EXPANDED_SECRET_KEY_BYTES],
+ const u8 sk[XWING_SECRET_KEY_BYTES])
+{
+ u8 *sk_mlkem = &expanded_sk[0];
+ u8 *sk_x25519 = &sk_mlkem[MLKEM768_SECRET_KEY_BYTES];
+ u8 *pk_mlkem = &sk_x25519[CURVE25519_KEY_SIZE];
+ u8 *pk_x25519 = &pk_mlkem[MLKEM768_PUBLIC_KEY_BYTES];
+ u8 seed_mlkem[MLKEM_SEED_BYTES];
+ struct shake_ctx shake;
+ int err;
+
+ shake256_init(&shake);
+ shake_update(&shake, sk, XWING_SECRET_KEY_BYTES);
+ shake_squeeze(&shake, seed_mlkem, sizeof(seed_mlkem));
+
+ err = mlkem768_keygen_internal(pk_mlkem, sk_mlkem, seed_mlkem);
+ if (err) /* can only be -ENOMEM */
+ goto out;
+ shake_squeeze(&shake, sk_x25519, CURVE25519_KEY_SIZE);
+ curve25519_clamp_secret(sk_x25519);
+ if (unlikely(!curve25519_generate_public(pk_x25519, sk_x25519)))
+ err = -EAGAIN;
+out:
+ shake_zeroize_ctx(&shake);
+ memzero_explicit(seed_mlkem, sizeof(seed_mlkem));
+ return err;
+}
+
+VISIBLE_IF_KUNIT int xwing_keygen_internal(u8 pk[XWING_PUBLIC_KEY_BYTES],
+ u8 sk[XWING_SECRET_KEY_BYTES],
+ const u8 seed[XWING_SEED_BYTES])
+{
+ u8 *expanded_sk __free(kfree_sensitive) =
+ kmalloc(XWING_EXPANDED_SECRET_KEY_BYTES, GFP_KERNEL);
+ int err;
+
+ if (!expanded_sk)
+ return -ENOMEM;
+
+ err = xwing_expand_sk(expanded_sk, seed);
+ if (err)
+ return err;
+ /* pk = pk_mlkem || pk_x25519 */
+ memcpy(pk,
+ &expanded_sk[MLKEM768_SECRET_KEY_BYTES + CURVE25519_KEY_SIZE],
+ XWING_PUBLIC_KEY_BYTES);
+ /* sk = seed */
+ memcpy(sk, seed, XWING_SECRET_KEY_BYTES);
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(xwing_keygen_internal);
+
+int xwing_keygen(u8 pk[XWING_PUBLIC_KEY_BYTES], u8 sk[XWING_SECRET_KEY_BYTES])
+{
+ u8 seed[XWING_SEED_BYTES];
+ int err;
+
+ do {
+ get_random_bytes(seed, sizeof(seed));
+ err = xwing_keygen_internal(pk, sk, seed);
+ } while (err == -EAGAIN); /* curve25519_null_point case */
+ memzero_explicit(seed, sizeof(seed));
+ return err;
+}
+EXPORT_SYMBOL_GPL(xwing_keygen);
+
+static void xwing_combine(u8 ss[XWING_SHARED_SECRET_BYTES],
+ const u8 ss_mlkem[MLKEM_SHARED_SECRET_BYTES],
+ const u8 ss_x25519[CURVE25519_KEY_SIZE],
+ const u8 ct_x25519[CURVE25519_KEY_SIZE],
+ const u8 pk_x25519[CURVE25519_KEY_SIZE])
+{
+ static const u8 xwing_label[6] = { 0x5c, 0x2e, 0x2f, 0x2f, 0x5e, 0x5c };
+ struct sha3_ctx ctx;
+
+ sha3_256_init(&ctx);
+ sha3_update(&ctx, ss_mlkem, MLKEM_SHARED_SECRET_BYTES);
+ sha3_update(&ctx, ss_x25519, CURVE25519_KEY_SIZE);
+ sha3_update(&ctx, ct_x25519, CURVE25519_KEY_SIZE);
+ sha3_update(&ctx, pk_x25519, CURVE25519_KEY_SIZE);
+ sha3_update(&ctx, xwing_label, sizeof(xwing_label));
+ sha3_final(&ctx, ss);
+}
+
+VISIBLE_IF_KUNIT int xwing_encaps_internal(u8 ct[XWING_CIPHERTEXT_BYTES],
+ u8 ss[XWING_SHARED_SECRET_BYTES],
+ const u8 pk[XWING_PUBLIC_KEY_BYTES],
+ const u8 eseed[XWING_ESEED_BYTES])
+{
+ const u8 *pk_mlkem = &pk[0];
+ const u8 *pk_x25519 = &pk[MLKEM768_PUBLIC_KEY_BYTES];
+ const u8 *eseed_mlkem = &eseed[0];
+ const u8 *eseed_x25519 = &eseed[MLKEM_ESEED_BYTES];
+ u8 eph_sk_x25519[CURVE25519_KEY_SIZE];
+ u8 *ct_mlkem = &ct[0];
+ u8 *ct_x25519 = &ct[MLKEM768_CIPHERTEXT_BYTES];
+ u8 ss_mlkem[MLKEM_SHARED_SECRET_BYTES];
+ u8 ss_x25519[CURVE25519_KEY_SIZE];
+ int err;
+
+ err = mlkem768_encaps_internal(ct_mlkem, ss_mlkem, pk_mlkem,
+ eseed_mlkem);
+ if (err)
+ goto out;
+ memcpy(eph_sk_x25519, eseed_x25519, CURVE25519_KEY_SIZE);
+ curve25519_clamp_secret(eph_sk_x25519);
+ if (!curve25519_generate_public(ct_x25519, eph_sk_x25519)) {
+ err = -EAGAIN;
+ goto out;
+ }
+ if (!curve25519(ss_x25519, eph_sk_x25519, pk_x25519)) {
+ err = -EBADMSG;
+ goto out;
+ }
+ xwing_combine(ss, ss_mlkem, ss_x25519, ct_x25519, pk_x25519);
+ err = 0;
+out:
+ if (err) {
+ get_random_bytes(ct, XWING_CIPHERTEXT_BYTES);
+ get_random_bytes(ss, XWING_SHARED_SECRET_BYTES);
+ }
+ memzero_explicit(eph_sk_x25519, sizeof(eph_sk_x25519));
+ memzero_explicit(ss_mlkem, sizeof(ss_mlkem));
+ memzero_explicit(ss_x25519, sizeof(ss_x25519));
+ return err;
+}
+EXPORT_SYMBOL_IF_KUNIT(xwing_encaps_internal);
+
+int xwing_encaps(u8 ct[XWING_CIPHERTEXT_BYTES],
+ u8 ss[XWING_SHARED_SECRET_BYTES],
+ const u8 pk[XWING_PUBLIC_KEY_BYTES])
+{
+ u8 eseed[XWING_ESEED_BYTES];
+ int err;
+
+ do {
+ get_random_bytes(eseed, sizeof(eseed));
+ err = xwing_encaps_internal(ct, ss, pk, eseed);
+ } while (err == -EAGAIN); /* curve25519_null_point case */
+ memzero_explicit(eseed, sizeof(eseed));
+ return err;
+}
+EXPORT_SYMBOL_GPL(xwing_encaps);
+
+int xwing_decaps(u8 ss[XWING_SHARED_SECRET_BYTES],
+ const u8 ct[XWING_CIPHERTEXT_BYTES],
+ const u8 sk[XWING_SECRET_KEY_BYTES])
+{
+ u8 *expanded_sk __free(kfree_sensitive) =
+ kmalloc(XWING_EXPANDED_SECRET_KEY_BYTES, GFP_KERNEL);
+ u8 *sk_mlkem, *sk_x25519, *pk_mlkem, *pk_x25519;
+ const u8 *ct_mlkem = &ct[0];
+ const u8 *ct_x25519 = &ct[MLKEM768_CIPHERTEXT_BYTES];
+ u8 ss_mlkem[MLKEM_SHARED_SECRET_BYTES];
+ u8 ss_x25519[CURVE25519_KEY_SIZE];
+ int err;
+
+ if (!expanded_sk) {
+ err = -ENOMEM;
+ goto out;
+ }
+ err = xwing_expand_sk(expanded_sk, sk);
+ if (err) {
+ if (err == -EAGAIN) /* curve25519_null_point case */
+ err = -EBADMSG;
+ goto out;
+ }
+ sk_mlkem = &expanded_sk[0];
+ sk_x25519 = &sk_mlkem[MLKEM768_SECRET_KEY_BYTES];
+ pk_mlkem = &sk_x25519[CURVE25519_KEY_SIZE];
+ pk_x25519 = &pk_mlkem[MLKEM768_PUBLIC_KEY_BYTES];
+
+ err = mlkem768_decaps(ss_mlkem, ct_mlkem, sk_mlkem);
+ if (err) {
+ /*
+ * This is either -ENOMEM, or -EBADMSG for a malformed secret
+ * key. This case is *not* reached if the ciphertext is
+ * invalid, as implicit rejection is used.
+ */
+ goto out;
+ }
+ if (!curve25519(ss_x25519, sk_x25519, ct_x25519)) {
+ /*
+ * ss_x25519 is curve25519_null_point, which can happen if the
+ * ciphertext is invalid. In this case the correct behavior is
+ * to continue anyway and implicitly reject.
+ */
+ }
+
+ xwing_combine(ss, ss_mlkem, ss_x25519, ct_x25519, pk_x25519);
+ err = 0;
+out:
+ if (err)
+ get_random_bytes(ss, XWING_SHARED_SECRET_BYTES);
+ memzero_explicit(ss_mlkem, sizeof(ss_mlkem));
+ memzero_explicit(ss_x25519, sizeof(ss_x25519));
+ return err;
+}
+EXPORT_SYMBOL_GPL(xwing_decaps);
+
+MODULE_DESCRIPTION("X-Wing key encapsulation mechanism");
+MODULE_IMPORT_NS("CRYPTO_INTERNAL");
+MODULE_LICENSE("GPL");
--
2.54.0
next prev parent reply other threads:[~2026-05-25 18:46 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-25 18:43 [PATCH 0/5] ML-KEM and X-Wing support Eric Biggers
2026-05-25 18:43 ` [PATCH 1/5] lib/crypto: mlkem: Add ML-KEM-768 and ML-KEM-1024 support Eric Biggers
2026-05-25 18:44 ` [PATCH 2/5] lib/crypto: mlkem: Add KUnit tests for ML-KEM Eric Biggers
2026-05-25 18:44 ` [PATCH 3/5] lib/crypto: mlkem: Add FIPS 140-3 tests Eric Biggers
2026-05-25 18:44 ` Eric Biggers [this message]
2026-05-25 18:44 ` [PATCH 5/5] lib/crypto: xwing: Add KUnit tests for X-Wing KEM Eric Biggers
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260525184403.101818-5-ebiggers@kernel.org \
--to=ebiggers@kernel.org \
--cc=Jason@zx2c4.com \
--cc=ardb@kernel.org \
--cc=cleech@redhat.com \
--cc=herbert@gondor.apana.org.au \
--cc=linux-crypto@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=ryan.appel.333@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox