§13 across engines, and the harness was only ever proving v1 #7

Merged
paulhitt merged 1 commits from 3-v3-cross-engine into main 2026-08-31 17:10:05 +00:00
paulhitt commented 2026-08-31 17:01:04 +00:00 (Migrated from gitlab.com)

This closes the in §15 — the Android PQ ratchet had never been run against
another engine.

The tripwire was wired to nothing

This file-s header said the day Kotlin gained ML-KEM was the day its v1
assertion would fail and get updated. That day passed and the assertion did not
move, because it could not:

const n = await negotiate(parsed, 1);
ok("JS reads the Kotlin bundle and negotiates v1", n.version === 1 && ...);

The harness capped negotiation at 1, so the bundle could have claimed anything
and negotiate() would still return v1. It had been proving v1 and nothing else.

What actually blocked v3

The driver published proto: 3 — and the web engine still read it as v1, correctly.
bundleFromWire discards the whole v2 block unless pqspk, pqspk_sig and
caps_sig are present, and negotiate() re-verifies both signatures. The driver
had been signing the bare PQ key with no domain string and sending no capability
signature at all.

Fixed to spec: FlamenetE2E_PQSPK_v2 ‖ pub, and FlamenetE2E_Caps_v2 ‖ uint8(3).

The assertion that matters

Thirteen new checks drive a v3 session both ways. The load-bearing one is the
web engine opening Kotlin-s reply
: to send it, Kotlin had to decapsulate with
its prekey seed, adopt the web engine-s pqdh, and encapsulate a fresh key —
and doing those three in the wrong order (§13.2) derives a different root key
with no error anywhere. Sixteen Kotlin unit tests cannot catch that, because
both sides of a self-test make the same mistake together.

Also pinned: a fresh ML-KEM key per step, not a reused one — reuse still
decrypts and silently gives up the healing §13.1 exists for.

v1 coverage kept

Every earlier section now asks the driver for a v1 bundle explicitly. Those paths
are not legacy trivia — the released fleet speaks them — and they would have
stopped being covered the moment the driver learned v3.

56 passed, 0 failed   (was 43)

What it found

paulhitt/flamenet-messenger-android!4 — the Android engine was sending v3 ratchet
steps to v2 peers, which would have broken every conversation with the released
0.6 build. §15 should not go to until that merges.

This closes the in §15 — the Android PQ ratchet had never been run against another engine. ### The tripwire was wired to nothing This file-s header said the day Kotlin gained ML-KEM was the day its v1 assertion would fail and get updated. That day passed and the assertion did not move, because it could not: ```js const n = await negotiate(parsed, 1); ok("JS reads the Kotlin bundle and negotiates v1", n.version === 1 && ...); ``` The harness capped negotiation at 1, so the bundle could have claimed anything and `negotiate()` would still return v1. It had been proving v1 and nothing else. ### What actually blocked v3 The driver published `proto: 3` — and the web engine still read it as v1, correctly. `bundleFromWire` discards the whole v2 block unless `pqspk`, `pqspk_sig` **and** `caps_sig` are present, and `negotiate()` re-verifies both signatures. The driver had been signing the bare PQ key with no domain string and sending no capability signature at all. Fixed to spec: `FlamenetE2E_PQSPK_v2 ‖ pub`, and `FlamenetE2E_Caps_v2 ‖ uint8(3)`. ### The assertion that matters Thirteen new checks drive a v3 session both ways. The load-bearing one is **the web engine opening Kotlin-s reply**: to send it, Kotlin had to decapsulate with its prekey seed, adopt the web engine-s `pqdh`, and encapsulate a fresh key — and doing those three in the wrong order (§13.2) derives a different root key **with no error anywhere**. Sixteen Kotlin unit tests cannot catch that, because both sides of a self-test make the same mistake together. Also pinned: a fresh ML-KEM key per step, not a reused one — reuse still decrypts and silently gives up the healing §13.1 exists for. ### v1 coverage kept Every earlier section now asks the driver for a v1 bundle explicitly. Those paths are not legacy trivia — the released fleet speaks them — and they would have stopped being covered the moment the driver learned v3. ``` 56 passed, 0 failed (was 43) ``` ### What it found paulhitt/flamenet-messenger-android!4 — the Android engine was sending v3 ratchet steps to v2 peers, which would have broken every conversation with the released 0.6 build. §15 should not go to until that merges.
paulhitt (Migrated from gitlab.com) merged commit 2adadddea1 into main 2026-08-31 17:10:05 +00:00
paulhitt commented 2026-08-31 17:10:06 +00:00 (Migrated from gitlab.com)

mentioned in commit 2adadddea1

mentioned in commit 2adadddea1cf565e20219c057d2d2f936155da02
paulhitt commented 2026-09-09 18:59:49 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
paulhitt commented 2026-09-09 19:01:59 +00:00 (Migrated from gitlab.com)

mentioned in merge request flamenet-messenger-android!4

mentioned in merge request flamenet-messenger-android!4
Sign in to join this conversation.