53ce63c0fe08
add PCH-02-002 + PCH-02-003 fix drafts: Noise XX msg1 empty-payload mixHash deferred with ready patch (Android-coordinated wire change) + snow-interop doc correction; SAS peer-gate trust-model comment edits
- time
- 2026-06-12 20:10 UTC · 4 months ago
- machine
- koh2
- agent
- claude-fable-5
- session
- pch-02 fix drafts
- parent
- 2f6fb75710b9
↓ peach-browser-53ce63c0.tar.gzatlas at this save
files changed2 files
src/drafts/PCH-02-FIXES/PCH-02-002-noise-xx-msg1-hash.patch.md+132 −0src/drafts/PCH-02-FIXES/PCH-02-003-sas-trust-model-comment.patch.md+73 −0src/drafts/PCH-02-FIXES/PCH-02-002-noise-xx-msg1-hash.patch.md+132 −0
@@ -0,0 +1,132 @@1+# PCH-02-002 — Noise XX message 1 empty-payload mixHash2+3+**Severity:** Informational4+**Target file:** `src/shared/noise-xx.ts:93-97` (initiator), `184-191` (responder)5+**Also touched:** `src/shared/noise-js.ts:11-14` (doc comment correction — landable NOW)6+**Report:** peach-code-review.md, Finding PCH-02-0027+8+## Decision: DEFER the wire change to the Android coordination window9+10+Per the fix brief's own escape hatch ("if both sides already skip it11+consistently and the practical risk is only interop … defer the wire change to12+the Android coordination window. Do not implement a half-fix.") — that is13+exactly the situation, verified by a complete read of `noise-xx.ts`:14+15+- Both the initiator (msg 1 send, lines 93–96) and the responder (msg 116+ receive, lines 188–191) skip the canonical `MixHash(empty payload)` —17+ **consistently**. Every Peach↔Peach and Peach↔Android XX handshake computes18+ an identical transcript hash `h`.19+- Transport keys derive from the chaining key `ck`, not from `h`; the20+ handshake hash still binds every byte both sides actually exchanged. There21+ is no confidentiality/authenticity weakening — only non-interop with stock22+ Noise stacks (e.g. `snow`), which Peach explicitly does not rely on23+ (`noise-js.ts:5-9`: "interop holds by porting discipline, not by a shared24+ library").25+- Adding the `mixHash(empty)` changes `h` after message 1, which changes the26+ AAD of every subsequent handshake AEAD → **every ciphertext from msg 227+ onward changes**. peach-android must apply the identical change in the same28+ release or all XX sync sessions fail to handshake. This is the same class29+ of coordinated breaking change as PCH-01-002 (BLAKE2s), which shipped in30+ lockstep as browser v3.9.1 / android v2.2.9.31+32+Implementing the browser side alone would be the forbidden half-fix.33+34+## Ready-to-apply patch (apply ONLY in the coordinated window)35+36+Initiator — `noise-xx.ts`, message 1 block:37+38+```typescript39+// BEFORE (lines 93-96)40+ // --- Message 1: -> e ---41+ const e = generateKeyPair();42+ h = mixHash(h, e.publicKey);43+ await send(e.publicKey);44+45+// AFTER46+ // --- Message 1: -> e ---47+ const e = generateKeyPair();48+ h = mixHash(h, e.publicKey);49+ // PCH-02-002: canonical Noise ends every message with EncryptAndHash(payload);50+ // with no symmetric key yet, an empty msg-1 payload is MixHash(empty). The51+ // wire bytes are unchanged (empty payload), only the transcript hash moves.52+ // BREAKING transcript change — ships in lockstep with peach-android.53+ h = mixHash(h, new Uint8Array(0));54+ await send(e.publicKey);55+```56+57+Responder — `noise-xx.ts`, message 1 block:58+59+```typescript60+// BEFORE (lines 187-191)61+ // --- Message 1: <- e ---62+ const msg1 = await receive();63+ if (msg1.length < 32) throw new Error("Noise XX: msg1 too short");64+ const remoteEphemeral = msg1.slice(0, 32);65+ h = mixHash(h, remoteEphemeral);66+67+// AFTER68+ // --- Message 1: <- e ---69+ const msg1 = await receive();70+ if (msg1.length < 32) throw new Error("Noise XX: msg1 too short");71+ const remoteEphemeral = msg1.slice(0, 32);72+ h = mixHash(h, remoteEphemeral);73+ // PCH-02-002: mirror the initiator's empty-payload MixHash (see above).74+ h = mixHash(h, new Uint8Array(0));75+```76+77+The NK pattern (`noise-js.ts`) is already canonical — its msg-1 payload is a78+real AEAD seal because `es` establishes a key before the payload step79+(`noise-js.ts:84-91`); no change there.80+81+### Test impact when the wire change lands82+83+- `src/shared/noise-xx.test.ts` and `src/shared/noise-blake2s-kat.test.ts`:84+ any pinned XX handshake-hash / transcript vectors must be regenerated (the85+ NK vectors are unaffected). Initiator↔responder round-trip tests pass86+ unchanged (both sides move together).87+- Cross-repo: regenerate the XX vectors in peach-android88+ `CrossPlatformKATTest` to the same values, exactly as PCH-01-002 did.89+90+## Landable NOW (no wire impact): doc comment correction in noise-js.ts91+92+The header claim at `noise-js.ts:12` — "A stock `snow` stack would93+interoperate" — is true for NK but over-reads for XX. Correct it so the claim94+cannot mislead a future maintainer:95+96+```typescript97+// BEFORE (noise-js.ts:11-14)98+ * PCH-01-002: this implementation derives `h` and HKDF over BLAKE2s to match the99+ * named pattern. A stock `snow` stack would interoperate; the Android port must100+ * mirror the same hash choices. Any change to the hash, HKDF, or transcript here101+ * is a BREAKING wire change and requires a coordinated Android release.102+103+// AFTER104+ * PCH-01-002: this implementation derives `h` and HKDF over BLAKE2s to match the105+ * named pattern. A stock `snow` stack would interoperate with THIS NK pattern;106+ * the XX implementation (noise-xx.ts) omits the canonical msg-1 empty-payload107+ * MixHash (PCH-02-002, deferred to an Android-coordinated release), so stock108+ * stacks would NOT interoperate with Peach XX until that lands. The Android port109+ * must mirror the same hash choices. Any change to the hash, HKDF, or transcript110+ * here is a BREAKING wire change and requires a coordinated Android release.111+```112+113+And pin the deferral in `noise-xx.ts` so the omission is visible at the site:114+115+```typescript116+// Insert after noise-xx.ts:17 (end of the header comment block):117+//118+// PCH-02-002 (DEFERRED — Android-coordinated wire change): message 1 omits the119+// canonical empty-payload MixHash. Both Peach sides skip it identically, so120+// Peach↔Peach/Android transcripts agree; only interop with stock Noise stacks121+// is affected. The ready-to-apply fix lives in122+// src/drafts/PCH-02-FIXES/PCH-02-002-noise-xx-msg1-hash.patch.md and MUST land123+// in the same release as the matching peach-android change (PCH-01-002 model).124+```125+126+## Save message (integration, comment-only part)127+128+`docs: PCH-02-002 pin Noise XX msg1 empty-payload mixHash deferral — wire change requires Android coordination; correct noise-js snow-interop claim`129+130+## Save message (coordinated window, wire part)131+132+`fix: PCH-02-002 Noise XX message 1 empty-payload mixHash — NOTE: wire change, ships in lockstep with peach-android`src/drafts/PCH-02-FIXES/PCH-02-003-sas-trust-model-comment.patch.md+73 −0
@@ -0,0 +1,73 @@1+# PCH-02-003 — Document the SAS peer-gate trust-model dependency (doc-only)2+3+**Severity:** Informational4+**Target file:** `src/shared/chirp-handshake.ts:125-154` (the SECURITY block)5+**Report:** peach-code-review.md, Finding PCH-02-0036+7+## Decision8+9+Documentation only — no code change. The review itself calls the existing10+comment "one of the better in-code threat statements in the codebase"; the11+brief asks for the finding ID, the Android source pointer, and the explicit12+"documented residual risk" framing to be added. The existing text is kept —13+three surgical insertions rather than a rewrite, so the PCH-01-024 history in14+the block stays intact.15+16+## Exact edits17+18+Edit 1 — tag the block with the finding ID (line 126):19+20+```typescript21+// BEFORE22+// SECURITY — TRUST MODEL: the pairing handshake does NOT cryptographically23+24+// AFTER25+// SECURITY — TRUST MODEL (PCH-02-003): the pairing handshake does NOT cryptographically26+```27+28+Edit 2 — add the repository pointer to the OPEN ITEM paragraph (lines 146-149):29+30+```typescript31+// BEFORE32+// OPEN ITEM (NOT verifiable from this repository): the Android/native client33+// MUST surface and enforce the identical SAS gate before it persists the peer.34+// If the app skips or auto-confirms the SAS, the channel is only as strong as35+// the app's weakest end — confirm the app-side gate against the Android source.36+37+// AFTER38+// OPEN ITEM (NOT verifiable from this repository): the Android/native client39+// MUST surface and enforce the identical SAS gate before it persists the peer.40+// If the app skips or auto-confirms the SAS, the channel is only as strong as41+// the app's weakest end — confirm the app-side gate against the Android source42+// at kepr.uk/peach-android (verify the NoiseXX/NK SAS enforcement before any43+// device is persisted). A MITM who intercepts the acoustic chirp and completes44+// the handshake against a non-enforcing peer can pair without user awareness.45+```46+47+Edit 3 — close the block with the residual-risk framing (after line 153,48+before the closing rule):49+50+```typescript51+// BEFORE52+// (noise-js) so each side proves possession of the key it advertises, making53+// authentication structural rather than dependent on the user's SAS comparison.54+// ───────────────────────────────────────────────────────────────────────────55+56+// AFTER57+// (noise-js) so each side proves possession of the key it advertises, making58+// authentication structural rather than dependent on the user's SAS comparison.59+//60+// This is documented residual risk in the cross-repo trust model, not a bug in61+// this codebase: control (1) — the extension-side gateOnSAS — is enforced here62+// (chirp-pair.ts) and regression-tested (PCH-01-024); control (2) — the peer-63+// side gate — lives in the Android repository and must be re-verified there on64+// every Android release that touches pairing.65+// ───────────────────────────────────────────────────────────────────────────66+```67+68+No tests — comment-only change; the existing `gateOnSAS` regression test69+(added in PCH-01-024) already pins the extension-side control.70+71+## Save message (integration)72+73+`docs: PCH-02-003 document SAS peer-gate trust model dependency with Android source reference`