Repository navigation
Conversation
Under load, a session or pairing message 1 must carry a valid cookie for its exact source address (IP and port) before the host reads it, answers it from a cache, resends message 2 or uses the live address's reserve. Without one the host sends only a 64-byte cookie reply, for a known dev_id or while a QR window is open, and does no Noise read and no Diffie-Hellman. - Cookie: HMAC-SHA-256(secret, address)[..16]; the 32-byte secret rotates every 120 s and the previous one is still accepted. - Cookie reply: packet type 0x42, sealed with ChaCha20-Poly1305 under a key from K_dev or the QR PSK, with SHA-256 of the message 1 as AAD. - Message 1 with a cookie: a trailing EXT_COOKIE extension carrying HMAC-SHA-256(cookie, message 1)[..16]. - Load: 400 message 1 arrivals in this second or the one before, or half the host-wide Diffie-Hellman budget in use. - The reference session and pairing clients attach the cookie and resend at once when a reply arrives. Clients without cookie support connect whenever the host isn't under load. Adds vectors written by an independent Python script, the v3_cookie fuzz target, cookie parsers in v3_parsers, flood and rotation tests, and runs the host's crash-at-every-step tests with cookies forced on.
A cached message 2, or a cached Stale, TooNew or PairAgain answer, now goes without a cookie only to the exact address (IP and port) its message 1 came from. Any other address gets a cookie reply until it comes back with a valid cookie, whether or not the host is under load, and then has its own resend allowance. This applies to both the session and the pairing handshake. A cookie opens only under the handshake's own key, so copies of a message 1 replayed from other addresses, at any rate, no longer use any allowance the iPad's retries need. The shared budget for cached answers to other sources is removed, because only the key holder can reach it now.
Cached Stale, TooNew and PairAgain answers now have exact-address resend allowances, the same as message 2: the address their message 1 came from, or an address that came back with a valid cookie, each with its own 100 ms allowance. The per-IP replay budget for them is removed. Every other limit a replay or spoofed source could use up before the iPad's own message 1 now falls back to the cookie: - Message 1 reads (session and pairing): a message 1 that the IP's limit refuses gets a cookie reply. One with a valid cookie is limited per exact address instead, so another port on the iPad's IP never uses up its reads. - The dev_id's Stale/TooNew/PairAgain budget: an unverified message 1 over it gets a cookie reply instead of being remembered unanswered. Message 1s with a valid cookie have a budget of their own, and the table of remembered message 1s is sized for it.
- The reference client's test now covers a draining session: an unknown type isn't delivered and a Ping is, with draining set. It also checks that an unknown type on the live session still counts as liveness. - SessionMessage::parse says callers deliver Ok(None) only for application inner types, and the inner-type constants are documented as a group (0x50-0x53, and v2's Bye at 0x13). - The pairing read budget states its rate on average (up to twice that across a window boundary) and where the read cost was measured. - The many-senders pairing test expects message 2 on every retry, and notes that more senders than the source table holds is the cookie's case.
whoisaldo
force-pushed
the
feat/transport-v3-p2b3
branch
from
November 8, 2026 22:10
f289c41 to
5e2834d
Compare
whoisaldo
changed the base branch from
feat/transport-v3-pairing-limits
to
feat/transport-v3
November 8, 2026 22:10
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Based on
feat/transport-v3, after #252 (merged). It also carries the review follow-ups for #252's application-type rule: draining-session test coverage and doc fixes inwire.rs,pairing.rsandclient.rs.Phase 2b-iii of transport v3: a return-routability cookie for message 1 of both the session and the pairing handshakes, in the style of WireGuard's cookie reply.
Behaviour
Under load, every message 1 needs a valid cookie for its exact source address (IP and port). Without one, the host does nothing except send a cookie reply. It doesn't:
A reply goes back only for a known
dev_idor, for pairing, while a QR window is open; anything else is dropped as before. A valid cookie lifts no limit underneath it: an address flooding with its own cookie is still read 20 times a second.Below load, nothing a replay or spoofed source can use up stands between the iPad and its answer.
dev_id's Stale/TooNew/PairAgain budget: a message 1 over it without a cookie gets a cookie reply instead of being remembered unanswered. Message 1s with a valid cookie have a budget of their own.A cookie opens only under the handshake's key, so only the iPad reaches the limits a cookie leads to. The host-wide and per-IP budgets for cached answers to other sources are removed.
Load is 400 or more message 1 arrivals in this second or the one before (counted before any check), or, for sessions, half the host-wide Diffie-Hellman budget used this second.
The cookie is
HMAC-SHA-256(secret, address)[..16]. The 32-byte secret rotates every 120 s, and the previous one is still accepted, so a cookie is good for at least 120 s; the iPad keeps one for 110 s.The cookie reply is packet type
0x42in the v2 envelope, 64 bytes:session_id0,msg_seq0 andpayload_len48;ChaCha20-Poly1305(key, nonce, cookie), with AADSHA-256(message 1 without a cookie).The key is
HKDF(salt "EM v3 cookie", K_dev, info dev_id)for a session, orHKDF(salt, QR PSK, info "pair")for pairing, so only the iPad that sent that message 1 can read its reply. Every message 1 the host answers is longer than 64 bytes.Message 1 with a cookie ends in one more extension,
EXT_COOKIE(0x00FF), length 16:HMAC-SHA-256(cookie, message 1 without it)[..16]. The authenticated part of message 1 is unchanged.Cookie replies have no budget of their own. A budget would be the one thing a flood of spoofed message 1s for a known
dev_idcould use up to keep the genuine iPad from its cookie. Each reply is a fixed amount of symmetric work, smaller than what it answers, one per datagram received.The reference clients.
ClientandPairingClientattach a held cookie to every message 1. When a reply arrives for the message 1 being sent, they keep the cookie and resend at once.Compatibility with earlier v3 clients
v3 clients without cookie support still connect whenever the host isn't under load. While it is, they get cookie replies, drop them as an unknown packet type, and connect once the load has passed: about 2 s after a flood ends. When the host isn't under load, one more case: if a copy of such a client's message 1 reached the host first from another address, that attempt gets no message 2 (the client can't answer the cookie reply), and its next attempt, a new message 1 from its own address, connects as usual. No v3 client has been released; the iPad's v3 client implements cookies from the start, from
proto/testdata/v3/cookie_vectors.txt.Tests
cookie_vectors.txt): addresses, cookies, both keys,mac2, message 1 with a cookie, and both replies. They're written bycookie_vectors.py, which uses Python's hashlib/hmac and an RFC 8439 ChaCha20-Poly1305 checked against RFC 8439 §2.8.2. Message 1 is the golden transcript's.dev_ids;dev_id, half of them replays of its genuine old message 1s. Once the load is on there are no reads and no Diffie-Hellman, only one smaller cookie reply per datagram, and the iPad reconnects through the cookie in the middle of it.v3_cookie: the session table or the pairing host with cookies forced, a genuine iPad, and replays, bit flips and forgeries from its own or other addresses across secret rotations. No message 1 from another address is read or answered with anything but a smaller cookie reply, unknowndev_ids get nothing, and the iPad takes a cookie only from a reply the host produced.v3_parsersadds the reply parser and the cookie split.dev_ids;