X-Wing
Header: #include <cryptopp/xwing.h> | Namespace: CryptoPP
Since: cryptopp-modern 2026.3.0
Thread Safety: Treat instances as not thread-safe; use separate instances per thread
X-Wing is a hybrid key encapsulation mechanism that combines X25519 (classical ECDH) with ML-KEM-768 (post-quantum KEM). This provides security against both classical and quantum adversaries - the shared secret remains secure even if one of the two underlying algorithms is broken.
Key Features
- Hybrid security - Combines classical and post-quantum cryptography
- Defence in depth - Secure even if X25519 or ML-KEM is broken
- Industry aligned - Proposed in IETF CFRG with active industry involvement
- Simple API - Single encapsulate/decapsulate interface
- Efficient combiner - SHA3-256 with domain separation
Key Sizes
Wire encoding (per IETF draft):
| Component | Size |
|---|---|
| Decapsulation Key (seed) | 32 bytes |
| Public Key | 1216 bytes (1184 ML-KEM-768 + 32 X25519) |
| Ciphertext | 1120 bytes (1088 ML-KEM-768 + 32 X25519) |
| Shared Secret | 32 bytes |
In-memory expanded key (Crypto++ internal storage):
| Component | Size |
|---|---|
| Private Key | 2464 bytes (2400 ML-KEM-768 + 32 X25519 sk + 32 X25519 pk) |
The library stores the expanded form internally for efficient decapsulation. The 32-byte seed can be used with GenerateFromSeed() for deterministic key generation.
Note: ML-KEM is often represented as a 64-byte seed; X-Wing defines its own 32-byte seed for deriving the hybrid key material.
Security Levels
- Classical security: 128-bit (from X25519)
- Post-quantum security: NIST security category 3 (from ML-KEM-768, often compared to AES-192)
Quick Example
#include <cryptopp/xwing.h>
#include <cryptopp/osrng.h>
#include <cryptopp/secblock.h>
#include <iostream>
int main() {
using namespace CryptoPP;
AutoSeededRandomPool rng;
// === Recipient generates key pair ===
XWingDecapsulator recipient(rng);
// Extract public key to share with sender
SecByteBlock publicKey(recipient.GetKey().GetPublicKeySize());
recipient.GetKey().GetPublicKey(publicKey.begin());
// === Sender encapsulates with recipient's public key ===
XWingEncapsulator sender(publicKey.begin(), publicKey.size());
SecByteBlock ciphertext(sender.CiphertextLength());
SecByteBlock senderSecret(sender.SharedSecretLength());
sender.Encapsulate(rng, ciphertext.begin(), senderSecret.begin());
// === Recipient decapsulates to get shared secret ===
// Decapsulate always succeeds per X-Wing spec (implicit rejection).
// Invalid ciphertexts produce a pseudorandom shared secret.
SecByteBlock recipientSecret(recipient.SharedSecretLength());
recipient.Decapsulate(ciphertext.begin(), recipientSecret.begin());
// Both parties now have the same shared secret
std::cout << "Shared secrets match: "
<< ((senderSecret == recipientSecret) ? "YES" : "NO") << std::endl;
return 0;
}Usage Guidelines
Do:
- Use X-Wing for key exchange requiring long-term security
- Use the shared secret as input to a KDF for deriving session keys
- Store private keys securely using SecByteBlock
- Consider X-Wing as a strong default for hybrid KEM when you want defence in depth
Avoid:
- Using the shared secret directly as an encryption key (use a KDF)
- Replaying ciphertexts - treat them as single-use handshake messages
- Implementing your own hybrid combiner - X-Wing’s combiner is carefully designed
When to Use X-Wing
Use X-Wing when:
- You need defence in depth against both classical and quantum attacks
- Long-term confidentiality is required (10+ years)
- You want security even if lattice cryptography is later broken
- Building TLS-style handshakes or secure messaging key agreement systems
Consider ML-KEM instead when:
- Pure post-quantum is acceptable (no classical backup needed)
- Slightly smaller keys/ciphertexts are important (32 bytes less)
- Building a post-quantum-only system
Consider X25519 instead when:
- Quantum computers are not a concern for your threat model
- Minimum bandwidth is critical (32-byte keys vs 1216 bytes)
- Maximum performance is required
Class: XWingEncapsulator
Encapsulate (generate shared secret and ciphertext) using recipient’s public key.
Constants
CRYPTOPP_CONSTANT(PUBLIC_KEYLENGTH = 1216);
CRYPTOPP_CONSTANT(CIPHERTEXT_LENGTH = 1120);
CRYPTOPP_CONSTANT(SHARED_SECRET_LENGTH = 32);Constructors
Default Constructor
XWingEncapsulator();Create an uninitialized encapsulator. Set the public key before calling Encapsulate().
Constructor with Public Key
XWingEncapsulator(const byte* publicKey, size_t publicKeyLen);Create an encapsulator from recipient’s public key.
Parameters:
publicKey- Pointer to 1216-byte public keypublicKeyLen- Must be 1216
Example:
// Receive public key from recipient
SecByteBlock publicKey = /* received from recipient */;
XWingEncapsulator encapsulator(publicKey.begin(), publicKey.size());Methods
Encapsulate
void Encapsulate(RandomNumberGenerator& rng,
byte* ciphertext,
byte* sharedSecret) const;Generate a random shared secret and encrypt it for the recipient.
Parameters:
rng- Random number generatorciphertext- Output buffer for ciphertext (1120 bytes)sharedSecret- Output buffer for shared secret (32 bytes)
Details:
- Performs ML-KEM-768 encapsulation to get
ct_Mandss_M - Generates ephemeral X25519 key pair and computes DH with recipient’s X25519 public key to get
ss_X - Combines:
SHA3-256(ss_M || ss_X || ct_X || pk_X || label)
Example:
SecByteBlock ciphertext(encapsulator.CiphertextLength());
SecByteBlock sharedSecret(encapsulator.SharedSecretLength());
encapsulator.Encapsulate(rng, ciphertext.begin(), sharedSecret.begin());
// Send ciphertext to recipient
AccessPublicKey / GetPublicKey
XWingPublicKey& AccessPublicKey();
const XWingPublicKey& GetPublicKey() const;Access the public key for setting or retrieving.
CiphertextLength / SharedSecretLength
size_t CiphertextLength() const; // Returns 1120
size_t SharedSecretLength() const; // Returns 32
Class: XWingDecapsulator
Generate key pairs and decapsulate (recover shared secret from ciphertext).
Constants
CRYPTOPP_CONSTANT(SECRET_KEYLENGTH = 2464);
CRYPTOPP_CONSTANT(PUBLIC_KEYLENGTH = 1216);
CRYPTOPP_CONSTANT(CIPHERTEXT_LENGTH = 1120);
CRYPTOPP_CONSTANT(SHARED_SECRET_LENGTH = 32);Constructors
Default Constructor
XWingDecapsulator();Create an uninitialized decapsulator. Generate or load a key before calling Decapsulate().
Constructor with RNG (Generate New Key Pair)
XWingDecapsulator(RandomNumberGenerator& rng);Generate a new key pair.
Example:
AutoSeededRandomPool rng;
XWingDecapsulator decapsulator(rng);
// Share public key with senders
Methods
Decapsulate
bool Decapsulate(const byte* ciphertext, byte* sharedSecret) const;Recover the shared secret from a ciphertext.
Parameters:
ciphertext- Ciphertext from encapsulator (1120 bytes)sharedSecret- Output buffer for shared secret (32 bytes)
Returns: true always. Per the X-Wing spec, decapsulation never fails. If the ciphertext is invalid, a pseudorandom shared secret is produced via implicit rejection.
Details:
- Parses ciphertext into ML-KEM-768 ciphertext and X25519 ephemeral public key
- Performs ML-KEM-768 decapsulation to get
ss_M - Performs X25519 DH with ephemeral public key to get
ss_X - Combines:
SHA3-256(ss_M || ss_X || ct_X || pk_X || label)
Example:
SecByteBlock sharedSecret(decapsulator.SharedSecretLength());
decapsulator.Decapsulate(ciphertext.begin(), sharedSecret.begin());
// Use sharedSecret with a KDF
AccessPrivateKey / GetPrivateKey / GetKey
XWingPrivateKey& AccessPrivateKey();
const XWingPrivateKey& GetPrivateKey() const;
const XWingPrivateKey& GetKey() const;Access the private key.
Class: XWingPrivateKey
Holds the secret key material for decapsulation.
Methods
GenerateRandom
void GenerateRandom(RandomNumberGenerator& rng, const NameValuePairs& params);Generate a new random key pair.
Example:
XWingPrivateKey privateKey;
privateKey.GenerateRandom(rng, g_nullNameValuePairs);GenerateFromSeed
void GenerateFromSeed(const byte seed[32]);Generate key pair from a 32-byte seed (deterministic).
Example:
byte seed[32] = /* ... */;
XWingPrivateKey privateKey;
privateKey.GenerateFromSeed(seed);GetPublicKey
void GetPublicKey(byte* publicKey) const;Copy the public key (1216 bytes) to the output buffer.
Example:
SecByteBlock publicKey(XWingPrivateKey::PUBLIC_KEYLENGTH);
privateKey.GetPublicKey(publicKey.begin());GetPublicKeySize / GetPrivateKeySize
size_t GetPublicKeySize() const; // Returns 1216
size_t GetPrivateKeySize() const; // Returns 2464
Validate
bool Validate(RandomNumberGenerator& rng, unsigned int level) const;Check the key for validity.
Class: XWingPublicKey
Holds the public key material for encapsulation.
Methods
SetPublicKey
void SetPublicKey(const byte* key, size_t len);Set the public key bytes (must be 1216 bytes).
Example:
XWingPublicKey publicKey;
publicKey.SetPublicKey(keyData, 1216);GetPublicKeyBytePtr / GetPublicKeySize
const byte* GetPublicKeyBytePtr() const;
size_t GetPublicKeySize() const;Get the public key bytes.
GetMLKEMPublicKeyPtr / GetX25519PublicKeyPtr
const byte* GetMLKEMPublicKeyPtr() const; // First 1184 bytes
const byte* GetX25519PublicKeyPtr() const; // Last 32 bytes
Access the individual component public keys.
Validate
bool Validate(RandomNumberGenerator& rng, unsigned int level) const;Check the key for validity.
Key Serialization
Requires: #include <cryptopp/files.h> and #include <cryptopp/filters.h>
Saving Keys
XWingDecapsulator decapsulator(rng);
const auto& privKey = decapsulator.GetKey();
// Get public key to share
SecByteBlock publicKey(privKey.GetPublicKeySize());
privKey.GetPublicKey(publicKey.begin());
// Save public key to file or send to partner
FileSink pubFile("xwing_public.key");
pubFile.Put(publicKey.begin(), publicKey.size());Loading Keys
// Load public key for encapsulation
SecByteBlock publicKey(1216);
FileSource("xwing_public.key", true,
new ArraySink(publicKey.begin(), publicKey.size()));
XWingEncapsulator encapsulator(publicKey.begin(), publicKey.size());Using with a KDF
Always derive keys from the shared secret using a KDF:
#include <cryptopp/xwing.h>
#include <cryptopp/hkdf.h>
#include <cryptopp/sha3.h>
// Assumes decapsulator and ciphertext from earlier example
SecByteBlock sharedSecret(32);
decapsulator.Decapsulate(ciphertext.begin(), sharedSecret.begin());
// Derive encryption and MAC keys
HKDF<SHA3_256> hkdf;
SecByteBlock encryptionKey(32);
SecByteBlock macKey(32);
const byte info1[] = "encryption key";
hkdf.DeriveKey(encryptionKey.begin(), 32,
sharedSecret.begin(), 32,
nullptr, 0, info1, sizeof(info1)-1);
const byte info2[] = "mac key";
hkdf.DeriveKey(macKey.begin(), 32,
sharedSecret.begin(), 32,
nullptr, 0, info2, sizeof(info2)-1);
// Note: Use a protocol-specific salt if you have one
X-Wing Combiner Details
The shared secret is computed using the X-Wing combiner (per IETF draft-connolly-cfrg-xwing-kem):
SharedSecret = SHA3-256(ss_M || ss_X || ct_X || pk_X || label)Where:
ss_M= ML-KEM-768 shared secret (32 bytes)ss_X= X25519 shared secret (32 bytes)ct_X= X25519 ephemeral public key / ciphertext (32 bytes)pk_X= Recipient’s X25519 public key (32 bytes)label= 6-byte X-Wing domain separator:"\./"+"/^\\"(hex:5c 2e 2f 2f 5e 5c)
The label provides domain separation, ensuring X-Wing shared secrets cannot collide with other SHA3-256 uses.
Comparison with Pure ML-KEM
| Aspect | X-Wing | ML-KEM-768 |
|---|---|---|
| Public Key | 1216 bytes | 1184 bytes |
| Decapsulation Key (seed) | 32 bytes | 64 bytes |
| Ciphertext | 1120 bytes | 1088 bytes |
| Quantum Security | Yes (ML-KEM) | Yes |
| Classical Security | Yes (X25519) | Yes |
| Security if ML-KEM broken | Yes (X25519) | No |
| Security if X25519 broken | Yes (ML-KEM) | N/A |
Recommendation: Use X-Wing when you need defence in depth against both classical and quantum attacks, especially for long-term security requirements.
Performance
X-Wing adds minimal overhead over pure ML-KEM-768 due to the additional X25519 operations (X25519 is very fast). Expect roughly similar performance to ML-KEM-768 alone.
Security Considerations
Hybrid Guarantee - X-Wing is secure if either X25519 OR ML-KEM-768 is secure. Both would need to be broken to compromise the shared secret.
Shared Secret Usage - Never use the shared secret directly as a key. Always derive keys using a KDF (HKDF, SHA-3, etc.).
Domain Separation - The X-Wing combiner includes a unique label that prevents cross-protocol attacks.
Forward Secrecy - For forward secrecy, use X-Wing in a handshake that rotates or uses ephemeral recipient keys.
Binding Properties - The combiner binds the shared secret to both the X25519 ciphertext and public key, preventing misbinding attacks.
Specification Compliance
This implementation follows IETF draft-connolly-cfrg-xwing-kem:
- Combiner: SHA3-256 with 6-byte domain separation label (appended at end)
- Components: X25519 + ML-KEM-768
- Label:
"\./"+"/^\\"(hex:5c 2e 2f 2f 5e 5c)
Note: X-Wing is still an IETF draft. Identifiers (id-XWing) and encodings are defined in the draft but may change; check the current draft for the latest registry and ASN.1 details.
See Also
- IETF X-Wing Draft
- ML-KEM - Underlying post-quantum KEM
- FIPS 203 - ML-KEM Standard
- RFC 7748 - X25519
- X25519 - Classical key exchange
- PQC Overview - Algorithm comparison and guidance