ADR 0013: X-Wing combiner at algorithm-id 0x02 (preview)
Status: Accepted (preview)
Context
ADR 0003 picked HKDF-SHA256 with transcript binding as the v1 KEM combiner. The argument was that it inherits IND-CCA security from each component KEM informally; we have no formal proof.
The X-Wing construction (draft-connolly-cfrg-xwing-kem) specifies a SHA3-256-based combiner explicitly for X25519 + ML-KEM-768 that has a published security analysis. v1 users who want that property should be able to opt in without forking the library or jumping algorithms entirely.
ADR 0012 had already added the
IKemCombiner seam; this ADR uses it.
Decision
Introduce a second algorithm-id value, HybridKemAlgorithm.X25519MlKem768XWing = 2,
backed by XWingKemCombiner (an IKemCombiner implementation that
produces the derived secret as:
SS = SHA3-256( ss_M || ss_X || ct_X || pk_X || XWingLabel )
where XWingLabel is the 6-byte literal \x5c\x2e\x2f\x2f\x5e\x5c from
the Combiner section of the X-Wing draft, ss_M is the ML-KEM-768 shared secret,
ss_X is the X25519 shared secret, ct_X is the X25519 "ciphertext"
(sender's ephemeral public key), and pk_X is the recipient's X25519
public key.
The v1 algorithm-id (X25519MlKem768 = 1, HKDF combiner) remains the
default for HybridKem.GenerateKeyPair() and for the
HybridKem.Default property; X-Wing is strictly opt-in via
GenerateKeyPair(HybridKemAlgorithm.X25519MlKem768XWing).
Honest scope: "X-Wing combiner over the v1 wire shape", NOT IETF X-Wing interop
PostQuantum.Hybrid v1 orders hybrid wire-format components classical-first at every algorithm-id:
[ 1B algorithm id ] [ X25519 ... ] [ ML-KEM ... ]
The IETF X-Wing draft orders components post-quantum-first:
[ ML-KEM ... ] [ X25519 ... ]
That means a HybridKemPublicKey exported at algorithm-id 0x02 is NOT
byte-compatible with an X-Wing implementation in any other language. To
spell this out:
- An X-Wing IETF reference implementation could not decapsulate
ciphertexts we produce at id
0x02(different byte ordering of the ML-KEM and X25519 components inside the blob). - Conversely, we cannot parse a blob another library produces in the
IETF X-Wing wire format using id
0x02.
What algorithm-id 0x02 does give you is the X-Wing combiner formula
applied to a PostQuantum.Hybrid v1-shaped key/ciphertext, so callers
inside this library's ecosystem get the X-Wing combiner's analysis
without needing to migrate wire formats.
Strict IETF X-Wing wire-format interop is deliberately deferred to a
later algorithm-id (likely 0x03) so the ordering split is visible at
the algorithm-id level.
Rationale
- Seam already exists.
IKemCombinerfrom ADR 0012's sibling refactor lets us add the combiner without touchingHybridKembeyond a registry dispatch. - Both combiners coexist. Existing v1 artifacts continue to work
unchanged at id
0x01. New callers opt in to id0x02by passing the enum value toGenerateKeyPair. - Default stays HKDF. The
HybridKem.Defaultproperty still returnsX25519MlKem768. TestHybridKem_Default_StaysX25519MlKem768defends against accidental default flips. - Implementation uses BouncyCastle's
Sha3Digest. The BCLSystem.Security.Cryptography.SHA3_256requires Windows 11 24H2 or an OpenSSL build with SHA3 — we cannot rely on it across the support matrix. BC's SHA3 implementation has been audited and is always available because we already depend on BouncyCastle.
Consequences
- The
HybridKemAlgorithmenum gains one public value. Documented as a preview member: subject to refinement before v2. KemCombiner.ForAlgorithmdispatches to the right combiner.IKemCombiner.Combinegrew arecipientClassicalPublicKeyparameter —HkdfTranscriptKemCombinerignores it;XWingKemCombinerrequires it.HybridKem.Decapsulatederives the recipient's X25519 public key from the private seed inside the sameSecureBufferscope so the derivation does not leak material.HybridKemPublicKey.Import,HybridKemPrivateKey.Import, andHybridKemCiphertext.FromBytesnow accept both algorithm ids.- The XML doc on the enum value is explicit about the preview status and the wire-format scope.
- An ADR for a future strict IETF X-Wing interop algorithm-id (
0x03?) will reference this one. Until that lands, v1.x users that want cross-implementation interop should stay onX25519MlKem768.
Amendment (2026-06-10): label position corrected to match the draft
As shipped in v1.0.1, XWingKemCombiner hashed the label first
(SHA3-256(label || ss_M || ss_X || ct_X || pk_X)). That order matched
draft-connolly-cfrg-xwing-kem-02; draft-03 moved the label to the end
("Move label at the end. As everything fits within a single block of
SHA3-256, this does not make any difference."), and it has stayed there
through draft-10 (March 2026), which was verified directly against the
draft source on 2026-06-10.
The two orders are security-equivalent but derive different secrets, so
the v1.0.1 combiner did not match the construction whose published
analysis this ADR cites. Because 0x02 is explicitly a preview member
("subject to refinement before v2"), has no cross-library interop by
design, and the wire layouts are unchanged (blobs parse identically —
only the derived secret differs, and mixed-version peers fail closed at
the AEAD layer), the combiner was corrected in place rather than
burning a new algorithm-id. SPEC.md records the v1.0.1 divergence under
"History".