10d155fee98b

drop the legacy word-list fallback so decoding has one unambiguous path; words written under the pre-2.4.13 ordering are no longer accepted (v2.4.14)

time
2026-07-26 14:39 UTC · 3 months ago
machine
koh2
agent
claude-opus-5
session
recovery word divergence
parent
0f3f0e36bc4a
5 files VERSION+1 −1app/build.gradle+2 −2app/src/main/java/com/peachpasswords/vault/RecoveryWordList.kt+4 −269app/src/main/java/com/peachpasswords/vault/RecoveryWords.kt+2 −19app/src/test/java/com/peachpasswords/vault/CrossPlatformRecoveryWordsTest.kt+7 −21
VERSION+1 −1
@@ -1 +1 @@1-2.4.131+2.4.14

view file

app/build.gradle+2 −2
@@ -16,8 +16,8 @@1616         applicationId "com.peachpasswords"1717         minSdk 261818         targetSdk 3519-        versionCode 24001320-        versionName "2.4.13"19+        versionCode 24001420+        versionName "2.4.14"2121 2222         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"2323         vectorDrawables {

view file

app/src/main/java/com/peachpasswords/vault/RecoveryWordList.kt+4 −269
@@ -8,8 +8,10 @@88  * This MUST stay identical to the browser's list. Words are only a human99  * readable encoding of the root key's first 16 bytes, so any divergence in1010  * ordering means the same vault prints different words on each platform and a11- * phrase written down on one device restores nothing on the other.12- * `CrossPlatformRecoveryWordsTest` pins that with a shared vector.11+ * phrase written down on one device restores nothing on the other. That is12+ * exactly what shipped up to v2.4.12, which carried a duplicate "abandon" at13+ * index 1 and was therefore this list shifted by one from index 1 onward.14+ * `CrossPlatformRecoveryWordsTest` pins the shared vector that catches it.1315  */1416 val WORD_LIST: List<String> = listOf(1517     "abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract",@@ -268,271 +270,4 @@268270     "wonder", "wood", "wool", "word", "work", "world", "worry", "worth",269271     "wrap", "wreck", "wrestle", "wrist", "write", "wrong", "yard", "year",270272     "yellow", "you", "young", "youth", "zebra", "zero", "zone", "zoo",271-)272-273-/**274- * The list shipped up to v2.4.12, which carried a duplicate "abandon" at index275- * 1 and so was the canonical list shifted by one from index 1 onward. Phrases276- * written down from those builds decode only under this ordering, so277- * [wordsToRootKeyMaterial] falls back to it. Never used for display or for new278- * vaults — decode only, and it will be removed once no such phrase can remain.279- */280-val LEGACY_WORD_LIST: List<String> = listOf(281-    "abandon", "abandon", "ability", "able", "about", "above", "absent", "absorb",282-    "abstract", "absurd", "abuse", "access", "accident", "account", "accuse", "achieve",283-    "acid", "acoustic", "acquire", "across", "act", "action", "actor", "actress",284-    "actual", "adapt", "add", "addict", "address", "adjust", "admit", "adult",285-    "advance", "advice", "aerobic", "affair", "afford", "afraid", "again", "age",286-    "agent", "agree", "ahead", "aim", "air", "airport", "aisle", "alarm",287-    "album", "alcohol", "alert", "alien", "all", "alley", "allow", "almost",288-    "alone", "alpha", "already", "also", "alter", "always", "amateur", "amazing",289-    "among", "amount", "amused", "analyst", "anchor", "ancient", "anger", "angle",290-    "angry", "animal", "ankle", "announce", "annual", "another", "answer", "antenna",291-    "antique", "anxiety", "any", "apart", "apology", "appear", "apple", "approve",292-    "april", "arch", "arctic", "area", "arena", "argue", "arm", "armed",293-    "armor", "army", "around", "arrange", "arrest", "arrive", "arrow", "art",294-    "artefact", "artist", "artwork", "ask", "aspect", "assault", "asset", "assist",295-    "assume", "asthma", "athlete", "atom", "attack", "attend", "attitude", "attract",296-    "auction", "audit", "august", "aunt", "author", "auto", "autumn", "average",297-    "avocado", "avoid", "awake", "aware", "away", "awesome", "awful", "awkward",298-    "axis", "baby", "bachelor", "bacon", "badge", "bag", "balance", "balcony",299-    "ball", "bamboo", "banana", "banner", "bar", "barely", "bargain", "barrel",300-    "base", "basic", "basket", "battle", "beach", "bean", "beauty", "because",301-    "become", "beef", "before", "begin", "behave", "behind", "believe", "below",302-    "belt", "bench", "benefit", "best", "betray", "better", "between", "beyond",303-    "bicycle", "bid", "bike", "bind", "biology", "bird", "birth", "bitter",304-    "black", "blade", "blame", "blanket", "blast", "bleak", "bless", "blind",305-    "blood", "blossom", "blouse", "blue", "blur", "blush", "board", "boat",306-    "body", "boil", "bomb", "bone", "bonus", "book", "boost", "border",307-    "boring", "borrow", "boss", "bottom", "bounce", "box", "boy", "bracket",308-    "brain", "brand", "brass", "brave", "bread", "breeze", "brick", "bridge",309-    "brief", "bright", "bring", "brisk", "broccoli", "broken", "bronze", "broom",310-    "brother", "brown", "brush", "bubble", "buddy", "budget", "buffalo", "build",311-    "bulb", "bulk", "bullet", "bundle", "bunker", "burden", "burger", "burst",312-    "bus", "business", "busy", "butter", "buyer", "buzz", "cabbage", "cabin",313-    "cable", "cactus", "cage", "cake", "call", "calm", "camera", "camp",314-    "can", "canal", "cancel", "candy", "cannon", "canoe", "canvas", "canyon",315-    "capable", "capital", "captain", "car", "carbon", "card", "cargo", "carpet",316-    "carry", "cart", "case", "cash", "casino", "castle", "casual", "cat",317-    "catalog", "catch", "category", "cattle", "caught", "cause", "caution", "cave",318-    "ceiling", "celery", "cement", "census", "century", "cereal", "certain", "chair",319-    "chalk", "champion", "change", "chaos", "chapter", "charge", "chase", "chat",320-    "cheap", "check", "cheese", "chef", "cherry", "chest", "chicken", "chief",321-    "child", "chimney", "choice", "choose", "chronic", "chuckle", "chunk", "churn",322-    "cigar", "cinnamon", "circle", "citizen", "city", "civil", "claim", "clap",323-    "clarify", "claw", "clay", "clean", "clerk", "clever", "click", "client",324-    "cliff", "climb", "clinic", "clip", "clock", "clog", "close", "cloth",325-    "cloud", "clown", "club", "clump", "cluster", "clutch", "coach", "coast",326-    "coconut", "code", "coffee", "coil", "coin", "collect", "color", "column",327-    "combine", "come", "comfort", "comic", "common", "company", "concert", "conduct",328-    "confirm", "congress", "connect", "consider", "control", "convince", "cook", "cool",329-    "copper", "copy", "coral", "core", "corn", "correct", "cost", "cotton",330-    "couch", "country", "couple", "course", "cousin", "cover", "coyote", "crack",331-    "cradle", "craft", "cram", "crane", "crash", "crater", "crawl", "crazy",332-    "cream", "credit", "creek", "crew", "cricket", "crime", "crisp", "critic",333-    "crop", "cross", "crouch", "crowd", "crucial", "cruel", "cruise", "crumble",334-    "crunch", "crush", "cry", "crystal", "cube", "culture", "cup", "cupboard",335-    "curious", "current", "curtain", "curve", "cushion", "custom", "cute", "cycle",336-    "dad", "damage", "damp", "dance", "danger", "daring", "dash", "daughter",337-    "dawn", "day", "deal", "debate", "debris", "decade", "december", "decide",338-    "decline", "decorate", "decrease", "deer", "defense", "define", "defy", "degree",339-    "delay", "deliver", "demand", "demise", "denial", "dentist", "deny", "depart",340-    "depend", "deposit", "depth", "deputy", "derive", "describe", "desert", "design",341-    "desk", "despair", "destroy", "detail", "detect", "develop", "device", "devote",342-    "diagram", "dial", "diamond", "diary", "dice", "diesel", "diet", "differ",343-    "digital", "dignity", "dilemma", "dinner", "dinosaur", "direct", "dirt", "disagree",344-    "discover", "disease", "dish", "dismiss", "disorder", "display", "distance", "divert",345-    "divide", "divorce", "dizzy", "doctor", "document", "dog", "doll", "dolphin",346-    "domain", "donate", "donkey", "donor", "door", "dose", "double", "dove",347-    "draft", "dragon", "drama", "drastic", "draw", "dream", "dress", "drift",348-    "drill", "drink", "drip", "drive", "drop", "drum", "dry", "duck",349-    "dumb", "dune", "during", "dust", "dutch", "duty", "dwarf", "dynamic",350-    "eager", "eagle", "early", "earn", "earth", "easily", "east", "easy",351-    "echo", "ecology", "economy", "edge", "edit", "educate", "effort", "egg",352-    "eight", "either", "elbow", "elder", "electric", "elegant", "element", "elephant",353-    "elevator", "elite", "else", "embark", "embody", "embrace", "emerge", "emotion",354-    "employ", "empower", "empty", "enable", "enact", "end", "endless", "endorse",355-    "enemy", "energy", "enforce", "engage", "engine", "enhance", "enjoy", "enlist",356-    "enough", "enrich", "enroll", "ensure", "enter", "entire", "entry", "envelope",357-    "episode", "equal", "equip", "era", "erase", "erode", "erosion", "error",358-    "erupt", "escape", "essay", "essence", "estate", "eternal", "ethics", "evidence",359-    "evil", "evoke", "evolve", "exact", "example", "excess", "exchange", "excite",360-    "exclude", "excuse", "execute", "exercise", "exhaust", "exhibit", "exile", "exist",361-    "exit", "exotic", "expand", "expect", "expire", "explain", "expose", "express",362-    "extend", "extra", "eye", "eyebrow", "fabric", "face", "faculty", "fade",363-    "faint", "faith", "fall", "false", "fame", "family", "famous", "fan",364-    "fancy", "fantasy", "farm", "fashion", "fat", "fatal", "father", "fatigue",365-    "fault", "favorite", "feature", "february", "federal", "fee", "feed", "feel",366-    "female", "fence", "festival", "fetch", "fever", "few", "fiber", "fiction",367-    "field", "figure", "file", "film", "filter", "final", "find", "fine",368-    "finger", "finish", "fire", "firm", "first", "fiscal", "fish", "fit",369-    "fitness", "fix", "flag", "flame", "flash", "flat", "flavor", "flee",370-    "flight", "flip", "float", "flock", "floor", "flower", "fluid", "flush",371-    "fly", "foam", "focus", "fog", "foil", "fold", "follow", "food",372-    "foot", "force", "forest", "forget", "fork", "fortune", "forum", "forward",373-    "fossil", "foster", "found", "fox", "fragile", "frame", "frequent", "fresh",374-    "friend", "fringe", "frog", "front", "frost", "frown", "frozen", "fruit",375-    "fuel", "fun", "funny", "furnace", "fury", "future", "gadget", "gain",376-    "galaxy", "gallery", "game", "gap", "garage", "garbage", "garden", "garlic",377-    "garment", "gas", "gasp", "gate", "gather", "gauge", "gaze", "general",378-    "genius", "genre", "gentle", "genuine", "gesture", "ghost", "giant", "gift",379-    "giggle", "ginger", "giraffe", "girl", "give", "glad", "glance", "glare",380-    "glass", "glide", "glimpse", "globe", "gloom", "glory", "glove", "glow",381-    "glue", "goat", "goddess", "gold", "good", "goose", "gorilla", "gospel",382-    "gossip", "govern", "gown", "grab", "grace", "grain", "grant", "grape",383-    "grass", "gravity", "great", "green", "grid", "grief", "grit", "grocery",384-    "group", "grow", "grunt", "guard", "guess", "guide", "guilt", "guitar",385-    "gun", "gym", "habit", "hair", "half", "hammer", "hamster", "hand",386-    "happy", "harbor", "hard", "harsh", "harvest", "hat", "have", "hawk",387-    "hazard", "head", "health", "heart", "heavy", "hedgehog", "height", "hello",388-    "helmet", "help", "hen", "hero", "hidden", "high", "hill", "hint",389-    "hip", "hire", "history", "hobby", "hockey", "hold", "hole", "holiday",390-    "hollow", "home", "honey", "hood", "hope", "horn", "horror", "horse",391-    "hospital", "host", "hotel", "hour", "hover", "hub", "huge", "human",392-    "humble", "humor", "hundred", "hungry", "hunt", "hurdle", "hurry", "hurt",393-    "husband", "hybrid", "ice", "icon", "idea", "identify", "idle", "ignore",394-    "ill", "illegal", "illness", "image", "imitate", "immense", "immune", "impact",395-    "impose", "improve", "impulse", "inch", "include", "income", "increase", "index",396-    "indicate", "indoor", "industry", "infant", "inflict", "inform", "inhale", "inherit",397-    "initial", "inject", "injury", "inmate", "inner", "innocent", "input", "inquiry",398-    "insane", "insect", "inside", "inspire", "install", "intact", "interest", "into",399-    "invest", "invite", "involve", "iron", "island", "isolate", "issue", "item",400-    "ivory", "jacket", "jaguar", "jar", "jazz", "jealous", "jeans", "jelly",401-    "jewel", "job", "join", "joke", "journey", "joy", "judge", "juice",402-    "jump", "jungle", "junior", "junk", "just", "kangaroo", "keen", "keep",403-    "ketchup", "key", "kick", "kid", "kidney", "kind", "kingdom", "kiss",404-    "kit", "kitchen", "kite", "kitten", "kiwi", "knee", "knife", "knock",405-    "know", "lab", "label", "labor", "ladder", "lady", "lake", "lamp",406-    "language", "laptop", "large", "later", "latin", "laugh", "laundry", "lava",407-    "law", "lawn", "lawsuit", "layer", "lazy", "leader", "leaf", "learn",408-    "leave", "lecture", "left", "leg", "legal", "legend", "leisure", "lemon",409-    "lend", "length", "lens", "leopard", "lesson", "letter", "level", "liar",410-    "liberty", "library", "license", "life", "lift", "light", "like", "limb",411-    "limit", "link", "lion", "liquid", "list", "little", "live", "lizard",412-    "load", "loan", "lobster", "local", "lock", "logic", "lonely", "long",413-    "loop", "lottery", "loud", "lounge", "love", "loyal", "lucky", "luggage",414-    "lumber", "lunar", "lunch", "luxury", "lyrics", "machine", "mad", "magic",415-    "magnet", "maid", "mail", "main", "major", "make", "mammal", "man",416-    "manage", "mandate", "mango", "mansion", "manual", "maple", "marble", "march",417-    "margin", "marine", "market", "marriage", "mask", "mass", "master", "match",418-    "material", "math", "matrix", "matter", "maximum", "maze", "meadow", "mean",419-    "measure", "meat", "mechanic", "medal", "media", "melody", "melt", "member",420-    "memory", "mention", "menu", "mercy", "merge", "merit", "merry", "mesh",421-    "message", "metal", "method", "middle", "midnight", "milk", "million", "mimic",422-    "mind", "minimum", "minor", "minute", "miracle", "mirror", "misery", "miss",423-    "mistake", "mix", "mixed", "mixture", "mobile", "model", "modify", "mom",424-    "moment", "monitor", "monkey", "monster", "month", "moon", "moral", "more",425-    "morning", "mosquito", "mother", "motion", "motor", "mountain", "mouse", "move",426-    "movie", "much", "muffin", "mule", "multiply", "muscle", "museum", "mushroom",427-    "music", "must", "mutual", "myself", "mystery", "myth", "naive", "name",428-    "napkin", "narrow", "nasty", "nation", "nature", "near", "neck", "need",429-    "negative", "neglect", "neither", "nephew", "nerve", "nest", "net", "network",430-    "neutral", "never", "news", "next", "nice", "night", "noble", "noise",431-    "nominee", "noodle", "normal", "north", "nose", "notable", "note", "nothing",432-    "notice", "novel", "now", "nuclear", "number", "nurse", "nut", "oak",433-    "obey", "object", "oblige", "obscure", "observe", "obtain", "obvious", "occur",434-    "ocean", "october", "odor", "off", "offer", "office", "often", "oil",435-    "okay", "old", "olive", "olympic", "omit", "once", "one", "onion",436-    "online", "only", "open", "opera", "opinion", "oppose", "option", "orange",437-    "orbit", "orchard", "order", "ordinary", "organ", "orient", "original", "orphan",438-    "ostrich", "other", "outdoor", "outer", "output", "outside", "oval", "oven",439-    "over", "own", "owner", "oxygen", "oyster", "ozone", "pact", "paddle",440-    "page", "pair", "palace", "palm", "panda", "panel", "panic", "panther",441-    "paper", "parade", "parent", "park", "parrot", "party", "pass", "patch",442-    "path", "patient", "patrol", "pattern", "pause", "pave", "payment", "peace",443-    "peanut", "pear", "peasant", "pelican", "pen", "penalty", "pencil", "people",444-    "pepper", "perfect", "permit", "person", "pet", "phone", "photo", "phrase",445-    "physical", "piano", "picnic", "picture", "piece", "pig", "pigeon", "pill",446-    "pilot", "pink", "pioneer", "pipe", "pistol", "pitch", "pizza", "place",447-    "planet", "plastic", "plate", "play", "please", "pledge", "pluck", "plug",448-    "plunge", "poem", "poet", "point", "polar", "pole", "police", "pond",449-    "pony", "pool", "popular", "portion", "position", "possible", "post", "potato",450-    "pottery", "poverty", "powder", "power", "practice", "praise", "predict", "prefer",451-    "prepare", "present", "pretty", "prevent", "price", "pride", "primary", "print",452-    "priority", "prison", "private", "prize", "problem", "process", "produce", "profit",453-    "program", "project", "promote", "proof", "property", "prosper", "protect", "proud",454-    "provide", "public", "pudding", "pull", "pulp", "pulse", "pumpkin", "punch",455-    "pupil", "puppy", "purchase", "purity", "purpose", "purse", "push", "put",456-    "puzzle", "pyramid", "quality", "quantum", "quarter", "question", "quick", "quit",457-    "quiz", "quote", "rabbit", "raccoon", "race", "rack", "radar", "radio",458-    "rail", "rain", "raise", "rally", "ramp", "ranch", "random", "range",459-    "rapid", "rare", "rate", "rather", "raven", "raw", "razor", "ready",460-    "real", "reason", "rebel", "rebuild", "recall", "receive", "recipe", "record",461-    "recycle", "reduce", "reflect", "reform", "refuse", "region", "regret", "regular",462-    "reject", "relax", "release", "relief", "rely", "remain", "remember", "remind",463-    "remove", "render", "renew", "rent", "reopen", "repair", "repeat", "replace",464-    "report", "require", "rescue", "resemble", "resist", "resource", "response", "result",465-    "retire", "retreat", "return", "reunion", "reveal", "review", "reward", "rhythm",466-    "rib", "ribbon", "rice", "rich", "ride", "ridge", "rifle", "right",467-    "rigid", "ring", "riot", "ripple", "risk", "ritual", "rival", "river",468-    "road", "roast", "robot", "robust", "rocket", "romance", "roof", "rookie",469-    "room", "rose", "rotate", "rough", "round", "route", "royal", "rubber",470-    "rude", "rug", "rule", "run", "runway", "rural", "sad", "saddle",471-    "sadness", "safe", "sail", "salad", "salmon", "salon", "salt", "salute",472-    "same", "sample", "sand", "satisfy", "satoshi", "sauce", "sausage", "save",473-    "say", "scale", "scan", "scare", "scatter", "scene", "scheme", "school",474-    "science", "scissors", "scorpion", "scout", "scrap", "screen", "script", "scrub",475-    "sea", "search", "season", "seat", "second", "secret", "section", "security",476-    "seed", "seek", "segment", "select", "sell", "seminar", "senior", "sense",477-    "sentence", "series", "service", "session", "settle", "setup", "seven", "shadow",478-    "shaft", "shallow", "share", "shed", "shell", "sheriff", "shield", "shift",479-    "shine", "ship", "shiver", "shock", "shoe", "shoot", "shop", "short",480-    "shoulder", "shove", "shrimp", "shrug", "shuffle", "shy", "sibling", "sick",481-    "side", "siege", "sight", "sign", "silent", "silk", "silly", "silver",482-    "similar", "simple", "since", "sing", "siren", "sister", "situate", "six",483-    "size", "skate", "sketch", "ski", "skill", "skin", "skirt", "skull",484-    "slab", "slam", "sleep", "slender", "slice", "slide", "slight", "slim",485-    "slogan", "slot", "slow", "slush", "small", "smart", "smile", "smoke",486-    "smooth", "snack", "snake", "snap", "sniff", "snow", "soap", "soccer",487-    "social", "sock", "soda", "soft", "solar", "soldier", "solid", "solution",488-    "solve", "someone", "song", "soon", "sorry", "sort", "soul", "sound",489-    "soup", "source", "south", "space", "spare", "spatial", "spawn", "speak",490-    "special", "speed", "spell", "spend", "sphere", "spice", "spider", "spike",491-    "spin", "spirit", "split", "spoil", "sponsor", "spoon", "sport", "spot",492-    "spray", "spread", "spring", "spy", "square", "squeeze", "squirrel", "stable",493-    "stadium", "staff", "stage", "stairs", "stamp", "stand", "start", "state",494-    "stay", "steak", "steel", "stem", "step", "stereo", "stick", "still",495-    "sting", "stock", "stomach", "stone", "stool", "story", "stove", "strategy",496-    "street", "strike", "strong", "struggle", "student", "stuff", "stumble", "style",497-    "subject", "submit", "subway", "success", "such", "sudden", "suffer", "sugar",498-    "suggest", "suit", "summer", "sun", "sunny", "sunset", "super", "supply",499-    "supreme", "sure", "surface", "surge", "surprise", "surround", "survey", "suspect",500-    "sustain", "swallow", "swamp", "swap", "swarm", "swear", "sweet", "swift",501-    "swim", "swing", "switch", "sword", "symbol", "symptom", "syrup", "system",502-    "table", "tackle", "tag", "tail", "talent", "talk", "tank", "tape",503-    "target", "task", "taste", "tattoo", "taxi", "teach", "team", "tell",504-    "ten", "tenant", "tennis", "tent", "term", "test", "text", "thank",505-    "that", "theme", "then", "theory", "there", "they", "thing", "this",506-    "thought", "three", "thrive", "throw", "thumb", "thunder", "ticket", "tide",507-    "tiger", "tilt", "timber", "time", "tiny", "tip", "tired", "tissue",508-    "title", "toast", "tobacco", "today", "toddler", "toe", "together", "toilet",509-    "token", "tomato", "tomorrow", "tone", "tongue", "tonight", "tool", "tooth",510-    "top", "topic", "topple", "torch", "tornado", "tortoise", "toss", "total",511-    "tourist", "toward", "tower", "town", "toy", "track", "trade", "traffic",512-    "tragic", "train", "transfer", "trap", "trash", "travel", "tray", "treat",513-    "tree", "trend", "trial", "tribe", "trick", "trigger", "trim", "trip",514-    "trophy", "trouble", "truck", "true", "truly", "trumpet", "trust", "truth",515-    "try", "tube", "tuition", "tumble", "tuna", "tunnel", "turkey", "turn",516-    "turtle", "twelve", "twenty", "twice", "twin", "twist", "two", "type",517-    "typical", "ugly", "umbrella", "unable", "unaware", "uncle", "uncover", "under",518-    "undo", "unfair", "unfold", "unhappy", "uniform", "unique", "unit", "universe",519-    "unknown", "unlock", "until", "unusual", "unveil", "update", "upgrade", "uphold",520-    "upon", "upper", "upset", "urban", "urge", "usage", "use", "used",521-    "useful", "useless", "usual", "utility", "vacant", "vacuum", "vague", "valid",522-    "valley", "valve", "van", "vanish", "vapor", "various", "vast", "vault",523-    "vehicle", "velvet", "vendor", "venture", "venue", "verb", "verify", "version",524-    "very", "vessel", "veteran", "viable", "vibrant", "vicious", "victory", "video",525-    "view", "village", "vintage", "violin", "virtual", "virus", "visa", "visit",526-    "visual", "vital", "vivid", "vocal", "voice", "void", "volcano", "volume",527-    "vote", "voyage", "wage", "wagon", "wait", "walk", "wall", "walnut",528-    "want", "warfare", "warm", "warrior", "wash", "wasp", "waste", "water",529-    "wave", "way", "wealth", "weapon", "wear", "weasel", "weather", "web",530-    "wedding", "weekend", "weird", "welcome", "west", "wet", "whale", "what",531-    "wheat", "wheel", "when", "where", "whip", "whisper", "wide", "width",532-    "wife", "wild", "will", "win", "window", "wine", "wing", "wink",533-    "winner", "winter", "wire", "wisdom", "wise", "wish", "witness", "wolf",534-    "woman", "wonder", "wood", "wool", "word", "work", "world", "worry",535-    "worth", "wrap", "wreck", "wrestle", "wrist", "write", "wrong", "yard",536-    "year", "yellow", "you", "young", "youth", "zebra", "zero", "zone",537-    "zoo",538273 )

view file

app/src/main/java/com/peachpasswords/vault/RecoveryWords.kt+2 −19
@@ -35,30 +35,13 @@3535     return words3636 }3737 38-/**39- * Decode a phrase to its 16 bytes of word material.40- *41- * Tries the canonical list first, then [LEGACY_WORD_LIST] so a phrase written42- * down from a build that shipped the duplicated-"abandon" ordering still43- * recovers. Both orderings are checksum-verified, and the escrow this material44- * unwraps is authenticated, so a phrase that decodes under the wrong ordering45- * cannot silently yield the wrong vault.46- */47-fun wordsToRootKeyMaterial(words: List<String>): ByteArray =48-    runCatching { decodeWith(words, WORD_LIST) }49-        .recoverCatching { canonicalFailure ->50-            runCatching { decodeWith(words, LEGACY_WORD_LIST) }51-                .getOrElse { throw canonicalFailure }52-        }53-        .getOrThrow()54-55-private fun decodeWith(words: List<String>, list: List<String>): ByteArray {38+fun wordsToRootKeyMaterial(words: List<String>): ByteArray {5639     require(words.size == TOTAL_WORDS) { "exactly $TOTAL_WORDS words required" }5740 5841     val indices = IntArray(words.size)5942     for (i in words.indices) {6043         val word = words[i].trim().lowercase()61-        val index = list.indexOf(word)44+        val index = WORD_LIST.indexOf(word)6245         require(index >= 0) { "unknown word at position ${i + 1}: \"${words[i]}\"" }6346         indices[i] = index6447     }

view file

app/src/test/java/com/peachpasswords/vault/CrossPlatformRecoveryWordsTest.kt+7 −21
@@ -1,7 +1,6 @@11 package com.peachpasswords.vault22 33 import org.junit.Assert.assertEquals4-import org.junit.Assert.assertNotEquals54 import org.junit.Assert.assertTrue65 import org.junit.Test76 @@ -15,7 +14,12 @@1514  *1615  * The vector below is computed from the browser's canonical list1716  * (`src/shared/recovery-word-list.ts`) and is pinned identically in18- * `src/background/recovery-words.test.ts`.17+ * `src/shared/recovery-words.cross-platform.test.ts`.18+ *19+ * There is deliberately no fallback to the old ordering: a decoder that20+ * accepts a phrase under two different lists is ambiguity in the one path that21+ * must never be ambiguous. Phrases written down from a build at or before22+ * v2.4.12 are not accepted and must be re-recorded.1923  */2024 class CrossPlatformRecoveryWordsTest {2125 @@ -60,25 +64,7 @@6064         }6165     }6266 63-    @Test fun a_phrase_written_under_the_legacy_ordering_still_recovers() {64-        // What an affected phone displayed for the same root key. It must keep65-        // working: someone has this on paper.66-        val legacyWords = LEGACY_WORD_LIST.let { legacy ->67-            val key = hexToRootKey(vectorRootKey)68-            val canonical = rootKeyToWords(key)69-            // Same indices, rendered through the legacy ordering: the extra70-            // "abandon" at index 1 means legacy[i] is canonical[i - 1].71-            canonical.map { legacy[WORD_LIST.indexOf(it)] }72-        }73-        assertNotEquals(vectorWords, legacyWords)74-75-        val expected = hexToRootKey(vectorRootKey).copyOfRange(0, 16)76-        assertTrue(77-            wordsToRootKeyMaterial(legacyWords).copyOfRange(0, 16).contentEquals(expected),78-        )79-    }80-81-    @Test fun canonical_phrases_are_not_misread_as_legacy() {67+    @Test fun the_shared_vector_decodes_to_its_root_key_entropy() {8268         val expected = hexToRootKey(vectorRootKey).copyOfRange(0, 16)8369         assertTrue(8470             wordsToRootKeyMaterial(vectorWords).copyOfRange(0, 16).contentEquals(expected),

view file