83c1766c1a8c
test: PCH-01-002 cross-repo BLAKE2s KAT — export initHash/hkdf2 from noise-xx.ts + noise-js.ts, pin initHash + HKDF-BLAKE2s vectors byte-identical to peach-android CrossPlatformKATTest (proven matching) (v3.9.0)
- time
- 2026-06-10 20:58 UTC · 4 months ago
- machine
- koh2
- agent
- claude-opus-4-8
- session
- pch-01-015 test + pch-01-002 blake2s
- parent
- 4a07efb9b3a4
↓ peach-browser-83c1766c.tar.gzatlas at this save
files changed3 files
src/shared/noise-blake2s-kat.test.ts+32 −0src/shared/noise-js.ts+2 −2src/shared/noise-xx.ts+2 −2src/shared/noise-blake2s-kat.test.ts+32 −0
@@ -1,5 +1,7 @@11 import { beforeEach, describe, expect, it, vi } from "vitest";22 import { generateKeyPairFromSeed } from "@stablelib/x25519";3+import { initHash as xxInitHash, hkdf2 as xxHkdf2 } from "./noise-xx";4+import { initHash as nkInitHash, hkdf2 as nkHkdf2 } from "./noise-js";35 46 // PCH-01-002: the Noise handshakes derive `h` (protocol-name init hash) and the57 // HKDF in MixKey/Split over BLAKE2s, matching the declared pattern name. These@@ -50,6 +52,36 @@5052 function hex(b: Uint8Array): string {5153 return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");5254 }55+56+// Cross-repo frozen vectors for the two changed primitives. These exact hex57+// values are ALSO pinned in peach-android's CrossPlatformKATTest.kt — a mismatch58+// on either side means the BLAKE2s wire derivations have diverged.59+describe("PCH-01-002 — BLAKE2s primitive vectors (shared with peach-android)", () => {60+ const CK = new Uint8Array(32).map((_, i) => i); // 0x00..0x1f61+ const IKM = new Uint8Array(32).map((_, i) => i + 32); // 0x20..0x3f62+63+ it("initHash is BLAKE2s-256 of the XX protocol name", () => {64+ expect(hex(xxInitHash())).toBe(65+ "1ceedd81c5f458b225923dc2507787bf156f9251fc17c45af63263a929fc1ed2",66+ );67+ });68+69+ it("initHash is BLAKE2s-256 of the NK protocol name", () => {70+ expect(hex(nkInitHash())).toBe(71+ "e01688d376ef280a27a8af5c559024a02017585b3763763e4f3c7118a979b09e",72+ );73+ });74+75+ it("hkdf2 (HKDF-BLAKE2s) matches the frozen vector and is identical XX vs NK", () => {76+ const [k1, k2] = xxHkdf2(CK, IKM);77+ expect(hex(k1)).toBe("6a96444e20e8d4c1cee974416acae1c10b3c92886010e54ed94dafb2c3b80ea0");78+ expect(hex(k2)).toBe("57af120b0de7acbe7907ec149c5ae870a2dbb74232b65777ba4123f1f7f888f5");79+ // The hkdf2 logic is duplicated in noise-xx.ts and noise-js.ts — they must agree.80+ const [n1, n2] = nkHkdf2(CK, IKM);81+ expect(hex(n1)).toBe(hex(k1));82+ expect(hex(n2)).toBe(hex(k2));83+ });84+});5385 5486 describe("PCH-01-002 — Noise handshakes derive over BLAKE2s", () => {5587 beforeEach(() => {src/shared/noise-js.ts+2 −2
@@ -22,7 +22,7 @@2222 2323 const PROTOCOL_NAME = "Noise_NK_25519_ChaChaPoly_BLAKE2s";2424 25-function initHash(): Uint8Array {25+export function initHash(): Uint8Array {2626 const nameBytes = new TextEncoder().encode(PROTOCOL_NAME);2727 if (nameBytes.length <= 32) {2828 const h = new Uint8Array(32);@@ -41,7 +41,7 @@4141 return blake.digest();4242 }4343 44-function hkdf2(ck: Uint8Array, ikm: Uint8Array): [Uint8Array, Uint8Array] {44+export function hkdf2(ck: Uint8Array, ikm: Uint8Array): [Uint8Array, Uint8Array] {4545 // PCH-01-002: HKDF must run over the named hash (BLAKE2s), not SHA-256.4646 // @stablelib's HKDF reuses one HMAC across blocks and BLAKE2s's restoreState()4747 // never clears its `finished` flag, so `new HKDF(BLAKE2s, …)` throws. Runsrc/shared/noise-xx.ts+2 −2
@@ -23,7 +23,7 @@2323 2424 const PROTOCOL_NAME = "Noise_XX_25519_ChaChaPoly_BLAKE2s";2525 26-function initHash(): Uint8Array {26+export function initHash(): Uint8Array {2727 const nameBytes = new TextEncoder().encode(PROTOCOL_NAME);2828 if (nameBytes.length <= 32) {2929 const h = new Uint8Array(32);@@ -43,7 +43,7 @@4343 return blake.digest();4444 }4545 46-function hkdf2(ck: Uint8Array, ikm: Uint8Array): [Uint8Array, Uint8Array] {46+export function hkdf2(ck: Uint8Array, ikm: Uint8Array): [Uint8Array, Uint8Array] {4747 // PCH-01-002: HKDF must run over the named hash (BLAKE2s), not SHA-256.4848 // @stablelib's HKDF reuses one HMAC across output blocks and BLAKE2s's4949 // restoreState() never clears its `finished` flag, so `new HKDF(BLAKE2s, …)`