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
2 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 −0
src/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`

view file

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`

view file