Skip to content

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):

ComponentSize
Decapsulation Key (seed)32 bytes
Public Key1216 bytes (1184 ML-KEM-768 + 32 X25519)
Ciphertext1120 bytes (1088 ML-KEM-768 + 32 X25519)
Shared Secret32 bytes

In-memory expanded key (Crypto++ internal storage):

ComponentSize
Private Key2464 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 key
  • publicKeyLen - 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 generator
  • ciphertext - Output buffer for ciphertext (1120 bytes)
  • sharedSecret - Output buffer for shared secret (32 bytes)

Details:

  1. Performs ML-KEM-768 encapsulation to get ct_M and ss_M
  2. Generates ephemeral X25519 key pair and computes DH with recipient’s X25519 public key to get ss_X
  3. 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:

  1. Parses ciphertext into ML-KEM-768 ciphertext and X25519 ephemeral public key
  2. Performs ML-KEM-768 decapsulation to get ss_M
  3. Performs X25519 DH with ephemeral public key to get ss_X
  4. 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

AspectX-WingML-KEM-768
Public Key1216 bytes1184 bytes
Decapsulation Key (seed)32 bytes64 bytes
Ciphertext1120 bytes1088 bytes
Quantum SecurityYes (ML-KEM)Yes
Classical SecurityYes (X25519)Yes
Security if ML-KEM brokenYes (X25519)No
Security if X25519 brokenYes (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

  1. 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.

  2. Shared Secret Usage - Never use the shared secret directly as a key. Always derive keys using a KDF (HKDF, SHA-3, etc.).

  3. Domain Separation - The X-Wing combiner includes a unique label that prevents cross-protocol attacks.

  4. Forward Secrecy - For forward secrecy, use X-Wing in a handshake that rotates or uses ephemeral recipient keys.

  5. 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