// PCH-02-009 regression tests — every Noise NK DH must reject an all-zero // shared secret (low-order / identity peer point) instead of feeding it into // the key schedule. import { describe, expect, it } from "vitest"; import { generateKeyPair } from "@stablelib/x25519"; import { noiseNKInitiator, noiseNKResponder } from "./noise-js"; const ZERO_POINT = new Uint8Array(32); function queuePair() { const makeQueue = () => { const items: Uint8Array[] = []; const waiters: Array<(m: Uint8Array) => void> = []; return { push(m: Uint8Array) { const w = waiters.shift(); if (w) w(m); else items.push(m); }, pull(): Promise { const m = items.shift(); return m ? Promise.resolve(m) : new Promise((r) => waiters.push(r)); }, }; }; const aToB = makeQueue(); const bToA = makeQueue(); return { a: { send: async (m: Uint8Array) => aToB.push(m), receive: () => bToA.pull() }, b: { send: async (m: Uint8Array) => bToA.push(m), receive: () => aToB.pull() }, }; } const deadTransport = { send: async () => undefined, receive: () => new Promise(() => undefined), }; describe("PCH-02-009 — reject zero X25519 DH outputs", () => { it("NK initiator aborts against an all-zero responder static key", async () => { // The zero point is also small-order: the JS blocklist guard now rejects // it before the DH even runs, hence the small-order message. await expect( noiseNKInitiator(ZERO_POINT, deadTransport.send, deadTransport.receive), ).rejects.toThrow(/diffie|dh|small-order/i); }); it("NK responder aborts on an all-zero msg-1 ephemeral", async () => { const ourStatic = generateKeyPair(); const msg1 = new Uint8Array(48); await expect( noiseNKResponder( ourStatic.secretKey, ourStatic.publicKey, deadTransport.send, async () => msg1, ), ).rejects.toThrow(/diffie|dh/i); }); it("honest NK handshake still completes and transports a frame (regression)", async () => { const responderStatic = generateKeyPair(); const { a, b } = queuePair(); const [initiator, responder] = await Promise.all([ noiseNKInitiator(responderStatic.publicKey, a.send, a.receive), noiseNKResponder(responderStatic.secretKey, responderStatic.publicKey, b.send, b.receive), ]); const frame = new TextEncoder().encode("peach-nk-frame"); expect(responder.decrypt(initiator.encrypt(frame))).toEqual(frame); expect(initiator.handshakeHash).toEqual(responder.handshakeHash); }); });