Skip to content

noise: hybrid post-quantum handshake (X25519 + ML-KEM-768) - #727

Open
royzah wants to merge 2 commits into
libp2p:masterfrom
royzah:noise-pq-mlkem768-hfs
Open

noise: hybrid post-quantum handshake (X25519 + ML-KEM-768)#727
royzah wants to merge 2 commits into
libp2p:masterfrom
royzah:noise-pq-mlkem768-hfs

Conversation

@royzah

@royzah royzah commented Jul 6, 2026

Copy link
Copy Markdown

Concrete draft for #723, so we have something byte-level to react to instead of an open question.

Additive, separately negotiated handshake that mixes ML-KEM-768 into the XX pattern via Noise HFS. Advertised ahead of /noise, falls back with no flag day. Auth stays classical (identity key signs the static key), only secrecy is hybridized.

Pins the protocol id and suite, defers token/KDF order to Noise HFS. Still open (called out in the doc):

  1. the protocol id string
  2. test vectors so go/js/rust match byte-for-byte
  3. ML-KEM-768 only vs the 512/768/1024 family

Reference impl: libp2p/rust-libp2p#6481 (off-by-default mlkem-hfs). Would love go/js eyes early. cc @jxs @thomaseizinger @mxinden @MarcoPolo @achingbrain

Comment thread noise/pq-mlkem768-hfs.md Outdated
Mixed into the handshake hash, so it must match byte-for-byte:

```text
Noise_XXhfs_25519+ML-KEM-768_ChaChaPoly_SHA256

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, 8.2 allows alphanumerics and / only. Suite is now Noise_XXhfs_25519+MLKEM768_ChaChaPoly_SHA256, and both implementations follow:

Handshake tests pass on both. Thanks for catching it.

Comment thread noise/pq-mlkem768-hfs.md Outdated
```

Advertised ahead of `/noise`. Two capable peers pick it; otherwise negotiation
falls back to `/noise` with no extra round trip.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that there's still a roundtrip if the receiver does not support mlkem768:

  1. Emitter sends /noise-mlkem768-hfs/0.1.0
  2. Receiver replies na
  3. Emitter sends /noise
  4. Receiver replies back with /noise
  5. Handshake starts

@richard-ramos richard-ramos Aug 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idea for lines 27-28:

An initiator supporting both protocols SHOULD propose `/noise-mlkem768-hfs/0.1.0` before /noise. A responder supporting this protocol MUST accept that protocol ID. If the responder returns `na`, the initiator MAY subsequently propose /noise.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, the na costs a round trip. Took your wording verbatim, so the claim is gone.

Comment thread noise/pq-mlkem768-hfs.md Outdated
Comment on lines +65 to +66
## To pin for interop

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rename this to Open issues or similar.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to "Open issues".

Comment thread noise/pq-mlkem768-hfs.md Outdated
-> s, se
```

- `e1`: initiator sends the ML-KEM-768 encap key (1184 B).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `e1`: initiator sends the ML-KEM-768 encap key (1184 B).
- `e1`: initiator sends the ML-KEM-768 encapsulation key (1184 B).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied.

Comment thread noise/pq-mlkem768-hfs.md Outdated
## Why

[`/noise`][noise] is X25519 only, so recorded sessions are open to
harvest-now-decrypt-later. This adds a separate handshake that mixes an

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
harvest-now-decrypt-later. This adds a separate handshake that mixes an
harvest-now-decrypt-later. This specification adds a separate handshake that mixes an

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied.

Comment thread noise/pq-mlkem768-hfs.md Outdated
ML-KEM-768 KEM into the same `XX` pattern via Noise [HFS][hfs]. It stays secure
if either primitive holds.

Auth is unchanged and stays classical: in libp2p the identity key signs the

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Auth is unchanged and stays classical: in libp2p the identity key signs the
Authentication is unchanged and stays classical: in libp2p the identity key signs the

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied.

Comment thread noise/pq-mlkem768-hfs.md Outdated

## Handshake

`XX` with the `hfs` modifier. Token placement and KDF order are exactly Noise

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`XX` with the `hfs` modifier. Token placement and KDF order are exactly Noise
`XX` with the `hfs` modifier. Token placement and KDF order are exactly as specified by Noise

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied.

Comment thread noise/pq-mlkem768-hfs.md Outdated
- rust-libp2p: [#6481][pr], off-by-default `mlkem-hfs` feature; classical path
untouched. Revives [#2168][old].
- KEM: ML-KEM-768 in `snow` ([mcginty/snow#210][snow]). Until it ships in a
release, the impl pins `snow` via `[patch]`; KATs land with it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
release, the impl pins `snow` via `[patch]`; KATs land with it.
release, the implementation pins `snow` via `[patch]`; KATs land with it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied.

Comment thread noise/pq-mlkem768-hfs.md Outdated
Comment on lines +56 to +63
```mermaid
sequenceDiagram
participant I as Initiator
participant R as Responder
I->>R: e, e1
R->>I: e, ee, ekem1, s, es (+ identity)
I->>R: s, se (+ identity)
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this diagram needed or would the pattern described in line 43 be enough?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, the pattern block says it. Diagram removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

2 participants