Local RFC: 621 2026-08-10 Category: Experimental
YIFF Wire Protocol, version 2
The protocol as seen by a client. Each finished WebTransport unidirectional stream contains one frame, no more than 1024 bytes. Integers are unsigned big-endian.
How to read a frame
Every strip below is one frame. Read fields left to right. Labels, offsets, and sizes are visible inline; point at, focus, or tap any field to inspect every byte.
| Type byte | Frame | Type byte | Frame |
|---|---|---|---|
01 | SAY | 02 | MESSAGE |
10 | HELLO | 11 | INTRODUCE |
12 | INTRODUCED | 13 | NONCE_REFILL |
Every frame starts with magic byte 59 (Y). Labels are four ASCII uppercase Base36 bytes. AES-256-GCM emits ciphertext || tag[16]. A counter becomes IV 00 00 00 00 00 00 00 00 || counter:u32.
Worked identity. The strips use MUTT #1 · WOOF, secret 0000-0000-0000-0000-0000-8LSP, and HELLO context 00 01 02 … 1f. The browser keeps that textual secret local. Its transmitted credential hash is e4 3f 84 49 d1 9d 48 03 c4 6b 90 57 95 bd 40 5e 8f 4d b1 32 03 8c 14 89 97 b4 16 e3 58 4f 6a 2c.
1. RECEIVE HELLO
Compute H = SHA-256(secret digits), then derive three 32-byte HKDF-SHA-256 keys with IKM = H and salt = connection_context:
| Exact info bytes | Key use |
|---|---|
YIFF v2 introduction | INTRODUCE / INTRODUCED |
YIFF v2 client to server | SAY |
YIFF v2 server to client | MESSAGE / NONCE_REFILL |
2. SEND INTRODUCE
Claims, resumes, and probes all use this one label-free frame. The 12 clear random bytes are the bootstrap IV and bytes 0–13 are AAD. The public AES-256-GCM key is the 32 ASCII bytes YIFF-v2 public claim bootstrap!!.
Every label is derived, never requested:
H = SHA-256(secret digits)D = SHA-256("YIFF v2 label\\0" || callsign_utf8 || claim_number:u8 || H)label = Base36(u32_be(D[0..4]) mod 36⁴)The domain ends in literal bytes 5c 30, not NUL. The worked proof derives WOOF. To request another exact label, the browser searches for a different secret locally; the target label never crosses the network.
Probe, automatic claim, proof, and resume
A zero hash with claim 00 is a stateless probe for the next number. A real hash with claim 00 resumes a matching callsign/hash or automatically claims the next number. A real hash with claim 01–ff resumes that matching claim or creates it only when it is current. A stale request receives the current number without creating a claim. Exhaustion follows claim 255.
The server derives the label before storage and checks it globally. Derived 0000 and occupied labels are unavailable. Resuming sends the same unified callsign/hash/number payload; no resume label exists on the wire.
3. RECEIVE INTRODUCED
| Status plaintext | Meaning |
|---|---|
00 || label[4] || claim:u8 || nonce_start:u32 | Session established. |
01 || unavailable_label[4] | Derived label is reserved or already occupied. |
02 || current_claim:u8 | Probe result or stale-number correction. |
03 | All 255 claims for this callsign are allocated. |
Every result is encrypted with the introduction key derived from the submitted hash and current HELLO context. A probe therefore uses the all-zero hash as its key input.
4. RECEIVE MESSAGE
After INTRODUCED, the server sends retained 24-hour history, then live messages. Every recipient gets a separately encrypted frame.
5. SEND SAY
The plaintext is one trimmed UTF-8 body of 1–480 bytes.
6. RECEIVE NONCE_REFILL
Counter and rejection rules
Each successful introduction persistently allocates 256 client counters. The server marks a counter used before authenticating SAY. A replay, bad tag, invalid body, rate-limited send, or counter outside the current or queued range is silently discarded; an attempted counter remains burned.
After accepting range offset 223, 32 counters remain and one refill is sent. Entering the queued range retires the old range and its 256-bit replay bitmap. MESSAGE and NONCE_REFILL share one per-connection atomic server counter starting at zero; it never wraps.
Callsigns are trimmed, nonempty UTF-8 of at most 24 bytes. Accepted plaintext messages are journaled for 24 hours. Claim number 00 is reserved; valid claims are 1–255. Label 0000 is never assigned. Version 1, transmitted desired labels, VERIFY, REJECT, and plaintext SAY or MESSAGE do not exist.