Skip to content

Commit b7045b3

Browse files
3x3xX3N0Nclaude
andcommitted
Interop L1 PASSED: 40 of 40 from the document alone, in two rounds
Round 2 of the clean-room exercise: the same isolated implementer, the amended spec, no other input - 40/40 application messages byte-exact, 212/212 non-handshake packets decrypted, zero failures, on the amended spec first try. Verified independently by re-running the decoder here against the capture. The exit finding is as valuable as the pass: the frame catalog documented only the canonical 0x01 Msg, which the transport never sends. The wire reality - the 0x80 fec-seq marker frame plus the compact message frame with its type nibble and varint-delta msgId, and the per-direction msgId origin - was reverse-engineered from ciphertext by an implementer who had never seen the code, and is now normative in SPEC. The SPEC diff across the exercise is the score: a normative packet-protection section, a corrected packet layout, and the real message frames, none of which existed two rounds ago. Ledger in docs/INTEROP.md; reports and the passing decoder archived under interop/. Clean-room grade recorded honestly: rung-1 (instruction wall, attested compliance both rounds). L2 (active responder) now needs the control-frame layouts specified - the one gap the passive rung could leave open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 6c0856a commit b7045b3

5 files changed

Lines changed: 481 additions & 3 deletions

File tree

docs/INTEROP.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,18 @@ cross-implementation. Not scheduled until L2 exists.
6969
L1 passing settles "the wire format is real beyond this repo". L2 passing settles "the protocol is
7070
implementable from the document". The audit and formal-analysis halves of item 7 are unaffected — interop is
7171
evidence of implementability, not of security.
72+
73+
## Ledger
74+
75+
- **2026-08-29, L1 round 1: 0/40 — blocked, correctly.** The spec lacked the entire packet-protection key
76+
schedule, and its Packet section described only the long header. Fixed in SPEC ("Packet protection (v0)" is
77+
new; Packet corrected). Report: `interop/reports/L1-round1-report.md`.
78+
- **2026-08-29, L1 round 2: 40/40 byte-exact — L1 PASSED.** 212/212 non-handshake packets decrypted, zero
79+
failures, on the amended spec's first try. New gaps found on the way out: the wire message encoding (FEC seq
80+
frame + compact message frame) was absent from the frame catalog, which listed only the canonical `0x01`
81+
form the transport never sends; msgId origin per direction was undocumented. Fixed in SPEC. Report:
82+
`interop/reports/L1-round2-report.md`; the passing decoder: `interop/cleanroom-decoder/decoder.py`.
83+
- **Clean-room grade:** rung-1 (context-isolated agent; wall is instruction plus attested compliance, both
84+
rounds attested clean). A hard-sandboxed or human run remains the stronger form.
85+
- **Remaining before L2:** control-frame byte layouts (Ack/Grant/Repair/...) are still unspecified (round-2
86+
gap R2-5) — not needed for passive decoding, required for an active responder.

docs/SPEC.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,24 @@ pnLen/phase bits, shortConnId, true pn bytes). Tag length is the negotiated `tag
7171
- the sender pads so the sample always exists: the smallest unprotectable short packet is 25 bytes.
7272

7373
## Frames
74-
- `0x01 Msg(msgId, offset, fin, data)` — messages, not streams. Ordering/streams are a library above transport.
74+
75+
**Catalog correction (2026-08-29, found by the clean-room L1 round 2):** the transport does NOT send `0x01 Msg`
76+
on the wire in the established phase — that is the codec's canonical form (FrameCodec parses it, and it remains
77+
valid). What actually rides in every source packet is the pair below, previously undocumented:
78+
79+
- **FEC seq frame** `0x80 0x02 fecSeq(2, big-endian)` — an extension-framed marker carrying the packet's RLNC
80+
source sequence number, truncated to 16 bits and decoded pn-style against the largest fec seq seen. Its
81+
presence marks the packet as a source symbol. It uses extension framing (`type >= 0x80`, length-prefixed) so
82+
a peer that does not know it skips it — which is also why a naive reader files it under "grease".
83+
- **Compact message frame**, type byte `0x1?`: bits — `0x10` base, `0x04` = varint offset follows, `0x02` = fin,
84+
`0x01` = varint length follows (absent: the message data extends to the end of the packet). After the type
85+
byte: `msgId` as a **varint delta** from the previous compact frame's msgId in the same packet (0 base at
86+
packet start), then the optional offset varint, optional length varint, then data.
87+
- **msgId origin differs by direction**: the client's 0-RTT connect payload is its msgId 0, so its
88+
application messages start at 1; the server's start at 0. (Found empirically in L1 round 2; normative now.)
89+
90+
- `0x01 Msg(msgId, offset, fin, data)` — messages, not streams; canonical/codec form, valid but not emitted by
91+
the transport's data path. Ordering/streams are a library above transport.
7592
- `0x02 Ack(path, largest, ranges, ecnCe, rxTimeUs)` — per-path, carries ECN and receiver timestamp (OWD estimation).
7693
- `0x03 Grant(path, creditBytes, priority)` — receiver-driven CC.
7794
- `0x04 Repair(windowBase, windowLen, seed, symbol)` — RLNC over GF(256); coefficients regenerated from seed.

docs/TODO.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,15 @@ Each is real, tested, and off or unvalidated by choice — the work is the measu
118118

119119
## 7. Credibility: no interop, no audit, no formal analysis
120120

121-
The cheapest real progress is an **independent minimal decoder written from `docs/SPEC.md` alone**. If the spec
122-
cannot be implemented from the document, that is a finding; if it can, it is the first genuine interop evidence.
121+
**L1 PASSED 2026-08-29** — an independent decoder written from `docs/SPEC.md` alone (clean-room agent, both
122+
rounds' compliance attested, verified independently by re-running the decoder against the capture: 40/40
123+
byte-exact, 212/212 packets). It took two rounds: round 1 was blocked by the spec lacking the entire packet
124+
key schedule and mis-stating the short header; round 2 passed on the amended spec's first try and found the
125+
frame catalog missing the actual wire message encoding. **The SPEC diff across the exercise is the score**, and
126+
it was substantial — the document now contains a normative "Packet protection (v0)" section, a corrected Packet
127+
section, and the real message frames, none of which existed before. Ledger: `docs/INTEROP.md`; evidence:
128+
`interop/reports/`, `interop/cleanroom-decoder/`. Next rung: L2 (active responder), which first needs the
129+
control-frame layouts specified (round-2 gap R2-5).
123130
Interop now has its own ladder (`docs/INTEROP.md`, 2026-08-29): L0 publish golden vectors + a captured session
124131
with secrets (hours, not clean-room-bound); L1 clean-room passive decoder (days, highest information per
125132
effort); L2 clean-room active responder; L3 sustained interop. The contamination rule is what makes it a
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Tessera v0 clean-room decoder (round 2 -- full recovery).
4+
5+
Built from spec.md + captured wire data ONLY (no reference to any existing
6+
implementation). See report.md for the compliance statement and gap list.
7+
8+
Pipeline:
9+
1. Parse every datagram header (long-header handshake vs short-header data).
10+
2. Skip F_INITIAL (top flag bit) datagrams as parse-only.
11+
3. Derive per-direction packet keys from sessionKey per the amended spec's
12+
"Packet protection (v0)" section, remove header protection, open the
13+
ChaCha20-Poly1305 AEAD.
14+
4. Parse frames; reassemble Msg fragments into application messages.
15+
5. Print a census and compare against ground-truth.json (PASS/FAIL + verdict).
16+
"""
17+
import json, os
18+
19+
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
20+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
21+
from cryptography.hazmat.primitives.kdf.hkdf import HKDFExpand
22+
from cryptography.hazmat.primitives.hashes import SHA256
23+
from cryptography.hazmat.primitives.hmac import HMAC
24+
25+
HERE = os.path.dirname(os.path.abspath(__file__))
26+
27+
F_INITIAL = 0x80
28+
PN_OFFSET = 5 # flags(1) + shortConnId(4)
29+
SAMPLE_OFF = PN_OFFSET + 4 # sample assumes max pnLen 4
30+
HP_FLAG_MASK = 0x63 # pnLen bits (5-6) + key-phase bits (0-1)
31+
32+
FRAME_NAMES = {
33+
0x02: "Ack", 0x03: "Grant", 0x04: "Repair", 0x05: "PathChallenge",
34+
0x06: "Ping", 0x07: "PathResponse", 0x08: "Close", 0x09: "MaxData",
35+
0x0A: "AckFrequency", 0x80: "Msg", 0x81: "Padding",
36+
}
37+
38+
# --------------------------------------------------------------------------
39+
# Key schedule (amended spec, "Packet protection (v0)")
40+
# HKDF = extract-then-expand, absent (zero) salt, ASCII label as expand info.
41+
# --------------------------------------------------------------------------
42+
def hkdf(key, label, length):
43+
prk = HMAC(b"\x00" * 32, SHA256())
44+
prk.update(key)
45+
return HKDFExpand(SHA256(), length, label.encode()).derive(prk.finalize())
46+
47+
def direction_keys(secret):
48+
return {
49+
"key": hkdf(secret, "tessera pkt key", 32),
50+
"iv": hkdf(secret, "tessera pkt iv", 12),
51+
"hp": hkdf(secret, "tessera hp", 32),
52+
}
53+
54+
def hp_mask(hp_key, sample):
55+
enc = Cipher(algorithms.ChaCha20(hp_key, sample[:16]), mode=None).encryptor()
56+
return enc.update(b"\x00" * 5)
57+
58+
# --------------------------------------------------------------------------
59+
# QUIC-style truncated packet-number reconstruction (spec: "closest to
60+
# largestSeen+1 among candidates congruent to the truncation").
61+
# --------------------------------------------------------------------------
62+
def reconstruct_pn(truncated, pnlen, largest_seen):
63+
if largest_seen < 0:
64+
return truncated
65+
win = 1 << (8 * pnlen)
66+
base = (largest_seen + 1) & ~(win - 1)
67+
cand = base | truncated
68+
best = cand
69+
for c in (cand - win, cand, cand + win):
70+
if c >= 0 and abs(c - (largest_seen + 1)) < abs(best - (largest_seen + 1)):
71+
best = c
72+
return best
73+
74+
def open_packet(raw, keys, largest):
75+
"""Return (plaintext, pn, flags, pnlen) or raise on AEAD failure."""
76+
b = bytearray(raw)
77+
mask = hp_mask(keys["hp"], bytes(b[SAMPLE_OFF:SAMPLE_OFF + 16]))
78+
flags = b[0] ^ (mask[0] & HP_FLAG_MASK)
79+
pnlen = ((flags >> 5) & 0x03) + 1
80+
pn_bytes = bytearray(b[PN_OFFSET:PN_OFFSET + pnlen])
81+
for i in range(pnlen):
82+
pn_bytes[i] ^= mask[1 + i]
83+
pn = reconstruct_pn(int.from_bytes(pn_bytes, "big"), pnlen, largest)
84+
85+
nonce = bytearray(keys["iv"]) # nonce = iv, low 8 bytes XOR pn (BE)
86+
pnb = pn.to_bytes(8, "big")
87+
for i in range(8):
88+
nonce[4 + i] ^= pnb[i]
89+
aad = bytes([flags]) + bytes(b[1:PN_OFFSET]) + bytes(pn_bytes) # pre-HP header
90+
ct = bytes(b[PN_OFFSET + pnlen:])
91+
pt = ChaCha20Poly1305(keys["key"]).decrypt(bytes(nonce), ct, aad)
92+
return pt, pn, flags, pnlen
93+
94+
# --------------------------------------------------------------------------
95+
# Frame / Msg decoding
96+
# --------------------------------------------------------------------------
97+
def read_varint(buf, i):
98+
prefix = buf[i] >> 6
99+
ln = 1 << prefix
100+
val = buf[i] & 0x3f
101+
for k in range(1, ln):
102+
val = (val << 8) | buf[i + k]
103+
return val, i + ln
104+
105+
def parse_msg_frame(pt):
106+
"""
107+
Empirically decoded Msg frame (spec's compact Msg; the byte layout is not
108+
in the spec -- see gap R2-1):
109+
0x80 0x02 fragSeq(2, BE) flags(1) msgId(1) [offset(varint) if flags&0x04]
110+
data...(implied length = to end of packet; Msg is the sole/first frame)
111+
flags bits: 0x10 base, 0x02 = FIN, 0x04 = OFFSET present.
112+
"""
113+
flags = pt[4]
114+
msg_id = pt[5]
115+
i = 6
116+
offset = None
117+
if flags & 0x04:
118+
offset, i = read_varint(pt, i)
119+
fin = bool(flags & 0x02)
120+
return msg_id, flags, offset, fin, pt[i:]
121+
122+
# --------------------------------------------------------------------------
123+
def main():
124+
ds = [json.loads(l) for l in open(os.path.join(HERE, "datagrams.jsonl"))]
125+
meta = json.load(open(os.path.join(HERE, "meta.json")))
126+
gt = json.load(open(os.path.join(HERE, "ground-truth.json")))
127+
session_key = bytes.fromhex(meta["sessionKeyHex"])
128+
129+
keys = {
130+
"c2s": direction_keys(hkdf(session_key, "tessera-v0.3 c2s", 32)),
131+
"s2c": direction_keys(hkdf(session_key, "tessera-v0.3 s2c", 32)),
132+
}
133+
134+
census = {"c2s": 0, "s2c": 0}
135+
handshake = decrypted = failed = 0
136+
frame_counts = {}
137+
largest = {"c2s": -1, "s2c": -1}
138+
reasm = {"c2s": {}, "s2c": {}}
139+
140+
for d in ds:
141+
census[d["dir"]] += 1
142+
raw = bytes.fromhex(d["hex"])
143+
if raw[0] & F_INITIAL:
144+
handshake += 1
145+
continue
146+
try:
147+
pt, pn, flags, pnlen = open_packet(raw, keys[d["dir"]], largest[d["dir"]])
148+
except Exception:
149+
failed += 1
150+
continue
151+
decrypted += 1
152+
if pn > largest[d["dir"]]:
153+
largest[d["dir"]] = pn
154+
155+
# A data packet begins with a Msg frame (0x80 0x02); other packets carry
156+
# Ack/Grant/Repair/etc. We count the leading frame type for the census
157+
# and only reassemble from true Msg frames (never from Repair copies).
158+
ftype = pt[0] if pt else None
159+
if ftype == 0x80 and len(pt) >= 2 and pt[1] == 0x02:
160+
name = "Msg"
161+
else:
162+
name = FRAME_NAMES.get(ftype, f"0x{ftype:02x}" if ftype is not None else "empty")
163+
frame_counts[name] = frame_counts.get(name, 0) + 1
164+
165+
if name == "Msg":
166+
mid, mflags, off, fin, data = parse_msg_frame(pt)
167+
R = reasm[d["dir"]].setdefault(mid, {"buf": bytearray(), "fin": False})
168+
o = off if off is not None else len(R["buf"])
169+
if o + len(data) > len(R["buf"]):
170+
R["buf"].extend(b"\x00" * (o + len(data) - len(R["buf"])))
171+
R["buf"][o:o + len(data)] = data
172+
if fin:
173+
R["fin"] = True
174+
175+
# Direction -> ground-truth index. Client msgId 0 was the 0-RTT payload
176+
# (carried in the parse-only initial), so post-handshake c2s msgIds are
177+
# 1-based; the server sent no 0-RTT, so s2c msgIds are 0-based.
178+
base = {"c2s": 1, "s2c": 0}
179+
180+
def recovered(dirk, count):
181+
out = []
182+
for idx in range(count):
183+
R = reasm[dirk].get(idx + base[dirk])
184+
out.append(bytes(R["buf"]).hex() if R else None)
185+
return out
186+
187+
rec = {"c2s": recovered("c2s", len(gt["clientToServer"])),
188+
"s2c": recovered("s2c", len(gt["serverToClient"]))}
189+
190+
# ---- census ----
191+
print("=" * 60)
192+
print("TESSERA CLEAN-ROOM DECODER -- CENSUS")
193+
print("=" * 60)
194+
print(f"datagrams: total={len(ds)} c2s={census['c2s']} s2c={census['s2c']}")
195+
print(f"handshake (F_INITIAL, parse-only): {handshake}")
196+
print(f"packets decrypted: {decrypted} failed: {failed}")
197+
print("leading-frame counts by type:")
198+
for k, v in sorted(frame_counts.items()):
199+
print(f" {k}: {v}")
200+
print("\nRecovered application messages (in order, hex):")
201+
for dirk in ("c2s", "s2c"):
202+
print(f" {dirk}: {sum(x is not None for x in rec[dirk])} message(s)")
203+
for h in rec[dirk]:
204+
shown = h if h and len(h) <= 64 else (h[:64] + "..." if h else "(missing)")
205+
print(" " + shown)
206+
207+
# ---- verdict ----
208+
print("\n" + "=" * 60)
209+
print("VERDICT vs ground-truth.json")
210+
print("=" * 60)
211+
total = passed = 0
212+
for dirk, truth in (("c2s", gt["clientToServer"]), ("s2c", gt["serverToClient"])):
213+
for idx, exp in enumerate(truth):
214+
total += 1
215+
got = rec[dirk][idx]
216+
ok = (got == exp)
217+
passed += ok
218+
if not ok:
219+
print(f" {dirk}[{idx:02d}] FAIL "
220+
f"(expected {len(exp)//2}B, got "
221+
f"{'-' if got is None else str(len(got)//2)+'B'})")
222+
print(f"\n {passed}/{total} messages byte-exact")
223+
print(f" OVERALL: {'PASS' if passed == total else 'FAIL'}")
224+
225+
226+
if __name__ == "__main__":
227+
main()

0 commit comments

Comments
 (0)