The check was spent on the first frame that decrypted, whatever it held.
A server tags the messages it chooses to, and a frame carrying no echo
reads as `echoed == 0`, which passes; on the way out it also disarmed the
check for good, so an attacker who rewrote the advertised version only had
to let one untagged frame through to be rid of the only thing that would
have caught it.
A zero now says nothing either way and leaves the check standing. Only a
non-zero echo settles the question, matching or refusing the stream.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab
Version 1 of the key exchange splits the exchanged key into a subkey per
direction, the way Noise splits its cipher states after the handshake, and
leaves the nonce layout and the handshake messages as they are. Each subkey
is a keyed BLAKE2b over the handshake transcript: both ephemeral public keys,
the version the responder advertised and the one the initiator picked, so a
change to any of it in transit leaves the two sides with different keys. The
original scheme is version 0, which is also what an absent field means.
Negotiated through new fields old peers do not see: the side that speaks
first advertises the newest version it has (`KeyExchange.version` from the
server, `IdPk.kx_version` inside the signed identity from the controlled
side), the other side answers with the highest both have, and the pair runs
that. Any old peer on either end keeps today's stream byte for byte. The
controlled side's advertisement sits inside the signature; the server's is
repeated in `RendezvousMessage.kx_advertised` on its first message after the
exchange, inside the encrypted channel, and a client that was shown a
different value in the clear refuses the stream.
`Encrypt::new` is unchanged; `new_split`, `set_key_split` and
`check_kx_advertised` are added beside it, and `box_pk_of` for a responder
that kept only the secret half of its ephemeral pair. The tests pin the
version 0 output as well as the split and the echo.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns