12345678910import { describe, expect, it } from "vitest";11import {12 rootKeyToWords,13 wordsToRootKeyMaterial,14} from "./recovery-words";151617function makeRootKey(fillByte = 0x42): Uint8Array {18 const key = new Uint8Array(32);19 for (let i = 0; i < 32; i++) key[i] = fillByte ^ i;20 return key;21}2223describe("T-002: BIP39 checksum rejection", () => {24 25 26 it("T-002a: valid 12-word phrase passes checksum", async () => {27 const rootKey = makeRootKey();28 const words = await rootKeyToWords(rootKey);29 expect(words).toHaveLength(12);3031 const recovered = await wordsToRootKeyMaterial(words);32 33 expect(Array.from(recovered.slice(0, 16))).toEqual(Array.from(rootKey.slice(0, 16)));34 35 expect(Array.from(recovered.slice(16))).toEqual(new Array(16).fill(0));36 });3738 39 40 41 it("T-002b: single word changed → checksum fails", async () => {42 const words = await rootKeyToWords(makeRootKey(0xaa));43 const corrupted = [...words];44 45 corrupted[11] = corrupted[11] === "abandon" ? "ability" : "abandon";4647 await expect(wordsToRootKeyMaterial(corrupted)).rejects.toThrow(48 /checksum/i,49 );50 });5152 53 54 55 it("T-002c: words in wrong order → checksum fails", async () => {56 const words = await rootKeyToWords(makeRootKey(0xbb));57 58 if (words[0] !== words[11]) {59 const reordered = [...words];60 [reordered[0], reordered[11]] = [reordered[11], reordered[0]];61 62 63 64 await expect(wordsToRootKeyMaterial(reordered)).rejects.toThrow(65 /checksum|exactly 12/i,66 );67 } else {68 69 const reordered = [...words];70 [reordered[0], reordered[5]] = [reordered[5], reordered[0]];71 await expect(wordsToRootKeyMaterial(reordered)).rejects.toThrow(72 /checksum|exactly 12/i,73 );74 }75 });7677 78 79 80 it("T-002d: 11 words → throws (not silently truncated)", async () => {81 const words = await rootKeyToWords(makeRootKey());82 await expect(wordsToRootKeyMaterial(words.slice(0, 11))).rejects.toThrow(83 /exactly 12/i,84 );85 });8687 88 it("T-002e: empty array → throws gracefully", async () => {89 await expect(wordsToRootKeyMaterial([])).rejects.toThrow(/exactly 12/i);90 });9192 93 94 95 96 it("T-002f: unknown word → throws with position hint", async () => {97 const words = await rootKeyToWords(makeRootKey());98 const withBadWord = [...words];99 withBadWord[3] = "liberté"; 100101 await expect(wordsToRootKeyMaterial(withBadWord)).rejects.toThrow(102 /unknown word/i,103 );104 });105});