dictionary_1024/
lib.rs

1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3#![deny(unused_must_use)]
4#![deny(unused_mut)]
5
6//! dictionary-1024 is a mnemonic dictionary that can be used with cryptographic seeds or to
7//! transform other binary data. The dictionary has 1024 words in it, which means you can pack
8//! exactly 10 bits of entropy into each word. The dictionary has the property that every word can
9//! be uniquely determined by its first 3 characters. The API is designed such that only the first
10//! 3 characters of a word are considered when doing a lookup in the dictionary.
11//!
12//! This is a helper library that is used in downstream crates such as seed15 and mnemonic-16bit.
13//!
14//! ```
15//! let word = dictionary_1024::word_at_index(5); // "ace"
16//! let index = dictionary_1024::index_of_word(&word); // 5
17//! ```
18
19use anyhow::{bail, Error, Result};
20
21/// DICTIONARY_UNIQUE_PREFIX defines the number of characters that are guaranteed to be unique for
22/// each word in the dictionary. The seed code only looks at these three characters when parsing a
23/// word, allowing users to make substitutions for words if they prefer or find it easier to
24/// memorize.
25///
26/// This constant is here for documentation purposes.
27pub const DICTIONARY_UNIQUE_PREFIX: usize = 3;
28
29/// DICTIONARY contains the dictionary-1024 wordlist. This const is here for documentation
30/// purposes. Use the methods for accessing the dictionary instead.
31pub const DICTIONARY: [&str; 1024] = [
32    "abbey", "able", "abort", "absorb", "abyss", "ace", "ache", "acid", "across", "acumen",
33    "adapt", "adept", "adjust", "adopt", "adult", "aerial", "afar", "affair", "afield", "afloat",
34    "afoot", "afraid", "after", "age", "agile", "aglow", "agony", "agree", "ahead", "aid", "aisle",
35    "ajar", "akin", "alarm", "album", "alert", "alley", "almost", "aloof", "alps", "also",
36    "alumni", "always", "amaze", "ambush", "amidst", "ammo", "among", "ample", "amuse", "anchor",
37    "angle", "ankle", "antic", "anvil", "apart", "apex", "aphid", "aplomb", "apply", "arch",
38    "ardent", "arena", "argue", "arise", "arm", "around", "arrow", "ascend", "aside", "ask",
39    "asleep", "aspire", "asylum", "atlas", "atom", "atrium", "attire", "auburn", "audio", "august",
40    "aunt", "auto", "avatar", "avid", "avoid", "awful", "awning", "awoken", "axe", "axis", "axle",
41    "aztec", "azure", "baby", "bacon", "bad", "bail", "bakery", "bamboo", "banjo", "basin",
42    "batch", "bawl", "bay", "beer", "befit", "begun", "behind", "being", "below", "best", "bevel",
43    "beware", "beyond", "bias", "bid", "bike", "bird", "bite", "blip", "boat", "body", "bogey",
44    "boil", "bold", "bomb", "border", "boss", "both", "bovine", "box", "broken", "brunt", "bubble",
45    "budget", "buffet", "bug", "bulb", "bum", "bun", "but", "buy", "buzz", "byline", "bypass",
46    "cabin", "cactus", "cadet", "cafe", "cage", "cajun", "cake", "camp", "candy", "case", "cat",
47    "cause", "cease", "cedar", "cell", "cement", "cent", "chrome", "cider", "cigar", "cinema",
48    "circle", "claw", "click", "clue", "coal", "cobra", "cocoa", "code", "coffee", "cog", "coil",
49    "cold", "comb", "cool", "copy", "cousin", "cowl", "cube", "cuff", "custom", "dad", "daft",
50    "dagger", "daily", "dam", "dapper", "dart", "dash", "date", "dawn", "daze", "debt", "decay",
51    "deft", "deity", "den", "depth", "desk", "devoid", "dice", "diet", "dig", "dilute", "dim",
52    "dine", "diode", "ditch", "dive", "dizzy", "doctor", "dodge", "doe", "dog", "doing", "donut",
53    "dose", "dot", "double", "dove", "down", "doze", "dream", "drink", "drunk", "dry", "dual",
54    "dubbed", "dud", "duet", "duke", "dumb", "dune", "duplex", "dust", "duty", "dwarf", "dwelt",
55    "dying", "each", "eagle", "earth", "easy", "eat", "echo", "eden", "edgy", "edit", "eel", "egg",
56    "eight", "either", "eject", "elapse", "elbow", "eldest", "eleven", "elite", "elope", "else",
57    "elude", "email", "ember", "emerge", "emit", "empty", "energy", "enigma", "enjoy", "enlist",
58    "enmity", "enough", "ensign", "envy", "epoxy", "equip", "erase", "error", "estate", "etch",
59    "ethics", "excess", "exhale", "exit", "exotic", "extra", "exult", "fade", "fake", "fall",
60    "family", "fancy", "fatal", "fault", "fawn", "fax", "faze", "feast", "fee", "felt", "fence",
61    "ferry", "fever", "few", "fiat", "fibula", "fidget", "fierce", "fight", "film", "fir", "five",
62    "fix", "fizz", "fleet", "fly", "foam", "focus", "foe", "fog", "foil", "font", "fossil", "fowl",
63    "fox", "foyer", "frame", "frown", "fruit", "fry", "fudge", "fuel", "full", "fume", "fun",
64    "future", "fuzz", "gables", "gadget", "gag", "gain", "galaxy", "game", "gang", "gasp",
65    "gather", "gauze", "gave", "gawk", "gaze", "gecko", "geek", "gel", "germ", "geyser", "ghetto",
66    "ghost", "giant", "giddy", "gift", "gill", "ginger", "girth", "give", "glass", "glide", "gnaw",
67    "gnome", "goat", "goblet", "goes", "going", "gone", "gopher", "gossip", "got", "gown", "grunt",
68    "guest", "guide", "gulp", "guru", "gust", "gutter", "guy", "gypsy", "gyrate", "hair", "having",
69    "hawk", "haze", "heel", "heft", "height", "hence", "hero", "hide", "hijack", "hike", "hill",
70    "hinder", "hip", "hire", "hive", "hoax", "hobby", "hockey", "hold", "honk", "hook", "hop",
71    "horn", "hot", "hover", "howl", "huddle", "hug", "hull", "hum", "hunt", "hut", "hybrid",
72    "hyper", "icing", "icon", "idiom", "idle", "idol", "igloo", "ignore", "iguana", "impel",
73    "incur", "injury", "inline", "inmate", "input", "insult", "invoke", "ion", "irate", "iris",
74    "iron", "island", "issue", "itch", "item", "itself", "ivory", "jab", "jade", "jagged", "jail",
75    "jargon", "jaunt", "jaw", "jazz", "jeans", "jeer", "jest", "jewel", "jigsaw", "jingle", "jive",
76    "job", "jock", "jog", "joke", "jolt", "jostle", "joy", "judge", "juicy", "july", "jump",
77    "junk", "jury", "karate", "keep", "kennel", "kept", "kettle", "king", "kiosk", "kiss", "kiwi",
78    "knee", "knife", "koala", "lad", "lag", "lair", "lake", "lamb", "lap", "large", "last",
79    "late", "lava", "lay", "lazy", "ledge", "leech", "left", "legion", "lemon", "lesson", "liar",
80    "lick", "lid", "lie", "light", "lilac", "lime", "line", "lion", "liquid", "list", "live",
81    "load", "lock", "lodge", "loft", "logic", "long", "lopped", "lost", "loud", "love", "low",
82    "loyal", "lucky", "lump", "lung", "lurk", "lush", "luxury", "lymph", "lynx", "lyrics", "macro",
83    "mail", "major", "make", "male", "mammal", "map", "mate", "maul", "mayor", "maze", "mean",
84    "memoir", "men", "merge", "mesh", "met", "mew", "mice", "midst", "mighty", "mime", "mirror",
85    "misery", "moat", "mob", "mock", "mohawk", "molten", "moment", "money", "moon", "mop",
86    "morsel", "most", "mouth", "mow", "much", "mud", "muffin", "mug", "mullet", "mumble", "muppet",
87    "mural", "muzzle", "myriad", "myth", "nag", "nail", "name", "nanny", "nap", "nasty", "navy",
88    "near", "need", "neon", "nephew", "nerve", "nest", "never", "newt", "nexus", "nibs", "niche",
89    "niece", "nifty", "nimbly", "nobody", "nod", "noise", "nomad", "note", "noun", "nozzle",
90    "nuance", "nudged", "nugget", "null", "numb", "nun", "nurse", "nylon", "oak", "oar", "oasis",
91    "object", "occur", "ocean", "odd", "off", "often", "okay", "older", "olive", "omega", "onion",
92    "online", "onto", "onward", "ooze", "open", "opus", "orange", "orb", "orchid", "order",
93    "organ", "origin", "oscar", "otter", "ouch", "ought", "ounce", "oust", "oval", "oven", "owe",
94    "owl", "own", "oxygen", "oyster", "ozone", "pact", "page", "palace", "paper", "past", "pat",
95    "pause", "peel", "peg", "pen", "people", "pepper", "pest", "petal", "phase", "phone", "piano",
96    "pick", "pierce", "pimple", "pirate", "pivot", "pixel", "pizza", "plead", "pliers", "plus",
97    "poetry", "point", "poke", "pole", "pony", "pool", "pot", "pouch", "powder", "pray", "pride",
98    "prune", "pry", "public", "puck", "puddle", "puff", "pulp", "punch", "puppy", "purge", "push",
99    "putty", "pylon", "python", "queen", "quick", "quote", "radar", "raft", "rage", "rake",
100    "rally", "ram", "rapid", "rare", "rash", "rat", "rave", "ray", "razor", "react", "rebel",
101    "recipe", "reduce", "reef", "refer", "reheat", "relic", "remedy", "repent", "rerun", "rest",
102    "return", "revamp", "rewind", "rhino", "rhyme", "rib", "rich", "ride", "rift", "rigid", "rim",
103    "riot", "rip", "rise", "ritual", "river", "roar", "robot", "rodent", "rogue", "role", "room",
104    "rope", "roster", "rotate", "rover", "royal", "ruby", "rude", "rug", "ruin", "rule", "rumble",
105    "run", "rural", "sack", "safe", "saga", "sail", "sake", "salad", "sample", "sand", "sash",
106    "satin", "save", "scenic", "school", "scoop", "scrub", "scuba", "second", "sedan", "seed",
107    "setup", "sew", "sieve", "silk", "sip", "siren", "size", "skate", "skew", "skull", "slid",
108    "slow", "slug", "smash", "smog", "snake", "sneeze", "sniff", "snout", "snug", "soap", "sob",
109    "soccer", "soda", "soggy", "soil", "solve", "sonar", "soot", "sort", "sow", "soy", "space",
110    "speed", "sphere", "spout", "sprig", "spud", "spy", "square", "stick", "subtly", "suede",
111    "sugar", "sum", "sun", "surf", "sushi", "suture", "swept", "sword", "swung", "system", "tab",
112    "tacit", "tag", "taint", "take", "talent", "tamper", "tan", "task", "tattoo", "taunt",
113    "tavern", "tawny", "taxi", "tell", "tender", "tepid", "tether", "thaw", "thorn", "thumb",
114    "thwart", "ticket", "tidy", "tier", "tiger", "tilt", "timber", "tint", "tip", "tire", "tissue",
115    "titan", "today", "toffee", "toilet", "token", "tone", "top", "torn", "toss", "total", "touch",
116    "tow", "toxic", "toy", "trash", "trend", "tribal", "truth", "try", "tube", "tuck", "tudor",
117    "tuft", "tug", "tulip", "tune", "turn", "tusk", "tutor", "tuxedo", "twang", "twice", "tycoon",
118    "type", "tyrant", "ugly", "ulcer", "umpire", "uncle", "under", "uneven", "unfit", "union",
119    "unmask", "unrest", "unsafe", "until", "unveil", "unwind", "unzip", "upbeat", "update",
120    "uphill", "upkeep", "upload", "upon", "upper", "urban", "urge", "usage", "use", "usher",
121    "using", "usual", "utmost", "utopia", "vague", "vain", "value", "vane", "vary", "vat", "vault",
122    "vector", "veer", "vegan", "vein", "velvet", "vest", "vexed", "vial", "vice", "video",
123    "viking", "violin", "viper", "vital", "vivid", "vixen", "vocal", "vogue", "voice", "vortex",
124    "vote", "vowel", "voyage", "wade", "waffle", "waist", "wake", "want", "war", "water", "wax",
125    "wedge", "weird", "went", "wept", "were", "whale", "when", "whole", "wide", "wield", "wife",
126    "wiggle", "wild", "winter", "wire", "wise", "wives", "wizard", "wobbly", "woes", "woke",
127    "wolf", "woozy", "worry", "woven", "wrap", "wrist", "wrong", "yacht", "yahoo", "yank",
128];
129
130/// word_at_index will return the word with the provided index. If the index is greater than 1023,
131/// the program will panic.
132pub fn word_at_index(i: usize) -> String {
133    if i > 1023 {
134        panic!("attempt to access index {} but dictionary-1024 only has 1024 elements", i);
135    }
136    DICTIONARY[i].to_string()
137}
138
139/// index_of_word will return the index of the provided word within the dictionary, using only the
140/// first three characters of the word to find a match. If no match is found, an error will be
141/// returned.
142pub fn index_of_word(word: &str) -> Result<usize, Error> {
143    if word.len() < 3 {
144        bail!("each word must have at least three characters");
145    }
146    let word = &word[..3];
147
148    for i in 0..1024 {
149        if &DICTIONARY[i][..3] == word {
150            return Ok(i);
151        }
152    }
153    bail!("word prefix '{}' was not found in dictionary", word);
154}
155
156/// words_match will return 'true' if the two provided words represent the same word in the
157/// dictionary, false otherwise. If the words do not appear in the dictionary at all, false will
158/// also be returned.
159pub fn words_match(a: &str, b: &str) -> bool {
160    let ai = match index_of_word(a) {
161        Ok(x) => x,
162        Err(_) => return false,
163    };
164    let bi = match index_of_word(b) {
165        Ok(x) => x,
166        Err(_) => return false,
167    };
168    ai == bi
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    #[test]
176    fn basic_tests() {
177        // Try all words without making changes.
178        for i in 0..1024 {
179            let word = word_at_index(i);
180            let index = index_of_word(&word).unwrap();
181            if index != i {
182                panic!("mismatch");
183            }
184        }
185
186        // Try all words while adding extensions.
187        for i in 0..1024 {
188            let mut word = word_at_index(i);
189            word += "b";
190            let index = index_of_word(&word).unwrap();
191            if index != i {
192                panic!("mismatch");
193            }
194        }
195
196        // Try all words while modifying the 4th character if it exists.
197        for i in 0..1024 {
198            let mut word = word_at_index(i);
199            word.truncate(3);
200            word += "a";
201            let index = index_of_word(&word).unwrap();
202            if index != i {
203                panic!("mismatch");
204            }
205        }
206
207        // Try all words truncated to just three characters.
208        for i in 0..1024 {
209            let mut word = word_at_index(i);
210            word.truncate(3);
211            let index = index_of_word(&word).unwrap();
212            if index != i {
213                panic!("mismatch");
214            }
215        }
216
217        // Check for errors.
218        index_of_word("aaron").unwrap_err();
219        index_of_word("ab").unwrap_err();
220
221        assert!(words_match("abbey", "abbot"));
222        assert!(words_match("war", "warp"));
223        assert!(words_match("wolf", "wolf"));
224        assert!(!words_match("ab", "abbey"));
225        assert!(!words_match("aaron", "aaa"));
226        assert!(!words_match("abbey", "warp"));
227        assert!(!words_match("abbey", "aaron"));
228    }
229}