PCH-02-002-noise-xx-msg1-hash.patch.md

5.9 KB · raw

rendered · source

PCH-02-002 — Noise XX message 1 empty-payload mixHash

Severity: Informational Target file: src/shared/noise-xx.ts:93-97 (initiator), 184-191 (responder) Also touched: src/shared/noise-js.ts:11-14 (doc comment correction — landable NOW) Report: peach-code-review.md, Finding PCH-02-002

Decision: DEFER the wire change to the Android coordination window

Per the fix brief's own escape hatch ("if both sides already skip it consistently and the practical risk is only interop … defer the wire change to the Android coordination window. Do not implement a half-fix.") — that is exactly the situation, verified by a complete read of noise-xx.ts:

Implementing the browser side alone would be the forbidden half-fix.

Ready-to-apply patch (apply ONLY in the coordinated window)

Initiator — noise-xx.ts, message 1 block:

// BEFORE (lines 93-96)
  // --- Message 1: -> e ---
  const e = generateKeyPair();
  h = mixHash(h, e.publicKey);
  await send(e.publicKey);

// AFTER
  // --- Message 1: -> e ---
  const e = generateKeyPair();
  h = mixHash(h, e.publicKey);
  // PCH-02-002: canonical Noise ends every message with EncryptAndHash(payload);
  // with no symmetric key yet, an empty msg-1 payload is MixHash(empty). The
  // wire bytes are unchanged (empty payload), only the transcript hash moves.
  // BREAKING transcript change — ships in lockstep with peach-android.
  h = mixHash(h, new Uint8Array(0));
  await send(e.publicKey);

Responder — noise-xx.ts, message 1 block:

// BEFORE (lines 187-191)
  // --- Message 1: <- e ---
  const msg1 = await receive();
  if (msg1.length < 32) throw new Error("Noise XX: msg1 too short");
  const remoteEphemeral = msg1.slice(0, 32);
  h = mixHash(h, remoteEphemeral);

// AFTER
  // --- Message 1: <- e ---
  const msg1 = await receive();
  if (msg1.length < 32) throw new Error("Noise XX: msg1 too short");
  const remoteEphemeral = msg1.slice(0, 32);
  h = mixHash(h, remoteEphemeral);
  // PCH-02-002: mirror the initiator's empty-payload MixHash (see above).
  h = mixHash(h, new Uint8Array(0));

The NK pattern (noise-js.ts) is already canonical — its msg-1 payload is a real AEAD seal because es establishes a key before the payload step (noise-js.ts:84-91); no change there.

Test impact when the wire change lands

Landable NOW (no wire impact): doc comment correction in noise-js.ts

The header claim at noise-js.ts:12 — "A stock snow stack would interoperate" — is true for NK but over-reads for XX. Correct it so the claim cannot mislead a future maintainer:

// BEFORE (noise-js.ts:11-14)
 * PCH-01-002: this implementation derives `h` and HKDF over BLAKE2s to match the
 * named pattern. A stock `snow` stack would interoperate; the Android port must
 * mirror the same hash choices. Any change to the hash, HKDF, or transcript here
 * is a BREAKING wire change and requires a coordinated Android release.

// AFTER
 * PCH-01-002: this implementation derives `h` and HKDF over BLAKE2s to match the
 * named pattern. A stock `snow` stack would interoperate with THIS NK pattern;
 * the XX implementation (noise-xx.ts) omits the canonical msg-1 empty-payload
 * MixHash (PCH-02-002, deferred to an Android-coordinated release), so stock
 * stacks would NOT interoperate with Peach XX until that lands. The Android port
 * must mirror the same hash choices. Any change to the hash, HKDF, or transcript
 * here is a BREAKING wire change and requires a coordinated Android release.

And pin the deferral in noise-xx.ts so the omission is visible at the site:

// Insert after noise-xx.ts:17 (end of the header comment block):
//
// PCH-02-002 (DEFERRED — Android-coordinated wire change): message 1 omits the
// canonical empty-payload MixHash. Both Peach sides skip it identically, so
// Peach↔Peach/Android transcripts agree; only interop with stock Noise stacks
// is affected. The ready-to-apply fix lives in
// src/drafts/PCH-02-FIXES/PCH-02-002-noise-xx-msg1-hash.patch.md and MUST land
// in the same release as the matching peach-android change (PCH-01-002 model).

Save message (integration, comment-only part)

docs: PCH-02-002 pin Noise XX msg1 empty-payload mixHash deferral — wire change requires Android coordination; correct noise-js snow-interop claim

Save message (coordinated window, wire part)

fix: PCH-02-002 Noise XX message 1 empty-payload mixHash — NOTE: wire change, ships in lockstep with peach-android