1import { beforeEach, describe, expect, it, vi } from "vitest";2import { generateKeyPairFromSeed } from "@stablelib/x25519";3import { initHash as xxInitHash, hkdf2 as xxHkdf2 } from "./noise-xx";4import { initHash as nkInitHash, hkdf2 as nkHkdf2 } from "./noise-js";5678910111213141516vi.mock("@stablelib/x25519", async (importOriginal) => {17 const actual = await importOriginal<typeof import("@stablelib/x25519")>();18 return {19 ...actual,20 generateKeyPair: () => {21 const g = globalThis as { __kpCounter?: number };22 g.__kpCounter = (g.__kpCounter ?? 0) + 1;23 return actual.generateKeyPairFromSeed(new Uint8Array(32).fill(g.__kpCounter));24 },25 };26});2728function memoryPair() {29 const a: Uint8Array[] = [];30 const b: Uint8Array[] = [];31 const aR: Array<(v: Uint8Array) => void> = [];32 const bR: Array<(v: Uint8Array) => void> = [];33 const tA = {34 async send(m: Uint8Array) { const r = bR.shift(); if (r) r(m); else b.push(m); },35 receive(): Promise<Uint8Array> {36 const v = a.shift();37 if (v) return Promise.resolve(v);38 return new Promise<Uint8Array>((resolve) => aR.push(resolve));39 },40 };41 const tB = {42 async send(m: Uint8Array) { const r = aR.shift(); if (r) r(m); else a.push(m); },43 receive(): Promise<Uint8Array> {44 const v = b.shift();45 if (v) return Promise.resolve(v);46 return new Promise<Uint8Array>((resolve) => bR.push(resolve));47 },48 };49 return [tA, tB] as const;50}5152function hex(b: Uint8Array): string {53 return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");54}5556575859describe("PCH-01-002 — BLAKE2s primitive vectors (shared with peach-android)", () => {60 const CK = new Uint8Array(32).map((_, i) => i); 61 const IKM = new Uint8Array(32).map((_, i) => i + 32); 6263 it("initHash is BLAKE2s-256 of the XX protocol name", () => {64 expect(hex(xxInitHash())).toBe(65 "1ceedd81c5f458b225923dc2507787bf156f9251fc17c45af63263a929fc1ed2",66 );67 });6869 it("initHash is BLAKE2s-256 of the NK protocol name", () => {70 expect(hex(nkInitHash())).toBe(71 "e01688d376ef280a27a8af5c559024a02017585b3763763e4f3c7118a979b09e",72 );73 });7475 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 80 const [n1, n2] = nkHkdf2(CK, IKM);81 expect(hex(n1)).toBe(hex(k1));82 expect(hex(n2)).toBe(hex(k2));83 });84});8586describe("PCH-01-002 — Noise handshakes derive over BLAKE2s", () => {87 beforeEach(() => {88 (globalThis as { __kpCounter?: number }).__kpCounter = 0;89 });9091 it("noise_xx_handshake_hash_is_blake2s_derived", async () => {92 const { noiseXXInitiator, noiseXXResponder } = await import("./noise-xx");93 const alice = generateKeyPairFromSeed(new Uint8Array(32).fill(0xa1));94 const bob = generateKeyPairFromSeed(new Uint8Array(32).fill(0xb2));95 const [tA, tB] = memoryPair();9697 const [a, b] = await Promise.all([98 noiseXXInitiator(alice.secretKey, alice.publicKey, tA.send, tA.receive),99 noiseXXResponder(bob.secretKey, bob.publicKey, tB.send, tB.receive),100 ]);101102 103 expect(hex(a.handshakeHash)).toBe(hex(b.handshakeHash));104 105 expect(hex(a.handshakeHash)).toBe(106 "11c8bf7fcefad1f00ff8573c7bca66891626d053addae6d2b78b2f195a9ffd8a",107 );108109 110 const ct = a.encrypt(new TextEncoder().encode("hello"));111 expect(new TextDecoder().decode(b.decrypt(ct))).toBe("hello");112 });113114 it("noise_nk_handshake_hash_is_blake2s_derived", async () => {115 const { noiseNKInitiator, noiseNKResponder } = await import("./noise-js");116 const responder = generateKeyPairFromSeed(new Uint8Array(32).fill(0xc3));117 const [tA, tB] = memoryPair();118119 const [a, b] = await Promise.all([120 noiseNKInitiator(responder.publicKey, tA.send, tA.receive),121 noiseNKResponder(responder.secretKey, responder.publicKey, tB.send, tB.receive),122 ]);123124 expect(hex(a.handshakeHash)).toBe(hex(b.handshakeHash));125 expect(hex(a.handshakeHash)).toBe(126 "0fbab8cd1782bf4b17d69beb69f5b80b0bf24d93da4924959c76ee701c32e655",127 );128129 const ct = a.encrypt(new TextEncoder().encode("ping"));130 expect(new TextDecoder().decode(b.decrypt(ct))).toBe("ping");131 });132});