noise-blake2s-kat.test.ts

3.6 KB · raw

1import { describe, expect, it } 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.1112function memoryPair() {13  const a: Uint8Array[] = [];14  const b: Uint8Array[] = [];15  const aR: Array<(v: Uint8Array) => void> = [];16  const bR: Array<(v: Uint8Array) => void> = [];17  const tA = {18    async send(m: Uint8Array) { const r = bR.shift(); if (r) r(m); else b.push(m); },19    receive(): Promise<Uint8Array> {20      const v = a.shift();21      if (v) return Promise.resolve(v);22      return new Promise<Uint8Array>((resolve) => aR.push(resolve));23    },24  };25  const tB = {26    async send(m: Uint8Array) { const r = aR.shift(); if (r) r(m); else a.push(m); },27    receive(): Promise<Uint8Array> {28      const v = b.shift();29      if (v) return Promise.resolve(v);30      return new Promise<Uint8Array>((resolve) => bR.push(resolve));31    },32  };33  return [tA, tB] as const;34}3536function hex(b: Uint8Array): string {37  return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");38}3940// Cross-repo frozen vectors for the two changed primitives. These exact hex41// values are ALSO pinned in peach-android's CrossPlatformKATTest.kt — a mismatch42// on either side means the BLAKE2s wire derivations have diverged.43describe("PCH-01-002 — BLAKE2s primitive vectors (shared with peach-android)", () => {44  const CK = new Uint8Array(32).map((_, i) => i); // 0x00..0x1f45  const IKM = new Uint8Array(32).map((_, i) => i + 32); // 0x20..0x3f4647  it("initHash is BLAKE2s-256 of the NK protocol name", () => {48    expect(hex(nkInitHash())).toBe(49      "e01688d376ef280a27a8af5c559024a02017585b3763763e4f3c7118a979b09e",50    );51  });5253  it("hkdf2 (HKDF-BLAKE2s) matches the frozen vector", () => {54    const [k1, k2] = nkHkdf2(CK, IKM);55    expect(hex(k1)).toBe("6a96444e20e8d4c1cee974416acae1c10b3c92886010e54ed94dafb2c3b80ea0");56    expect(hex(k2)).toBe("57af120b0de7acbe7907ec149c5ae870a2dbb74232b65777ba4123f1f7f888f5");57  });58});5960describe("PCH-01-002 — Noise handshakes derive over BLAKE2s", () => {61  it("noise_nk_handshake_hash_is_blake2s_derived", async () => {62    const { noiseNKInitiator, noiseNKResponder } = await import("./noise-js");63    const responder = generateKeyPairFromSeed(new Uint8Array(32).fill(0xc3));64    const initiatorEphemeral = generateKeyPairFromSeed(new Uint8Array(32).fill(0x01));65    const responderEphemeral = generateKeyPairFromSeed(new Uint8Array(32).fill(0x02));66    const [tA, tB] = memoryPair();6768    const [a, b] = await Promise.all([69      noiseNKInitiator(70        responder.publicKey,71        tA.send,72        tA.receive,73        initiatorEphemeral,74      ),75      noiseNKResponder(76        responder.secretKey,77        responder.publicKey,78        tB.send,79        tB.receive,80        responderEphemeral,81      ),82    ]);8384    expect(hex(a.handshakeHash)).toBe(hex(b.handshakeHash));85    expect(hex(a.handshakeHash)).toBe(86      "76f1402ea3ae01615a226b1c95a5d4f4c62eac356d1d5fe282b3417fcb01665f",87    );8889    const ct = a.encrypt(new TextEncoder().encode("ping"));90    expect(new TextDecoder().decode(b.decrypt(ct))).toBe("ping");91  });92});