noise-small-order-dh.test.ts

3.8 KB · raw

1// PB-26-014 follow-up — the JS-side DH boundaries in noise-js.ts mirror the2// Rust core's small-order X25519 blocklist (libhydrogen's contributory-behaviour3// list). The WASM core rejects these before every handshake DH; the TypeScript4// sharedKey calls must too, before doing the DH.56import { describe, expect, it } from "vitest";7import { generateKeyPair } from "@stablelib/x25519";8import {9  computeAuthProof,10  hexToBytes,11  isSmallOrderPubkey,12  noiseKKInitiator,13  noiseKKResponder,14  performResponderAuth,15} from "./noise-js";16import type { NoiseNKSession } from "./noise-js";1718const SMALL_ORDER_PUBKEY_HEX = [19  "00".repeat(32),20  "01" + "00".repeat(31),21  "e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b880",22  "5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f11d7",23  "ec" + "ff".repeat(30) + "7f",24  "ed" + "ff".repeat(30) + "7f",25  "ee" + "ff".repeat(30) + "7f",26  "cdeb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b880",27  "4c9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f11d7",28];2930const deadTransport = {31  send: async () => undefined,32  receive: () => new Promise<Uint8Array>(() => undefined),33};3435describe("PB-26-014 follow-up — JS-side small-order X25519 rejection", () => {36  it("flags all nine small-order u-coordinates; a real generated key passes", () => {37    for (const hex of SMALL_ORDER_PUBKEY_HEX) {38      expect(isSmallOrderPubkey(hexToBytes(hex))).toBe(true);39    }40    expect(isSmallOrderPubkey(generateKeyPair().publicKey)).toBe(false);41  });4243  it("KK initiator aborts against a small-order responder static", async () => {44    const initiator = generateKeyPair();45    for (const hex of SMALL_ORDER_PUBKEY_HEX) {46      await expect(47        noiseKKInitiator(48          initiator.secretKey,49          initiator.publicKey,50          hexToBytes(hex),51          deadTransport.send,52          deadTransport.receive,53        ),54      ).rejects.toThrow(/small-order/i);55    }56  });5758  it("PB-26-601: KK responder aborts against a small-order initiator static", async () => {59    const responder = generateKeyPair();60    for (const hex of SMALL_ORDER_PUBKEY_HEX) {61      // A transport that reports if the WASM core is reached: the guard must62      // throw first, so neither callback may ever run.63      const touched = { send: 0, receive: 0 };64      await expect(65        noiseKKResponder(66          responder.secretKey,67          responder.publicKey,68          hexToBytes(hex),69          async () => {70            touched.send += 1;71          },72          () => {73            touched.receive += 1;74            return Promise.reject(new Error("reached the WASM core"));75          },76        ),77      ).rejects.toThrow(/small-order/i);78      expect(touched).toEqual({ send: 0, receive: 0 });79    }80  });8182  it("computeAuthProof refuses a small-order responder key", () => {83    const initiator = generateKeyPair();84    const nonce = new Uint8Array(32);85    const handshakeHash = new Uint8Array(32);86    expect(() =>87      computeAuthProof(88        initiator.secretKey,89        initiator.publicKey,90        hexToBytes(SMALL_ORDER_PUBKEY_HEX[3]!),91        nonce,92        handshakeHash,93      ),94    ).toThrow(/small-order/i);95  });9697  it("performResponderAuth refuses a PROOF frame with a small-order initiator key", async () => {98    const responder = generateKeyPair();99    const session = { handshakeHash: new Uint8Array(32) } as NoiseNKSession;100    const proofFrame = new TextEncoder().encode(101      JSON.stringify({102        type: "PROOF",103        initiator_public_key_hex: SMALL_ORDER_PUBKEY_HEX[0],104        proof_hex: "00".repeat(32),105      }),106    );107    await expect(108      performResponderAuth(109        session,110        responder.secretKey,111        responder.publicKey,112        async () => undefined,113        async () => proofFrame,114      ),115    ).rejects.toThrow(/small-order/i);116  });117});