12345678910111213141516import { describe, expect, it } from "vitest";17import { generateKeyPair } from "@stablelib/x25519";18import {19 noiseXXInitiator,20 noiseXXResponder,21} from "./noise-xx";222324252627interface Channel {28 send: (msg: Uint8Array) => Promise<void>;29 receive: () => Promise<Uint8Array>;30}3132function createChannelPair(): { a: Channel; b: Channel } {33 const aToB: Uint8Array[] = [];34 const bToA: Uint8Array[] = [];35 const aToBWaiters: Array<(v: Uint8Array) => void> = [];36 const bToAWaiters: Array<(v: Uint8Array) => void> = [];3738 return {39 a: {40 send: async (msg) => {41 const waiter = aToBWaiters.shift();42 if (waiter) waiter(msg.slice());43 else aToB.push(msg.slice());44 },45 receive: () => {46 const msg = bToA.shift();47 if (msg) return Promise.resolve(msg);48 return new Promise((resolve) => bToAWaiters.push(resolve));49 },50 },51 b: {52 send: async (msg) => {53 const waiter = bToAWaiters.shift();54 if (waiter) waiter(msg.slice());55 else bToA.push(msg.slice());56 },57 receive: () => {58 const msg = aToB.shift();59 if (msg) return Promise.resolve(msg);60 return new Promise((resolve) => aToBWaiters.push(resolve));61 },62 },63 };64}6566function pubkeyToHex(key: Uint8Array): string {67 return Array.from(key).map(b => b.toString(16).padStart(2, "0")).join("");68}697071727374describe("T-010: PCH-01-018 subnet-probe identity-leak integration", () => {75 76 77 78 79 80 81 82 83 84 85 it("T-010a: wrong-key responder → initiator aborts before msg3; initiator static not transmitted", async () => {86 87 const initiatorKp = generateKeyPair();88 89 const expectedResponderKp = generateKeyPair();90 91 const wrongResponderKp = generateKeyPair();9293 const expectedHex = pubkeyToHex(expectedResponderKp.publicKey);94 const { a: initiatorChannel, b: responderChannel } = createChannelPair();9596 97 let responderMsgCount = 0;98 const wrappedResponderReceive = async (): Promise<Uint8Array> => {99 const msg = await responderChannel.receive();100 responderMsgCount++;101 return msg;102 };103104 105 106 const responderPromise = noiseXXResponder(107 wrongResponderKp.secretKey,108 wrongResponderKp.publicKey,109 responderChannel.send,110 wrappedResponderReceive,111 ).catch(() => { });112113 114 const initiatorPromise = noiseXXInitiator(115 initiatorKp.secretKey,116 initiatorKp.publicKey,117 initiatorChannel.send,118 initiatorChannel.receive,119 (remoteStatic) => {120 const hex = pubkeyToHex(remoteStatic);121 if (hex !== expectedHex) {122 throw new Error(123 `PCH-01-018: unexpected peer static (got ${hex.slice(0, 8)}…, ` +124 `expected ${expectedHex.slice(0, 8)}…)`,125 );126 }127 },128 );129130 131 await expect(initiatorPromise).rejects.toThrow(/unexpected peer static|PCH-01-018/i);132133 134 135 136 137 138 139 expect(responderMsgCount).toBe(1);140141 142 143 144 initiatorChannel.send(new Uint8Array(0)).catch(() => {});145 await responderPromise;146 });147148 149 150 151 it("T-010b: correct-key responder → handshake proceeds to completion", async () => {152 const initiatorKp = generateKeyPair();153 const responderKp = generateKeyPair();154 const expectedHex = pubkeyToHex(responderKp.publicKey);155156 const { a: initiatorChannel, b: responderChannel } = createChannelPair();157158 const responderPromise = noiseXXResponder(159 responderKp.secretKey,160 responderKp.publicKey,161 responderChannel.send,162 responderChannel.receive,163 );164165 const initiatorPromise = noiseXXInitiator(166 initiatorKp.secretKey,167 initiatorKp.publicKey,168 initiatorChannel.send,169 initiatorChannel.receive,170 (remoteStatic) => {171 const hex = pubkeyToHex(remoteStatic);172 if (hex !== expectedHex) {173 throw new Error("unexpected peer");174 }175 },176 );177178 const [initiatorSession, responderSession] = await Promise.all([179 initiatorPromise,180 responderPromise,181 ]);182183 184 expect(initiatorSession).toBeDefined();185 expect(responderSession).toBeDefined();186187 188 expect(pubkeyToHex(initiatorSession.remoteStaticPublicKey)).toBe(expectedHex);189 });190191 192 193 194 195 196 197 198 199 200 it("T-010c: initiator sends only msg1 before receiving msg2 (no speculative msg3)", async () => {201 const initiatorKp = generateKeyPair();202 const expectedHex = pubkeyToHex(generateKeyPair().publicKey); 203204 const sentMessages: Uint8Array[] = [];205 let receiveCallCount = 0;206207 208 const mockSend = async (msg: Uint8Array) => {209 sentMessages.push(msg.slice());210 };211212 213 const mockReceive = (): Promise<Uint8Array> => {214 receiveCallCount++;215 return new Promise(() => { });216 };217218 219 const initiatorPromise = noiseXXInitiator(220 initiatorKp.secretKey,221 initiatorKp.publicKey,222 mockSend,223 mockReceive,224 (remoteStatic) => {225 const hex = pubkeyToHex(remoteStatic);226 if (hex !== expectedHex) throw new Error("wrong peer");227 },228 );229230 231 await new Promise((r) => setTimeout(r, 10));232233 234 235 236 237 238 expect(sentMessages).toHaveLength(1);239 expect(sentMessages[0]).toHaveLength(32); 240241 242 initiatorPromise.catch(() => {});243 });244});