1import { describe, expect, it } from "vitest";2import { generateKeyPairFromSeed } from "@stablelib/x25519";3import { initHash as nkInitHash, hkdf2 as nkHkdf2 } from "./noise-js";456789101112function 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}3940414243describe("PCH-01-002 — BLAKE2s primitive vectors (shared with peach-android)", () => {44 const CK = new Uint8Array(32).map((_, i) => i); 45 const IKM = new Uint8Array(32).map((_, i) => i + 32); 4647 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 "4f80ae4d8745085d08d3e0b3ca315f66635cfd2c2831f41b897c8363364ffbf1",87 );8889 const ct = a.encrypt(new TextEncoder().encode("ping"));90 expect(new TextDecoder().decode(b.decrypt(ct))).toBe("ping");91 });92});