noise-blake2s-kat.test.ts

4.0 KB · raw

1import { beforeEach, describe, expect, it, vi } from "vitest";2import { generateKeyPairFromSeed } from "@stablelib/x25519";3import { initHash as nkInitHash, hkdf2 as nkHkdf2 } from "./noise-js";45// PCH-01-002: the Noise handshakes derive `h` (protocol-name init hash) and the6// HKDF in MixKey/Split over BLAKE2s, matching the declared pattern name. These7// are KNOWN-ANSWER tests over a fully deterministic handshake (fixed statics +8// seeded ephemerals): a revert to SHA-256 changes every derived byte, so the9// pinned handshakeHash below stops matching and the test fails. That failure is10// the intended signal of an accidental hash-algorithm change.1112// Mock ONLY ephemeral generation so the handshake is deterministic; sharedKey13// and the seeded keygen stay real. File-scoped, so other noise tests are14// unaffected. The counter lives on globalThis and is reset per test.15vi.mock("@stablelib/x25519", async (importOriginal) => {16  const actual = await importOriginal<typeof import("@stablelib/x25519")>();17  return {18    ...actual,19    generateKeyPair: () => {20      const g = globalThis as { __kpCounter?: number };21      g.__kpCounter = (g.__kpCounter ?? 0) + 1;22      return actual.generateKeyPairFromSeed(new Uint8Array(32).fill(g.__kpCounter));23    },24  };25});2627function memoryPair() {28  const a: Uint8Array[] = [];29  const b: Uint8Array[] = [];30  const aR: Array<(v: Uint8Array) => void> = [];31  const bR: Array<(v: Uint8Array) => void> = [];32  const tA = {33    async send(m: Uint8Array) { const r = bR.shift(); if (r) r(m); else b.push(m); },34    receive(): Promise<Uint8Array> {35      const v = a.shift();36      if (v) return Promise.resolve(v);37      return new Promise<Uint8Array>((resolve) => aR.push(resolve));38    },39  };40  const tB = {41    async send(m: Uint8Array) { const r = aR.shift(); if (r) r(m); else a.push(m); },42    receive(): Promise<Uint8Array> {43      const v = b.shift();44      if (v) return Promise.resolve(v);45      return new Promise<Uint8Array>((resolve) => bR.push(resolve));46    },47  };48  return [tA, tB] as const;49}5051function hex(b: Uint8Array): string {52  return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");53}5455// Cross-repo frozen vectors for the two changed primitives. These exact hex56// values are ALSO pinned in peach-android's CrossPlatformKATTest.kt — a mismatch57// on either side means the BLAKE2s wire derivations have diverged.58describe("PCH-01-002 — BLAKE2s primitive vectors (shared with peach-android)", () => {59  const CK = new Uint8Array(32).map((_, i) => i); // 0x00..0x1f60  const IKM = new Uint8Array(32).map((_, i) => i + 32); // 0x20..0x3f6162  it("initHash is BLAKE2s-256 of the NK protocol name", () => {63    expect(hex(nkInitHash())).toBe(64      "e01688d376ef280a27a8af5c559024a02017585b3763763e4f3c7118a979b09e",65    );66  });6768  it("hkdf2 (HKDF-BLAKE2s) matches the frozen vector", () => {69    const [k1, k2] = nkHkdf2(CK, IKM);70    expect(hex(k1)).toBe("6a96444e20e8d4c1cee974416acae1c10b3c92886010e54ed94dafb2c3b80ea0");71    expect(hex(k2)).toBe("57af120b0de7acbe7907ec149c5ae870a2dbb74232b65777ba4123f1f7f888f5");72  });73});7475describe("PCH-01-002 — Noise handshakes derive over BLAKE2s", () => {76  beforeEach(() => {77    (globalThis as { __kpCounter?: number }).__kpCounter = 0;78  });7980  it("noise_nk_handshake_hash_is_blake2s_derived", async () => {81    const { noiseNKInitiator, noiseNKResponder } = await import("./noise-js");82    const responder = generateKeyPairFromSeed(new Uint8Array(32).fill(0xc3));83    const [tA, tB] = memoryPair();8485    const [a, b] = await Promise.all([86      noiseNKInitiator(responder.publicKey, tA.send, tA.receive),87      noiseNKResponder(responder.secretKey, responder.publicKey, tB.send, tB.receive),88    ]);8990    expect(hex(a.handshakeHash)).toBe(hex(b.handshakeHash));91    expect(hex(a.handshakeHash)).toBe(92      "0fbab8cd1782bf4b17d69beb69f5b80b0bf24d93da4924959c76ee701c32e655",93    );9495    const ct = a.encrypt(new TextEncoder().encode("ping"));96    expect(new TextDecoder().decode(b.decrypt(ct))).toBe("ping");97  });98});