12345678910111213141516import { describe, expect, it } from "vitest";17import { generateKeyPair } from "@stablelib/x25519";18import {19 noiseXXInitiator,20 noiseXXResponder,21} from "../../shared/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 responderChannel.send(new Uint8Array(0)).catch(() => {});143 await responderPromise;144 });145146 147 148 149 it("T-010b: correct-key responder → handshake proceeds to completion", async () => {150 const initiatorKp = generateKeyPair();151 const responderKp = generateKeyPair();152 const expectedHex = pubkeyToHex(responderKp.publicKey);153154 const { a: initiatorChannel, b: responderChannel } = createChannelPair();155156 const responderPromise = noiseXXResponder(157 responderKp.secretKey,158 responderKp.publicKey,159 responderChannel.send,160 responderChannel.receive,161 );162163 const initiatorPromise = noiseXXInitiator(164 initiatorKp.secretKey,165 initiatorKp.publicKey,166 initiatorChannel.send,167 initiatorChannel.receive,168 (remoteStatic) => {169 const hex = pubkeyToHex(remoteStatic);170 if (hex !== expectedHex) {171 throw new Error("unexpected peer");172 }173 },174 );175176 const [initiatorSession, responderSession] = await Promise.all([177 initiatorPromise,178 responderPromise,179 ]);180181 182 expect(initiatorSession).toBeDefined();183 expect(responderSession).toBeDefined();184185 186 expect(pubkeyToHex(initiatorSession.remoteStaticPublicKey)).toBe(expectedHex);187 });188189 190 191 192 193 194 195 196 197 198 it("T-010c: initiator sends only msg1 before receiving msg2 (no speculative msg3)", async () => {199 const initiatorKp = generateKeyPair();200 const expectedHex = pubkeyToHex(generateKeyPair().publicKey); 201202 const sentMessages: Uint8Array[] = [];203 let receiveCallCount = 0;204205 206 const mockSend = async (msg: Uint8Array) => {207 sentMessages.push(msg.slice());208 };209210 211 const mockReceive = (): Promise<Uint8Array> => {212 receiveCallCount++;213 return new Promise(() => { });214 };215216 217 const initiatorPromise = noiseXXInitiator(218 initiatorKp.secretKey,219 initiatorKp.publicKey,220 mockSend,221 mockReceive,222 (remoteStatic) => {223 const hex = pubkeyToHex(remoteStatic);224 if (hex !== expectedHex) throw new Error("wrong peer");225 },226 );227228 229 await new Promise((r) => setTimeout(r, 10));230231 232 233 234 235 236 expect(sentMessages).toHaveLength(1);237 expect(sentMessages[0]).toHaveLength(32); 238239 240 initiatorPromise.catch(() => {});241 });242});