Conversation
5cd4da1 to
a8c5bcf
Compare
|
Note on the
|
|
how about changing to use clatter, no_std compatible implementation of Noise protocol framework with Post-Quantum extensions. |
|
Thanks @getong for clatter shout-outs! It is great crate though. However, in libp2p the Noise static key doesn't prove who you are, the identity key signs it for that. So the handshake only needs PQ for secrecy (harvest-now-decrypt-later), which is what hfs already does. clatter's extra is PQ auth on the static key, which seems redundant here (that's an identity-key job, Ed25519 -> ML-DSA). Do you see where the static key itself needs to be PQ? Also interop: the protocol name feeds the handshake hash, so clatter's WDYT ? |
|
You are right , go ahead. |
|
Cheers @getong 🙏 Keeping clatter in mind for a future no_std path. |
|
H @royzah, thanks for this! Happy to have this in |
|
Hey @jxs. Only thing left blocking this is snow shipping ML-KEM so we can drop the |
|
Rebased on master and conflicts resolved. Both sides had independently migrated to snow 0.10, so the upstream shape won in Still a draft, still blocked on the same two things, neither of which has moved:
Keeping it current so it does not rot while those resolve. |
libp2p/rust-libp2p#6481 was written independently by @royzah. We did not contribute to it; we ran live interop against it from Python and the full three-message handshake completed with mutual authentication. Two implementations written separately from the same specification and interoperating on the wire is stronger evidence that the specification is unambiguous than more implementations by one author, so the README now says so rather than counting it as a fourth of ours.
|
Hi @royzah, I've been working on the same handshake from the other side of the ecosystem and wanted to share something useful All six pairwise combinations across the four implementations now complete a live TCP handshake, and no implementation needed a protocol change to interoperate with any other. The pairs involving yours:
and the rest of the matrix, for completeness: TypeScript with Python, TypeScript with Nim in both roles, and Python with Nim. The four were written independently against the prose spec, and against four different ML-KEM-768 libraries, yours is RustCrypto's One thing you might want to add, and it's the reason your pairs are listed as one-directional. The Rust tree provides I've published the interop vectors at https://github.com/paschal533/pq-noise-artifacts. Five vectors, fully seeded and deterministic, covering all three messages, so the Rust side can be checked against them without running a peer. There's a second independently generated suite in On the spec question @jxs raised. I opened One number in case it helps the case here. Holding the cryptographic backend constant within each implementation, the same protocol costs 10.7x classical in Python, 1.51x in JavaScript, 1.24x in Rust and 1.13x in Nim. Three of four sit between 1.1x and 1.5x. Worth saying that a lower ratio isn't automatically better, Nim's 1.13x is partly a larger denominator, since only its KEM reaches BoringSSL while its classical primitives come from BearSSL and pure Nim. Yours is the faster stack carrying the higher ratio, which is the arithmetic working as expected rather than anything wrong with it. Happy to run more interop against this as it changes, and to test anything you want checked from the Python, TypeScript or Nim side. |
Adds an additive, off-by-default 'mlkem-hfs' feature advertising Noise_XXhfs_25519+ML-KEM-768_ChaChaPoly_SHA256 under a new protocol id, falling back to classical /noise for older peers. Ports the crate to snow 0.10 (patched to our fork carrying the ML-KEM KEM) and widens the handshake scratch buffer for the KEM material.
3ad742e to
7f4ab76
Compare
|
Thanks, this is useful. Four implementations, four different ML-KEM-768 libraries, no protocol change needed, argues for the pattern better than any one of them can. On split(): half right, and that half is fixed. The test already sent a real application frame initiator to responder, so a swapped cs1/cs2 would have failed there. The reverse leg was missing, which left one transport key unexercised. Both directions covered now. On the dialer: the tree ships no examples at all, only integration tests. Happy to add a pair if it helps your harness. Will check against your vectors. Two conformance baselines beats one. On the spec, no point in libp2p/specs#716 and libp2p/specs#723 competing. Neither has a shepherd, which is the real problem. Better raised once, together. One thing gates this PR regardless: it carries a [patch.crates-io] onto a snow fork, since snow 0.10 ships hfs with pqclean_kyber1024 and no ML-KEM. That cannot land upstream, so mcginty/snow#210 is the hard dependency. The spec unblocks the design, snow unblocks the merge. @jxs this is the spec development you asked to wait for in June: four independent implementations interoperating with no protocol change on any side. Happy for either specs issue to carry it. |
|
Checked the vectors. They cannot validate this branch as it stands. The protocol names differ. Yours is Noise_XXhfs_25519+ML-KEM-768_ChaChaPoly_SHA256, mine is Noise_XXhfs_25519+MLKEM768_ChaChaPoly_SHA256. That string seeds h, so handshake_hash, cs1_k and cs2_k all diverge and neither side can finish a handshake with the other. I renamed on 2026-08-17 in both mcginty/snow#210 and here, so I think your Rust runs were a pre-rename checkout. I lean to the unhyphenated form, since HFS already spells its KEM Kyber1024 not Kyber-1024. But the spec should decide |
|
Hey @royzah, thanks for the update. I'm working on it right now. I will give my feedback as soon as possible. |
Corrects the timeline stated in 122756a. The rename did not reach libp2p/rust-libp2p#6481 only with the 16 September 2026 force-push: royzah/rust-libp2p 1ae21ce (authored 2026-08-17) was on the feat/noise-mlkem-hfs branch by 22 August 2026 at the latest (PushEvent 1ae21ce -> 3ad742e; 3ad742e already spells MLKEM768 and is the force-push's beforeCommit). e7a1286 is the rebased copy. The 5 September run was therefore already behind the pull request. Also note that negative control B comes from the earlier run 20260917T015709Z.
ChainSafe/js-libp2p-noise#665, libp2p/py-libp2p#1310 and libp2p/rust-libp2p#6481 are open draft pull requests (gh pr view --json isDraft,state), matching the existing Nim row.
Corrects the timeline stated in be162b4. The rename did not reach libp2p/rust-libp2p#6481 only with the 16 September 2026 force-push: royzah/rust-libp2p 1ae21ce (authored 2026-08-17) was on the #6481 branch by 22 August 2026 at the latest (PushEvent 1ae21ce -> 3ad742e; 3ad742e already spells MLKEM768 and is the force-push's beforeCommit). e7a1286 is the rebased copy. The 5 September nim<->rust run was therefore already behind the pull request.
|
You're right, and thanks for checking. Rename. Agreed on Re-run against your head. I rebuilt royzah#1 on Protocol id. I'd suggest moving to Two small things, only if they're useful. The Dialer. No need for examples on your side: royzah#1 now has both a listener and a dialer. snow#210. Understood that it's the actual merge gate. If it helps, I can post the regenerated vectors and the interop result there as well. Spec. Agreed, once and together. libp2p/specs#716 and your libp2p/specs#727 now use the same suite name, |
|
Hey @royzah. I'm closing libp2p/specs#716, let's continue with your spec libp2p/specs#727 |
Revives #2168.
Summary
Additive, off-by-default
mlkem-hfsfeature: a hybrid post-quantum Noise handshake (Noise_XXhfs_25519+ML-KEM-768_ChaChaPoly_SHA256) negotiated alongside/noise, falling back to classical X25519 for peers without it.Why
/noiseis classical X25519, so recorded sessions are exposed to harvest-now-decrypt-later. This mixes an ML-KEM-768 ephemeral KEM into the handshake (secure if either primitive holds). Static-key auth stays classical, which cannot be broken retroactively.Changes
mlkem-hfsfeature (off by default) ->snow/use-ml-kem/noise-mlkem768-hfs/0.1.0, advertised ahead of/noise; pattern selected from the negotiated idDefaultResolver(ring has no KEM)Open questions
/noise-mlkem768-hfs/0.1.0is provisional; it needs a cross-implementation spec (go/js-libp2p) before stabilizing. Happy to take it to libp2p/specs.snow; until released,[patch]points snow at a fork branch.ml-kemis unaudited (passes NIST vectors).