// T-001: BIP39 word-list integrity // // Why this test exists: a single transcription error in recovery-word-list.ts // silently shifts every subsequent word index and renders affected recovery // phrases permanently unrestorable. There is no runtime error — the wrong word // just maps to a valid (but different) root-key prefix, which decrypts a // completely different vault. No test previously guarded this. import { describe, expect, it } from "vitest"; import { WORD_LIST } from "../../shared/recovery-word-list"; describe("T-001: BIP39 word-list integrity", () => { // T-001a: exact length is load-bearing — 2048 words = 2^11 indices per word, // which is what the BIP39 bit-packing arithmetic in recovery-words.ts assumes. it("T-001a: list has exactly 2048 words", () => { expect(WORD_LIST).toHaveLength(2048); }); // T-001b: a duplicate word at position N collapses two indices to one, // making N different root keys produce identical phrases (silent collision). it("T-001b: no duplicates — Set.size === 2048", () => { expect(new Set(WORD_LIST).size).toBe(2048); }); // T-001c: BIP39 requires lowercase ASCII 3–8 letters. A word with an accent, // uppercase letter, or number breaks the WORD_LIST.indexOf() lookup in // wordsToRootKeyMaterial and returns -1 → "unknown word" error on restore. it("T-001c: every word is lowercase ASCII only (3–8 letters)", () => { const badWords: string[] = []; for (const word of WORD_LIST) { if (!/^[a-z]{3,8}$/.test(word)) { badWords.push(word); } } expect(badWords).toEqual([]); }); // T-001d: canonical BIP39 position anchors. // Verified against the official BIP39 English wordlist // (https://github.com/trezor/python-mnemonic/blob/master/src/mnemonic/wordlist/english.txt). // A transposition, insertion, or deletion anywhere in the list shifts all // subsequent positions and is caught by one of these four anchors. it("T-001d: canonical BIP39 spot-checks", () => { expect(WORD_LIST[0]).toBe("abandon"); // first word expect(WORD_LIST[1]).toBe("ability"); // second word expect(WORD_LIST[2047]).toBe("zoo"); // last word expect(WORD_LIST[585]).toBe("empty"); // mid-list anchor }); // T-001e: the list must be in strict alphabetical order. // An out-of-order word would pass T-001b (no duplicates) but would break // any tooling or human verification that cross-references by position. it("T-001e: list is in canonical alphabetical order", () => { const sorted = [...WORD_LIST].sort((a, b) => a.localeCompare(b)); expect(WORD_LIST).toEqual(sorted); }); });