X-Wing hybrid KEM for Python (X25519 + ML-KEM-768), per draft-connolly-cfrg-xwing-kem
Project description
xwing-kem
The X-Wing hybrid post-quantum KEM (X25519 + ML-KEM-768) for Python.
xwing-kem is a single, drop-in key-encapsulation mechanism that stays secure
as long as either classical X25519 or post-quantum ML-KEM-768 holds. It
implements draft-connolly-cfrg-xwing-kem
faithfully, so you get the vetted hybrid construction without ever hand-rolling
a combiner — the single most common (and dangerous) post-quantum migration bug.
⚠️ Pre-1.0 — please read before production use
xwing-kemis validated against all three official X-Wing Known-Answer-Test (KAT) vectors from the draft: key generation from seed and the SHA3-256 combiner are checked byte-for-byte, and derandomized encapsulation is verified against the draft's reference implementation.Honest limits that still apply: no ML-KEM backend exposes seeded encapsulation, so the library's own randomized
encapsulate()is validated transitively (round-trip + keygen KAT + combiner KAT + reference-encaps KAT), not by a direct ciphertext-byte match; constant-time guarantees come only from the C-backend primitives, not the Python glue; and secret keys are not portable between backends. The full, candid accounting is in KNOWN-GAPS.md.
Contents
- Key Features
- Quick Start
- Installation
- When to Use X-Wing
- Why X-Wing?
- The X-Wing Construction
- Backend Selection
- Performance
- Validation & Limitations
- Design Notes
- Contributing & Security
- License
Key Features
- 🛡️ Hybrid by construction — secure unless an attacker breaks both X25519 and ML-KEM-768.
- 🎯 Concrete, not generic — algorithms, combiner (SHA3-256), and security level (NIST PQC level 1) are all fixed. Nothing to misconfigure.
- 🔌 Two real backends, auto-selected — native
cryptographypreferred, with aliboqsfallback. No simulated math. - ⚡ Inherits native speed — sits above the primitives, so it gets faster the moment your platform ships native ML-KEM.
- ✅ KAT-validated — checked byte-for-byte against the official draft vectors (see the status note above).
- 🧩 Consistent, predictable API — functional and class forms return values in the same order; fixed 1216-byte public keys, 1120-byte ciphertexts, and a 32-byte shared secret.
- 🪶 Narrow, auditable scope — X-Wing only, MIT-licensed.
Quick Start
Both APIs return values in the same order — encapsulate() gives you
(shared_secret, ciphertext).
Functional API:
from xwing_kem import generate_keypair, encapsulate, decapsulate
kp = generate_keypair() # kp.public_key, kp.secret_key
shared_sender, ciphertext = encapsulate(kp.public_key)
shared_recipient = decapsulate(ciphertext, kp.secret_key)
assert shared_sender == shared_recipient # 32-byte shared secret
Class-based API:
from xwing_kem import XWing
kem = XWing()
public_key, secret_key = kem.generate_keypair()
shared_sender, ciphertext = kem.encapsulate(public_key)
shared_recipient = kem.decapsulate(ciphertext, secret_key)
assert shared_sender == shared_recipient
The only difference between the two forms is how the key pair is handed back:
the functional generate_keypair() returns a small XWingKeyPair object
(.public_key / .secret_key), while XWing.generate_keypair() returns a
plain (public_key, secret_key) tuple. Pick whichever reads better for you.
More runnable scripts live in
examples/.
Installation
pip install xwing-kem
That's it — the library auto-detects an ML-KEM backend at import time, so most
users need nothing further. ML-KEM-768 is provided natively by cryptography>=48
when its wheel is built against OpenSSL 3.5+, AWS-LC, or BoringSSL. If your
platform's wheel lacks post-quantum support, install the optional liboqs
fallback backend:
pip install "xwing-kem[liboqs]" # requires liboqs.so on the system
See Backend Selection to inspect or pin the active choice.
When to Use X-Wing
Reach for X-Wing when:
- You're adding post-quantum protection to key exchange and want defense-in-depth — security that holds if either the classical or the post-quantum component is later broken.
- You want a standards-track, opinionated construction with no knobs to tune, suitable for HPKE-style sealing, secure messaging, or wrapping data keys.
- You'd otherwise be tempted to glue X25519 and ML-KEM together yourself — this library is exactly that glue, done to spec.
Look elsewhere when:
- You need a bare ML-KEM or bare X25519 KEM (use
cryptographyorliboqsdirectly). - You need signatures or an authenticated KEM — out of scope here.
- You require certified constant-time guarantees across the whole stack; the Python combiner layer offers no timing guarantee (see Validation & Limitations).
Why X-Wing?
Migrating to post-quantum cryptography, you almost never want a raw KEM — you want a hybrid that survives the failure of either half. The dangerous part is the combiner that mixes the two shared secrets; a hand-rolled one is the classic PQ-migration footgun, and getting the byte order or omitted inputs wrong silently produces incompatible or insecure secrets.
X-Wing removes that risk by being opinionated and concrete:
- No parameters. The constituent algorithms (X25519 + ML-KEM-768), the combiner hash (SHA3-256), and the security target are all fixed by the spec.
- Belt-and-suspenders security. An attacker must defeat both a well-studied elliptic curve and a NIST-standardized lattice KEM.
- One obvious API.
generate_keypair/encapsulate/decapsulate, fixed lengths, a 32-byte secret — easy to wire into HPKE, TLS-like handshakes, or file/message encryption.
This library exists so you can adopt the vetted construction instead of writing the combiner yourself.
The X-Wing Construction
The 32-byte shared secret is the SHA3-256 hash of the two component secrets, the X25519 ciphertext and public key, and a fixed label:
ss = SHA3-256( ss_M || ss_X || ct_X || pk_X || XWING_LABEL )
| Symbol | Meaning | Size |
|---|---|---|
ss_M |
ML-KEM-768 shared secret | 32 B |
ss_X |
X25519 raw shared secret | 32 B |
ct_X |
X25519 ciphertext (the ephemeral public key) | 32 B |
pk_X |
recipient's X25519 public key | 32 B |
XWING_LABEL |
the 6-byte X-Wing sigil, appended last | 6 B |
The ML-KEM ciphertext ct_M is deliberately omitted from the hash:
ML-KEM-768 is ciphertext-collision-resistant, so mixing it in is unnecessary,
and leaving it out is exactly what gives X-Wing its performance edge over a
generic combiner.
Backend Selection
By default, no choice is required. The library auto-selects a backend at
first use — native cryptography first, then liboqs — and caches it. You can
inspect the active choice or pin it explicitly:
import xwing_kem
from xwing_kem import generate_keypair, XWing
# Which backend is active right now?
print(xwing_kem.active_backend()) # 'cryptography' or 'liboqs'
# Force a specific backend for a single call...
kp = generate_keypair(prefer_backend="liboqs")
# ...or for every operation on an object-style instance:
kem = XWing(prefer_backend="cryptography")
print(kem.backend_name) # 'cryptography'
public_key, secret_key = kem.generate_keypair()
prefer_backend accepts "cryptography", "liboqs", or None (auto). An
unavailable choice raises rather than silently falling back, so pinning is
always explicit and predictable.
Performance
xwing-kem is intentionally a thin, correct layer over the primitives — all the
heavy lattice and curve math runs in vetted C inside the chosen backend, not
in Python. That has two practical consequences:
- It inherits native speed. As native ML-KEM lands in
cryptography/ OpenSSL, this package gets faster automatically — no code change on your side. - The construction itself is lean. By omitting the ML-KEM ciphertext from the combiner, X-Wing avoids extra hashing that a generic combiner would incur.
Indicative single-thread timings (median of 300 runs, cryptography backend,
Python 3.12, x86-64), compared against pure X25519 used KEM-style (ephemeral
keygen + DH for "encapsulate", DH for "decapsulate"):
| Operation | X-Wing | Pure X25519 | Overhead |
|---|---|---|---|
| keygen | ~240 µs | ~51 µs | 4.7× |
| encapsulate | ~145 µs | ~117 µs | 1.2× |
| decapsulate | ~294 µs | ~54 µs | 5.5× |
Takeaway: full post-quantum protection costs roughly 2–6× a bare X25519 exchange, yet every operation still completes in well under a millisecond. Encapsulation is nearly free relative to X25519, because ML-KEM encapsulation is fast and X-Wing layers only a single curve operation on top. Numbers vary by machine, Python version, and backend.
Validation & Limitations
This project follows an honest-documentation discipline. The short version:
- ✅ KAT-validated. Key generation from seed and the SHA3-256 combiner are checked byte-for-byte against all three official draft vectors; derandomized encapsulation is verified against the reference implementation.
- ⚠️ Randomized
encapsulate()is validated transitively. No backend exposes seeded encapsulation, so it is covered by round-trip + keygen KAT + combiner KAT + reference-encaps KAT rather than a direct ciphertext-byte match. - ⚠️ Constant-time only in the C primitives. The Python combiner glue does no secret-dependent branching, but Python offers no timing guarantees — don't rely on this layer in a hostile co-located environment.
- ⚠️ Secret keys are not portable between backends (
cryptographystores the 64-byte seed form;liboqsthe expanded form).
The complete accounting lives in KNOWN-GAPS.md. Please read it before depending on this in production.
Design Notes
- Future-proof by position. This package sits above the primitives, so it inherits faster/native ML-KEM the moment your wheel has it.
- No simulated cryptography. Both backends use real, vetted C implementations.
- Deliberately narrow scope. X-Wing only — no ML-KEM-1024 variant, no authenticated KEM, no signatures, no HPKE/TLS wiring.
Contributing & Security
Contributions are welcome — see CONTRIBUTING.md. For vulnerability reports and the disclosure process, see SECURITY.md.
License
MIT.
X-Wing is the work of the IETF CFRG draft authors; this package is just a faithful, well-tested Python home for it. If it helps you ship post-quantum protection with a little more confidence, it's doing its job — issues and improvements are always welcome.
Soli Deo Gloria — 1 Corinthians 10:31.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file xwing_kem-0.2.0.tar.gz.
File metadata
- Download URL: xwing_kem-0.2.0.tar.gz
- Upload date:
- Size: 33.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
96a26c36cfcda8a7f3a9059592bfa1a22176bec45655f5a77038d8b8b8a72f9d
|
|
| MD5 |
7e6ebaa9c3853c1fa2cf5b171e3fa349
|
|
| BLAKE2b-256 |
21d31571639ae2f8f87cf855de10f3fac3f1fc13d8dace57ebc476610c7c28b9
|
Provenance
The following attestation bundles were made for xwing_kem-0.2.0.tar.gz:
Publisher:
release.yml on systemslibrarian/xwing-kem
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
xwing_kem-0.2.0.tar.gz -
Subject digest:
96a26c36cfcda8a7f3a9059592bfa1a22176bec45655f5a77038d8b8b8a72f9d - Sigstore transparency entry: 1697208500
- Sigstore integration time:
-
Permalink:
systemslibrarian/xwing-kem@567bc111ac0899a405ca1f9d731843d8fc182fe3 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/systemslibrarian
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@567bc111ac0899a405ca1f9d731843d8fc182fe3 -
Trigger Event:
push
-
Statement type:
File details
Details for the file xwing_kem-0.2.0-py3-none-any.whl.
File metadata
- Download URL: xwing_kem-0.2.0-py3-none-any.whl
- Upload date:
- Size: 12.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e3e8d9732c141cb34ba93ae440910ffd97986ff2dd9da5e00de44eb7ac2a3f84
|
|
| MD5 |
e48f2676e468ad5cd908a19a7ed9b4af
|
|
| BLAKE2b-256 |
f60757ae34419e435e2ed141998a794ff8dfb3b89e86733d781ecf367186dfc6
|
Provenance
The following attestation bundles were made for xwing_kem-0.2.0-py3-none-any.whl:
Publisher:
release.yml on systemslibrarian/xwing-kem
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
xwing_kem-0.2.0-py3-none-any.whl -
Subject digest:
e3e8d9732c141cb34ba93ae440910ffd97986ff2dd9da5e00de44eb7ac2a3f84 - Sigstore transparency entry: 1697208739
- Sigstore integration time:
-
Permalink:
systemslibrarian/xwing-kem@567bc111ac0899a405ca1f9d731843d8fc182fe3 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/systemslibrarian
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@567bc111ac0899a405ca1f9d731843d8fc182fe3 -
Trigger Event:
push
-
Statement type: