noise-blake2s-kat.test.ts

4.0 KB · raw

1import { beforeEach, describe, expect, it, vi } from "vitest";2import { generateKeyPairFromSeed } from "@stablelib/x25519";34// PCH-01-002: the Noise handshakes derive `h` (protocol-name init hash) and the5// HKDF in MixKey/Split over BLAKE2s, matching the declared pattern name. These6// are KNOWN-ANSWER tests over a fully deterministic handshake (fixed statics +7// seeded ephemerals): a revert to SHA-256 changes every derived byte, so the8// pinned handshakeHash below stops matching and the test fails. That failure is9// the intended signal of an accidental hash-algorithm change.1011// Mock ONLY ephemeral generation so the handshake is deterministic; sharedKey12// and the seeded keygen stay real. File-scoped, so other noise tests are13// unaffected. The counter lives on globalThis and is reset per test.14vi.mock("@stablelib/x25519", async (importOriginal) => {15  const actual = await importOriginal<typeof import("@stablelib/x25519")>();16  return {17    ...actual,18    generateKeyPair: () => {19      const g = globalThis as { __kpCounter?: number };20      g.__kpCounter = (g.__kpCounter ?? 0) + 1;21      return actual.generateKeyPairFromSeed(new Uint8Array(32).fill(g.__kpCounter));22    },23  };24});2526function memoryPair() {27  const a: Uint8Array[] = [];28  const b: Uint8Array[] = [];29  const aR: Array<(v: Uint8Array) => void> = [];30  const bR: Array<(v: Uint8Array) => void> = [];31  const tA = {32    async send(m: Uint8Array) { const r = bR.shift(); if (r) r(m); else b.push(m); },33    receive(): Promise<Uint8Array> {34      const v = a.shift();35      if (v) return Promise.resolve(v);36      return new Promise<Uint8Array>((resolve) => aR.push(resolve));37    },38  };39  const tB = {40    async send(m: Uint8Array) { const r = aR.shift(); if (r) r(m); else a.push(m); },41    receive(): Promise<Uint8Array> {42      const v = b.shift();43      if (v) return Promise.resolve(v);44      return new Promise<Uint8Array>((resolve) => bR.push(resolve));45    },46  };47  return [tA, tB] as const;48}4950function hex(b: Uint8Array): string {51  return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");52}5354describe("PCH-01-002 — Noise handshakes derive over BLAKE2s", () => {55  beforeEach(() => {56    (globalThis as { __kpCounter?: number }).__kpCounter = 0;57  });5859  it("noise_xx_handshake_hash_is_blake2s_derived", async () => {60    const { noiseXXInitiator, noiseXXResponder } = await import("./noise-xx");61    const alice = generateKeyPairFromSeed(new Uint8Array(32).fill(0xa1));62    const bob = generateKeyPairFromSeed(new Uint8Array(32).fill(0xb2));63    const [tA, tB] = memoryPair();6465    const [a, b] = await Promise.all([66      noiseXXInitiator(alice.secretKey, alice.publicKey, tA.send, tA.receive),67      noiseXXResponder(bob.secretKey, bob.publicKey, tB.send, tB.receive),68    ]);6970    // Both directions agree (consistency) ...71    expect(hex(a.handshakeHash)).toBe(hex(b.handshakeHash));72    // ... and equal the pinned BLAKE2s vector (a SHA-256 revert changes this).73    expect(hex(a.handshakeHash)).toBe(74      "11c8bf7fcefad1f00ff8573c7bca66891626d053addae6d2b78b2f195a9ffd8a",75    );7677    // Transport keys are also BLAKE2s-derived; a frame must still round-trip.78    const ct = a.encrypt(new TextEncoder().encode("hello"));79    expect(new TextDecoder().decode(b.decrypt(ct))).toBe("hello");80  });8182  it("noise_nk_handshake_hash_is_blake2s_derived", async () => {83    const { noiseNKInitiator, noiseNKResponder } = await import("./noise-js");84    const responder = generateKeyPairFromSeed(new Uint8Array(32).fill(0xc3));85    const [tA, tB] = memoryPair();8687    const [a, b] = await Promise.all([88      noiseNKInitiator(responder.publicKey, tA.send, tA.receive),89      noiseNKResponder(responder.secretKey, responder.publicKey, tB.send, tB.receive),90    ]);9192    expect(hex(a.handshakeHash)).toBe(hex(b.handshakeHash));93    expect(hex(a.handshakeHash)).toBe(94      "0fbab8cd1782bf4b17d69beb69f5b80b0bf24d93da4924959c76ee701c32e655",95    );9697    const ct = a.encrypt(new TextEncoder().encode("ping"));98    expect(new TextDecoder().decode(b.decrypt(ct))).toBe("ping");99  });100});