recovery-words.integrity.test.ts
1// T-001: BIP39 word-list integrity2//3// Why this test exists: a single transcription error in recovery-word-list.ts4// silently shifts every subsequent word index and renders affected recovery5// phrases permanently unrestorable. There is no runtime error — the wrong word6// just maps to a valid (but different) root-key prefix, which decrypts a7// completely different vault. No test previously guarded this.89import { describe, expect, it } from "vitest";10import { WORD_LIST } from "./recovery-word-list";1112describe("T-001: BIP39 word-list integrity", () => {13 // T-001a: exact length is load-bearing — 2048 words = 2^11 indices per word,14 // which is what the BIP39 bit-packing arithmetic in recovery-words.ts assumes.15 it("T-001a: list has exactly 2048 words", () => {16 expect(WORD_LIST).toHaveLength(2048);17 });1819 // T-001b: a duplicate word at position N collapses two indices to one,20 // making N different root keys produce identical phrases (silent collision).21 it("T-001b: no duplicates — Set.size === 2048", () => {22 expect(new Set(WORD_LIST).size).toBe(2048);23 });2425 // T-001c: BIP39 requires lowercase ASCII 3–8 letters. A word with an accent,26 // uppercase letter, or number breaks the WORD_LIST.indexOf() lookup in27 // wordsToRootKeyMaterial and returns -1 → "unknown word" error on restore.28 it("T-001c: every word is lowercase ASCII only (3–8 letters)", () => {29 const badWords: string[] = [];30 for (const word of WORD_LIST) {31 if (!/^[a-z]{3,8}$/.test(word)) {32 badWords.push(word);33 }34 }35 expect(badWords).toEqual([]);36 });3738 // T-001d: canonical BIP39 position anchors.39 // Verified against the official BIP39 English wordlist40 // (https://github.com/trezor/python-mnemonic/blob/master/src/mnemonic/wordlist/english.txt).41 // A transposition, insertion, or deletion anywhere in the list shifts all42 // subsequent positions and is caught by one of these four anchors.43 it("T-001d: canonical BIP39 spot-checks", () => {44 expect(WORD_LIST[0]).toBe("abandon"); // first word45 expect(WORD_LIST[1]).toBe("ability"); // second word46 expect(WORD_LIST[2047]).toBe("zoo"); // last word47 expect(WORD_LIST[585]).toBe("empty"); // mid-list anchor48 });4950 // T-001e: the list must be in strict alphabetical order.51 // An out-of-order word would pass T-001b (no duplicates) but would break52 // any tooling or human verification that cross-references by position.53 it("T-001e: list is in canonical alphabetical order", () => {54 const sorted = [...WORD_LIST].sort((a, b) => a.localeCompare(b));55 expect(WORD_LIST).toEqual(sorted);56 });57});