diff --git a/src/main/java/swiss/qpq/gajumaru/core/encoding/Mnemonic.java b/src/main/java/swiss/qpq/gajumaru/core/encoding/Mnemonic.java new file mode 100644 index 0000000..fb5b0e2 --- /dev/null +++ b/src/main/java/swiss/qpq/gajumaru/core/encoding/Mnemonic.java @@ -0,0 +1,594 @@ +/* + * Copyright (c) 2026 QPQ AG . All rights reserved. + * Project: Gajumaru Core Java Libraries + * + * This program is dual-licensed: + * 1) Under the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version (AGPL-3.0-or-later). + * + * 2) Under a commercial/proprietary license available directly from QPQ AG. + * If you wish to use this software outside the strict constraints of the + * AGPLv3 (e.g. within a closed-source or proprietary product), you must + * purchase a commercial license from QPQ AG. + * + * Authors: + * - Craig Everett + * + * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-QPQ-Commercial + */ + +package swiss.qpq.gajumaru.core.encoding; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +// Mnemonic provides Gajumaru-style mnemonic phrase encoding and decoding. +// Port of hz_key_master.erl. +// +// Uses raw bit manipulation on byte[] to avoid BigInteger artifacts on the heap. +// Returns byte[][] for phrases to allow for explicit memory scrubbing. + +public final class Mnemonic { + + private static final int DICT_SIZE = 4096; + private static final int WIDTH = 12; // bits per word + private static final int MAX_DATA_CHUNKS = 22; // For 256-bit seed + + private static final String[] WORDS = { + "aardvark", "abacus", "abalone", "abandon", "abbey", "abdomen", "abduct", "abhor", "abide", + "ability", "able", "abnormal", "aboard", "abolish", "abort", "above", "abrasive", "abridged", + "abroad", "abrupt", "abscond", "absence", "absinthe", "absorb", "abstract", "absurd", "abundant", + "abuser", "abyss", "acacia", "academic", "accent", "accident", "acclaim", "account", "accredit", + "accuse", "acetone", "achieve", "acid", "acne", "acolyte", "acoustic", "acquire", "acreage", + "acrobat", "acrylic", "activity", "actor", "actress", "actual", "acuity", "acumen", "acute", + "adage", "adamant", "adapt", "addendum", "addict", "address", "adequate", "adhesive", "adjacent", + "adjoin", "adjust", "admiral", "admonish", "adobe", "adoption", "adorn", "adrenal", "adult", + "advance", "advert", "advisor", "advocate", "aerobic", "affair", "affirm", "afflict", "afford", + "affront", "afraid", "africa", "again", "aged", "agency", "agile", "agitate", "agnostic", "agony", + "agrarian", "agree", "ahead", "ailment", "aimless", "airbrush", "airdrop", "airfare", "airline", + "airmail", "airport", "airship", "airtight", "aisle", "ajar", "alarm", "albacore", "albino", + "album", "alchemy", "alcove", "alert", "alfalfa", "algae", "algebra", "alias", "alibi", "alien", + "alimony", "alive", "alkaline", "alley", "allied", "allocate", "allure", "almanac", "almond", + "alone", "aloof", "alpaca", "alpha", "alpine", "alto", "altruism", "aluminum", "amateur", "amaze", + "amber", "ambition", "ambrosia", "ambush", "amend", "amethyst", "amicable", "amiss", "ammonia", + "amnesia", "amoeba", "amorous", "amount", "amperage", "amplify", "amputate", "amulet", "amuse", + "anaconda", "anagram", "analysis", "anarchy", "anatomy", "ancestor", "anchor", "ancient", + "android", "anecdote", "anemic", "aneurism", "angel", "angler", "angry", "anguish", "animal", + "ankle", "annex", "announce", "annual", "anoint", "anomaly", "anorexia", "another", "answer", + "antacid", "antenna", "anthem", "antique", "antonym", "anvil", "anxiety", "aorta", "apart", + "apathy", "aperture", "apex", "aphid", "apogee", "apology", "apostle", "apparel", "appear", + "apple", "appoint", "approve", "apricot", "apron", "aptitude", "aquatic", "arachnid", "arcade", + "archery", "arctic", "ardent", "arduous", "arena", "argue", "argyle", "arise", "armada", + "armchair", "armor", "armpit", "army", "aroma", "arouse", "arrange", "arrest", "arrive", "arrow", + "arsenal", "arson", "artefact", "artist", "artwork", "asbestos", "ascend", "ascot", "ashamed", + "ashtray", "ashy", "asia", "askew", "aspect", "asphalt", "aspirin", "assassin", "asset", "assist", + "assorted", "assume", "asteroid", "asthma", "astound", "astride", "astute", "asylum", "atheism", + "athlete", "atlas", "atoll", "atom", "atrium", "atrocity", "attack", "attend", "attic", "attorney", + "attract", "auburn", "auction", "audacity", "audit", "augment", "august", "aura", "aurora", + "austere", "author", "autopsy", "autumn", "avail", "avarice", "avatar", "avenger", "average", + "aviation", "avocado", "avoid", "await", "awake", "award", "awesome", "awkward", "awning", "awry", + "axe", "axiom", "axis", "azalea", "azimuth", "azure", "baboon", "babysit", "bachelor", "backhand", + "bacon", "bacteria", "badge", "baffle", "bagel", "baggage", "bagpipe", "bailiff", "bakery", + "balance", "balcony", "balder", "ballroom", "balmy", "baloney", "bamboo", "banana", "bandage", + "bane", "bangle", "banister", "banjo", "banknote", "banner", "banquet", "banshee", "baptize", + "barbecue", "barefoot", "bargain", "baritone", "bark", "barley", "barman", "barnyard", "baroness", + "barrel", "basalt", "baseball", "bashful", "basic", "basket", "bassinet", "baste", "bathe", + "battle", "bay", "bayonet", "bazooka", "beaker", "beam", "beanbag", "beard", "beastly", "beaten", + "beauty", "because", "beckon", "become", "bedbug", "bedroom", "bedsore", "beefy", "beehive", + "beeline", "beeswax", "beetle", "befall", "before", "befuddle", "beggar", "begin", "begrudge", + "beguile", "behavior", "behead", "behind", "behold", "beige", "believe", "bellhop", "belong", + "bemuse", "bench", "benefit", "benign", "bequeath", "berate", "beret", "berserk", "beseech", + "beside", "bespoke", "best", "beta", "betray", "between", "beverage", "beware", "bewilder", + "beyond", "biased", "bible", "bicep", "bicker", "bicycle", "bidding", "bifocal", "biggest", + "bigmouth", "bigotry", "bigwig", "bike", "bikini", "billfold", "binary", "binder", "bingo", + "binomial", "biology", "bionic", "biopsy", "bipedal", "birch", "birdcage", "birthday", "biscuit", + "bisect", "bisque", "bistro", "bitter", "bizarre", "blackout", "blade", "blamed", "blanket", + "blast", "blatant", "blazer", "bleak", "bleed", "blemish", "bless", "blimp", "blind", "blister", + "blitz", "blizzard", "bloated", "blob", "blockade", "blogger", "blonde", "blooper", "blossom", + "blouse", "blowgun", "blubber", "bludgeon", "bluejay", "bluffer", "blunder", "blur", "blustery", + "boastful", "boater", "bobcat", "bobsled", "bobtail", "bodega", "bodice", "bodywork", "bogey", + "bohemian", "boil", "boldface", "bolt", "bombard", "bonanza", "bond", "boneless", "bonfire", + "bonnet", "bonsai", "bonus", "boogie", "book", "boost", "bootleg", "borax", "bordered", "boredom", + "borrow", "bosom", "boss", "bosun", "botanist", "botched", "bother", "bottom", "botulism", + "boulder", "boundary", "bouquet", "bourbon", "boutique", "bovine", "bowler", "boxer", "boycott", + "boyhood", "bracelet", "brag", "braille", "bramble", "branch", "brass", "bratty", "brave", + "brawler", "brazen", "breathe", "breeze", "brethren", "brevity", "brewery", "briar", "bribery", + "brick", "bright", "brim", "brine", "brisket", "brittle", "broach", "broccoli", "broiler", + "broken", "bronze", "broom", "browse", "bruise", "brunt", "brute", "bubble", "buckle", "buddy", + "budget", "buffet", "builder", "bulb", "bulge", "bulimia", "bulky", "bulletin", "bummed", "bumpy", + "bundle", "bungalow", "bunion", "bunker", "buoyant", "burden", "bureau", "burger", "burial", + "burlap", "burnout", "blurp", "burrito", "burst", "bushel", "business", "bustle", "busy", "butane", + "butcher", "butler", "button", "buxom", "buyer", "buyout", "buzz", "bylaw", "bypass", "cabaret", + "cabbage", "cabin", "cactus", "cadaver", "cadet", "caffeine", "caftan", "cage", "cairn", "cajole", + "calamity", "calcium", "calendar", "calico", "callous", "calm", "calorie", "calypso", "camera", + "campus", "canal", "cancel", "candy", "canine", "cannibal", "canoe", "canteen", "canvas", "canyon", + "capacity", "capital", "capsule", "capture", "caramel", "carbon", "carcass", "card", "careful", + "cargo", "caribou", "carnival", "carousel", "carry", "carsick", "cartoon", "carve", "cascade", + "cashew", "casino", "cassette", "castle", "casual", "catalog", "catcher", "category", "catnap", + "catwalk", "cauldron", "causeway", "caution", "cavalry", "caveman", "cavity", "ceiling", "celery", + "celibate", "cellmate", "cement", "censored", "center", "ceramic", "ceremony", "certain", "cesar", + "cesium", "cesspool", "chaff", "chagrin", "chair", "chalice", "champion", "change", "chaotic", + "chapter", "charity", "chase", "chat", "cheap", "checkers", "cheddar", "cheese", "chemical", + "cherry", "chestnut", "chevron", "chew", "chicken", "chief", "chiffon", "child", "chimney", + "china", "chipmunk", "chirp", "chisel", "chive", "chlorine", "choice", "choke", "cholera", "chomp", + "choppy", "chorus", "chowder", "chronic", "chubby", "chuckle", "chug", "chummy", "chunk", "churn", + "chutney", "cicada", "cider", "cigar", "cilantro", "cinema", "cinnamon", "cipher", "circle", + "cistern", "citadel", "citizen", "citrus", "city", "civil", "claim", "clammy", "clang", "clarify", + "class", "clatter", "clavicle", "clay", "cleanup", "cleft", "clemency", "clench", "clerk", + "clever", "client", "cliff", "climate", "clinic", "clipped", "cloaked", "clock", "clog", + "cloister", "closet", "clothing", "cloudy", "clove", "clown", "clubfoot", "clueless", "clump", + "clunky", "cluster", "clutch", "coach", "coastal", "coated", "cobalt", "cobbler", "cobra", + "cobweb", "coccyx", "cocktail", "coconut", "code", "coerce", "coffee", "cognac", "coherent", + "cohort", "coiled", "coin", "colander", "colder", "coleslaw", "coliseum", "collect", "color", + "column", "comatose", "combine", "comedy", "comfort", "comic", "common", "company", "comrade", + "concert", "conduct", "confirm", "congress", "conical", "conjoin", "connect", "conquer", "consume", + "control", "convince", "cookbook", "cool", "copper", "copy", "corduroy", "corner", "coronary", + "corporal", "correct", "corset", "cortex", "cosmetic", "cosplay", "costume", "cotton", "cougar", + "counter", "coupon", "courier", "cousin", "cover", "cowardly", "cowboy", "cowlick", "coyote", + "crabby", "crackle", "cradle", "craft", "cram", "crane", "crater", "craving", "crawl", "crayon", + "crazy", "creamy", "credit", "creep", "cremate", "crescent", "crevice", "cricket", "criminal", + "cringe", "crisis", "critical", "croak", "crochet", "crooked", "crop", "croquet", "crossbow", + "crouch", "crowd", "crucial", "cruel", "cruiser", "crumble", "crunch", "crush", "crux", "cryptic", + "crystal", "cube", "cuckoo", "cucumber", "cuddle", "cudgel", "cuff", "cuisine", "culinary", + "culprit", "cultural", "culvert", "cumin", "cumulus", "cunning", "cupboard", "cupcake", "cupid", + "curator", "curb", "curfew", "curled", "currency", "cursive", "curtsy", "curvy", "cushion", "cuss", + "custody", "cutback", "cutest", "cuticle", "cutlery", "cutout", "cycle", "cylinder", "cynic", + "cypress", "cyst", "dabble", "daffodil", "dainty", "daiquiri", "daisy", "damage", "damsel", + "dance", "dandruff", "danger", "dapper", "darkness", "darling", "dart", "dash", "database", + "dateline", "daughter", "daunting", "dawdle", "daybreak", "daydream", "daylight", "dazed", + "dazzle", "deadline", "dealer", "dean", "deathbed", "debate", "debrief", "debtor", "debut", + "decade", "deceased", "decision", "deck", "declare", "decorate", "decrease", "dedicate", "deduct", + "deed", "deepest", "deface", "defense", "define", "deflate", "deformed", "defraud", "deft", + "defuse", "degree", "deity", "dejected", "delay", "delegate", "deliver", "delta", "delusion", + "delve", "demand", "demeanor", "demise", "democrat", "demure", "denial", "denounce", "density", + "dentist", "deny", "depart", "depend", "depict", "deploy", "deposit", "depress", "depth", "deputy", + "derail", "derby", "derelict", "derive", "describe", "deserter", "desire", "desktop", "desolate", + "despair", "destroy", "detach", "detect", "detour", "devalue", "develop", "device", "devote", + "dewdrop", "diabetic", "diagnose", "dialogue", "diamond", "diaper", "diary", "diatribe", "dicey", + "dictate", "diesel", "diet", "differ", "digest", "digital", "dignity", "digress", "dilemma", + "diligent", "dilute", "diminish", "dimmer", "dimpled", "dingy", "dinner", "dinosaur", "diorama", + "diploma", "direct", "dirty", "disabled", "disburse", "disco", "disdain", "disease", "disguise", + "dishevel", "dismal", "dispense", "disrupt", "dissuade", "distance", "dive", "divide", "divorce", + "divulge", "dizzy", "docility", "dockyard", "doctor", "document", "dodge", "dodo", "dogged", + "doghouse", "dogmatic", "doldrums", "doll", "dolphin", "domain", "domestic", "dominant", "donate", + "donkey", "doomsday", "door", "dorky", "dorm", "dorsal", "dosage", "dossier", "dotted", "doubt", + "doughnut", "downtown", "dowry", "dozen", "draftee", "dragon", "drainage", "dramatic", "drapery", + "drastic", "draw", "dream", "dredge", "dress", "dribble", "dried", "drift", "drink", "driveway", + "drizzle", "drool", "droplet", "drought", "drove", "drowsy", "drudgery", "drug", "druid", + "drummer", "drywall", "dubious", "duckling", "dugout", "duke", "dumbbell", "dumpster", "dungeon", + "duo", "duplex", "duration", "dust", "dutchess", "dutiful", "duty", "duvet", "dwarf", "dwell", + "dwindle", "dynamic", "dyslexia", "eager", "eagle", "eardrum", "earl", "earmark", "earner", + "earphone", "earring", "earshot", "earth", "earwig", "easel", "eastward", "easy", "eatery", "ebb", + "ebony", "echo", "eclectic", "eclipse", "ecology", "economy", "ecstasy", "edge", "edible", + "edifice", "editor", "educate", "eel", "eery", "effigy", "effort", "eggnog", "eggplant", + "eggshell", "ego", "elapse", "elastic", "elated", "elbow", "elder", "election", "elegance", + "element", "elephant", "elevator", "eligible", "elite", "elixir", "ellipsis", "elm", "elongate", + "elope", "eloquent", "elude", "elusive", "emaciate", "email", "emanate", "embark", "embezzle", + "emblem", "embody", "embrace", "emerald", "emigrant", "eminent", "emission", "emoji", "emotion", + "empathy", "emperor", "emphasis", "employer", "empower", "empty", "emu", "emulate", "enamel", + "enchant", "enclose", "encoder", "encrypt", "encumber", "endeavor", "endless", "endorse", "endure", + "enemy", "energize", "engage", "engine", "engraver", "engulf", "enhance", "enigma", "enjoy", + "enlist", "enmity", "enormity", "enraged", "enrich", "enroll", "ensemble", "ensnare", "entangle", + "enthrone", "entire", "entrance", "entwine", "envelope", "envision", "envy", "enzyme", "epic", + "epidemic", "epigram", "epilepsy", "episode", "epitaph", "epoch", "epoxy", "equation", "equinox", + "eraser", "erect", "erode", "errand", "error", "erupt", "escape", "escort", "escrow", "esoteric", + "espresso", "essay", "essence", "estate", "esteem", "estimate", "estrange", "estuary", "eternal", + "ethereal", "ethical", "ethnic", "eulogy", "euphoric", "eureka", "euro", "evade", "evaluate", + "evasion", "event", "evict", "evidence", "evil", "evoke", "evolve", "exact", "exalted", "example", + "excavate", "excerpt", "exchange", "excite", "exclude", "excrete", "excuse", "execute", "exempt", + "exercise", "exhaust", "exhibit", "exhume", "exile", "exist", "exodus", "exorcist", "exotic", + "expand", "expert", "expire", "explain", "expose", "express", "extend", "extinct", "extort", + "extra", "eyeball", "eyeglass", "eyelash", "fabled", "fabric", "fabulous", "facade", "facelift", + "facility", "fact", "faculty", "faded", "failure", "fainter", "fairy", "faith", "fake", "falcon", + "fallout", "false", "famished", "famous", "fanatic", "fanboy", "fancy", "fanfare", "fang", + "fantasy", "farewell", "farmer", "farther", "fashion", "fasten", "fatal", "fathom", "fatigue", + "fatty", "faucet", "fault", "favorite", "fax", "fealty", "fearless", "feast", "feature", "federal", + "fedora", "fee", "feeble", "feedback", "feeler", "feign", "feisty", "feline", "felon", "feminine", + "femur", "fence", "feral", "fern", "ferocity", "ferret", "fertile", "fervent", "festival", "fetch", + "fetid", "feud", "fever", "fiasco", "fiber", "fiction", "fiddler", "fidelity", "fidget", + "fiendish", "fiery", "fiesta", "figure", "filament", "filch", "filet", "filigree", "filling", + "filter", "finance", "fine", "finger", "finish", "firewood", "firm", "first", "fiscal", "fishery", + "fissure", "fitful", "fixate", "fizz", "fjord", "flabby", "flag", "flail", "flaky", "flame", + "flannel", "flapjack", "flash", "flatten", "flaunt", "flavor", "flawless", "fleece", "fleshy", + "flexible", "flick", "flight", "flimsy", "fling", "flip", "flirt", "float", "floor", "floppy", + "floral", "floss", "flotsam", "flourish", "flower", "fluent", "fluff", "fluid", "flummox", "flunk", + "fluoride", "flurry", "flusher", "flute", "flux", "flypaper", "flywheel", "foam", "focus", + "fodder", "fog", "fold", "foliage", "folklore", "follow", "fondue", "font", "foolish", "football", + "forager", "forbid", "force", "forecast", "forfeit", "forget", "forklift", "forlorn", "formal", + "forsake", "fortune", "forum", "forward", "fossil", "fought", "foul", "founder", "foxhound", + "foxtrot", "foxy", "foyer", "fraction", "fragment", "frailty", "frantic", "fraught", "freaky", + "freckled", "freeway", "freight", "frenzy", "frequent", "freshman", "fret", "fridge", "friend", + "frighten", "fringed", "frisky", "fritter", "frizzy", "frock", "frolic", "frontier", "frost", + "froth", "frown", "frozen", "fructose", "frugal", "fruit", "fugitive", "fulcrum", "fulfill", + "fullback", "fumigate", "fund", "funeral", "fungus", "funny", "furious", "furl", "furnace", + "furrow", "furthest", "fuselage", "fusion", "fussy", "futile", "futon", "future", "fuzzy", + "gadget", "galaxy", "gallery", "gambler", "game", "gangster", "gap", "garage", "garden", "gargle", + "garish", "garlic", "garment", "garnet", "garrison", "garter", "gaseous", "gaslight", "gasoline", + "gastric", "gatepost", "gather", "gaudy", "gauge", "gauntlet", "gavel", "gawk", "gazette", + "gearbox", "gecko", "geeky", "geezer", "gelatin", "gemstone", "general", "genius", "genre", + "gentle", "genuine", "geology", "geometry", "gerbil", "germ", "gesture", "getaway", "geyser", + "ghastly", "ghetto", "ghost", "ghoul", "giddy", "gifted", "gigantic", "giggle", "gilded", + "gimmick", "giraffe", "girdle", "girlish", "girth", "gist", "gizmo", "gizzard", "glacier", "glad", + "glamour", "glance", "glare", "glassy", "glaucoma", "glazed", "gleam", "gleeful", "glen", "glib", + "glide", "glimpse", "glint", "glitter", "globe", "gloom", "glory", "glossary", "gloved", "glow", + "glucose", "glue", "gluttony", "glycerin", "glyph", "gnarled", "gnash", "gnaw", "gnome", "goalie", + "goatee", "goblet", "goddess", "goldfish", "golfer", "gondola", "good", "gooey", "goose", "gopher", + "gorge", "gorilla", "gosling", "gospel", "gossip", "gothic", "gourmet", "govern", "gown", + "grabber", "gracious", "graduate", "graffiti", "grainy", "grammar", "grant", "grape", "grasp", + "grateful", "gravity", "gray", "greasy", "green", "gremlin", "grew", "gridlock", "grief", "grill", + "grimace", "grin", "gristle", "gritty", "grizzly", "grocery", "groggy", "grommet", "groove", + "gross", "grotto", "grout", "grovel", "grownup", "grub", "gruesome", "gruff", "grumpy", "grungy", + "gryphon", "guard", "guava", "guess", "guidance", "guilty", "guitar", "gullible", "gulp", "gumbo", + "gumdrop", "gumption", "gunsmith", "gurney", "guru", "gusty", "gut", "guttural", "gym", "gymnast", + "gypsy", "gyration", "gyro", "habit", "hacksaw", "haggler", "haiku", "haircut", "halberd", "half", + "halibut", "hallway", "halogen", "halter", "hamlet", "hammer", "hamster", "handrail", "hangover", + "happy", "harass", "harbor", "hardwood", "harmonic", "harness", "harpoon", "harsh", "harvest", + "hashtag", "hassle", "hatchet", "hateful", "hatred", "hauler", "haunch", "havoc", "haystack", + "haywire", "hazard", "hazelnut", "hazmat", "headset", "health", "hearsay", "heat", "heavy", + "heckler", "hectic", "hedgehog", "hedonism", "heedful", "hegemony", "height", "heinous", + "heirloom", "heliport", "hello", "helmet", "helpful", "hemlock", "hen", "henchman", "henna", + "herald", "herbal", "heresy", "heritage", "hernia", "heron", "herring", "hesitate", "hexagon", + "hiatus", "hibiscus", "hiccup", "hickory", "hidden", "hideaway", "highway", "hijacker", "hiker", + "hilarity", "hill", "hinge", "hint", "hip", "hippo", "history", "hither", "hoagie", "hoarder", + "hoax", "hobby", "hobo", "hockey", "hoedown", "hoggish", "hoist", "holiday", "holler", "hologram", + "holster", "holy", "homage", "home", "homicide", "homonym", "honeydew", "honk", "honor", "hoodie", + "hookworm", "hooligan", "hoop", "hooray", "hopeful", "horizon", "hormone", "hornet", "horror", + "horseman", "hospital", "hostess", "hotel", "hourly", "housing", "howdy", "howitzer", "hub", + "hubcap", "hubris", "huge", "hula", "human", "humble", "humdrum", "humidity", "hummus", "humpback", + "hungry", "hunter", "hurdle", "hurry", "hurt", "husband", "hush", "husky", "hustler", "hyacinth", + "hybrid", "hydrate", "hyena", "hygiene", "hyper", "hyphen", "hypnosis", "hysteria", "iceberg", + "icicle", "icon", "icy", "idea", "identify", "ideology", "idiot", "idler", "idolize", "idyllic", + "igloo", "ignite", "ignore", "iguana", "illicit", "illusion", "imagery", "imbibe", "imbue", + "imitate", "immense", "immolate", "immune", "impasse", "impede", "implode", "impostor", "impress", + "impunity", "inbox", "incense", "inch", "incision", "include", "income", "incubate", "index", + "indicate", "industry", "inept", "inertia", "infant", "inferno", "infinity", "inflict", "info", + "infrared", "ingest", "ingot", "inhale", "inherit", "inhibit", "initial", "inject", "injure", + "inkwell", "inlet", "inmate", "innocent", "innuendo", "input", "inquiry", "insane", "insert", + "insignia", "insomnia", "inspect", "instruct", "insult", "intact", "interest", "intimate", + "intruder", "invasion", "investor", "invite", "invoke", "iodine", "ionize", "iris", "irksome", + "ironwork", "irritate", "island", "isolate", "isotope", "issue", "itchy", "item", "iterate", + "ivory", "jabber", "jackal", "jade", "jagged", "jaguar", "jailer", "jamboree", "janitor", "jargon", + "jasmine", "jaundice", "javelin", "jaw", "jawbone", "jaywalk", "jazz", "jealousy", "jeans", + "jeopardy", "jerky", "jersey", "jester", "jet", "jettison", "jewelry", "jiffy", "jigsaw", "jinx", + "jittery", "jock", "jogger", "joint", "joke", "jolly", "jostle", "journey", "joust", "joy", + "joyride", "joystick", "jubilant", "judge", "judicial", "judo", "jug", "juggler", "juicy", + "jujitsu", "jukebox", "jump", "junction", "junior", "junk", "juror", "jury", "justify", "juvenile", + "kabob", "kale", "kangaroo", "karaoke", "karma", "kayak", "kazoo", "keen", "keepsake", "keg", + "kennel", "keratin", "kerchief", "kerosene", "kestrel", "ketchup", "keyboard", "keyhole", + "keynote", "keystone", "keyword", "khaki", "kick", "kidder", "kidney", "killjoy", "kilogram", + "kilt", "kimono", "kind", "kinetic", "kinfolk", "kingdom", "kinsman", "kiosk", "kiss", "kitchen", + "kite", "kitten", "kiwi", "klutzy", "knapsack", "knee", "knife", "knitted", "knockout", "know", + "knuckle", "koala", "kumquat", "lab", "label", "labor", "lacerate", "lackey", "lacquer", + "lacrosse", "lactose", "ladder", "ladybug", "laggard", "lagoon", "lair", "lament", "laminate", + "lamp", "lancer", "landlord", "language", "lanky", "lantern", "lanyard", "laptop", "larceny", + "large", "larvae", "larynx", "lasagna", "lasso", "latex", "latitude", "latrine", "lattice", + "laugh", "laundry", "laureate", "lava", "lavender", "lavish", "lawful", "lawn", "lawyer", + "laxative", "layaway", "layout", "lazy", "leader", "leaflet", "league", "leaky", "leap", "learn", + "leash", "leathery", "lecture", "ledger", "leeway", "lefty", "legacy", "legend", "legible", + "legume", "legwork", "leisure", "lemming", "lemonade", "lend", "length", "leniency", "lentil", + "leopard", "leprosy", "lesion", "lesser", "lethargy", "letter", "level", "levitate", "levy", + "lexicon", "liaison", "liberty", "library", "license", "lichen", "lieu", "lifespan", "lift", + "ligament", "lighter", "likable", "lilac", "limbo", "lime", "limit", "linchpin", "linen", + "linoleum", "linseed", "lion", "lipid", "lipstick", "liquid", "lisp", "listen", "literary", + "lithium", "litigate", "little", "livery", "lizard", "llama", "loaded", "loaf", "loaner", "loathe", + "lobbyist", "lobotomy", "lobster", "location", "lockjaw", "locust", "logic", "logo", "loiterer", + "lollipop", "lonesome", "long", "lookout", "loopy", "looter", "lopsided", "lordship", "loser", + "lotion", "lottery", "lotus", "loud", "lounge", "lousy", "lovely", "lowland", "loyalty", "lozenge", + "lucid", "luck", "luggage", "lukewarm", "lullaby", "lumber", "luminous", "lunar", "lunch", "lure", + "luxury", "lychee", "lymph", "lyric", "macaroni", "machine", "macro", "mad", "maestro", "magazine", + "magenta", "maggot", "magic", "magma", "magnet", "mahogany", "maiden", "mailman", "maimed", + "maintain", "majestic", "majority", "makeup", "malaria", "malice", "mallard", "malted", "malware", + "mammal", "manager", "mandolin", "maneuver", "mango", "manhole", "manicure", "mankind", "manly", + "manpower", "mansion", "mantra", "manual", "maple", "marathon", "marble", "march", "mare", + "margin", "marine", "market", "marmot", "maroon", "marriage", "marshal", "martini", "marvel", + "mascara", "mashup", "masked", "mason", "massive", "master", "matador", "matchbox", "material", + "math", "matrix", "mattress", "maturity", "maverick", "maximum", "mayhem", "mayor", "meadow", + "meander", "measure", "meatball", "mechanic", "medalist", "medical", "medley", "meeting", + "megabyte", "melanoma", "mellow", "melody", "melt", "member", "meme", "memory", "menace", "mental", + "menu", "meow", "merchant", "merge", "merit", "mermaid", "mesh", "message", "metallic", "meteor", + "method", "metric", "miasma", "microbe", "midair", "midday", "midnight", "midpoint", "midriff", + "midst", "midterm", "midwife", "midyear", "mighty", "migraine", "mild", "mileage", "military", + "milkman", "million", "mimic", "mimosa", "minced", "mindful", "mineral", "minister", "minnow", + "minstrel", "minty", "minute", "miracle", "mirror", "mischief", "misery", "misfit", "mishap", + "missile", "mistake", "mitosis", "mitt", "mixture", "mnemonic", "mobile", "moccasin", "mocha", + "mockery", "modern", "modify", "module", "mogul", "moisture", "molasses", "moldy", "molecule", + "mollusk", "molten", "moment", "momma", "monarch", "money", "mongoose", "monitor", "monocle", + "monster", "month", "monument", "moocher", "moon", "mop", "moped", "moral", "morgue", "morning", + "morocco", "morphine", "morsel", "mortgage", "mosaic", "mosquito", "mossy", "motherly", "motivate", + "motley", "motor", "motto", "mountain", "mourn", "mouse", "mouthful", "move", "mucky", "mucus", + "muffin", "mugger", "mulch", "mullet", "multiply", "mummify", "mundane", "murmur", "muscle", + "museum", "mushroom", "musical", "musket", "mustang", "mutant", "mute", "mutilate", "mutter", + "mutual", "myopia", "myriad", "mystery", "mythical", "nacho", "nail", "naive", "naked", "namesake", + "nanny", "napkin", "narcotic", "narrator", "narwhal", "nasal", "nasty", "national", "nature", + "naughty", "nausea", "nautical", "navigate", "nearest", "nebula", "necklace", "nectar", "needle", + "negative", "neglect", "neighbor", "nemesis", "neon", "neoprene", "nephew", "nepotism", "nerd", + "nerve", "nestle", "network", "neuron", "neutral", "newborn", "newlywed", "newscast", "newton", + "next", "nexus", "niche", "nickel", "nicotine", "niece", "nifty", "nightcap", "nihilist", "nimble", + "ninja", "nippy", "nirvana", "nitpick", "nitrogen", "nitwit", "noble", "nobody", "nocturne", + "noise", "nomad", "nominee", "nonsense", "noodle", "normalcy", "north", "nosedive", "nostril", + "nosy", "notary", "notch", "notepad", "notice", "noun", "nourish", "novel", "noxious", "nozzle", + "nuance", "nuclear", "nugget", "nuisance", "nullify", "numb", "numeral", "nunnery", "nurse", + "nurture", "nutmeg", "nutrient", "nutshell", "nutty", "nylon", "oak", "oasis", "oatmeal", + "obedient", "obelisk", "obese", "obey", "obituary", "object", "oblige", "oblong", "oboe", + "obscure", "observe", "obsidian", "obsolete", "obstacle", "obtain", "obtuse", "obvious", + "occasion", "occupant", "ocean", "ocelot", "octave", "octopus", "ocular", "oddity", "odometer", + "odyssey", "offer", "offhand", "office", "offload", "offshore", "often", "ogre", "oilskin", + "ointment", "okay", "oligarch", "omelet", "omit", "omnivore", "oncoming", "onion", "online", + "onset", "onward", "onyx", "ooze", "opaque", "opera", "opinion", "oppose", "oppress", "option", + "opulent", "oracle", "orange", "orbit", "orchard", "order", "ordinary", "oregano", "organize", + "original", "ornament", "orphan", "orthodox", "osmosis", "ostrich", "otter", "ottoman", "ounce", + "outage", "outburst", "outcome", "outdoor", "outfit", "outgrow", "outhouse", "outlaw", "output", + "outrun", "outside", "outtake", "outward", "oval", "oven", "overall", "owl", "owner", "oxford", + "oxygen", "oxymoron", "oyster", "ozone", "pacific", "package", "paddle", "padlock", "pageant", + "pagoda", "painful", "pajamas", "palpable", "palsy", "paltry", "pamphlet", "panacea", "pancake", + "panda", "panel", "panic", "panorama", "panther", "papaya", "paper", "paprika", "papyrus", + "parade", "parcel", "pardon", "parental", "pariah", "parka", "parlor", "parody", "parrot", + "parsley", "particle", "passport", "pasta", "patchy", "patent", "pathway", "patio", "patrol", + "pattern", "pave", "pavilion", "pawnshop", "payday", "payload", "peaceful", "peanut", "pearly", + "peasant", "pebble", "pecan", "pectoral", "peculiar", "pedantic", "peddler", "pedestal", + "pedicure", "peerless", "pelican", "pellet", "penalty", "pencil", "pendant", "penguin", "penny", + "pensive", "pentagon", "pepper", "percent", "perfect", "period", "perjury", "perk", "permit", + "perplex", "person", "perturb", "peruse", "perverse", "pesky", "petal", "petition", "petrify", + "petunia", "pewter", "phalanx", "phantom", "pharmacy", "phlegm", "phobia", "phoenix", "phonetic", + "photo", "phrase", "physical", "piano", "pickup", "picnic", "picture", "pierce", "piety", "pigeon", + "piglet", "pigment", "pile", "pilfer", "pilgrim", "pillow", "pinball", "pincer", "pinhole", "pink", + "pinnacle", "pinpoint", "pinto", "pioneer", "pious", "pirate", "piston", "pitch", "pitfall", + "pitiful", "pitted", "pivot", "pixel", "pixie", "pizza", "placard", "plan", "plaque", "plasma", + "platform", "playlist", "plaza", "plead", "pledge", "plenty", "plethora", "pliable", "plot", + "plumage", "plunge", "plural", "plushy", "plywood", "poacher", "pockmark", "podcast", "podiatry", + "poem", "poignant", "pointy", "poison", "poker", "polarize", "polished", "polka", "pollen", "polo", + "polygon", "pomade", "pompom", "poncho", "pontoon", "ponytail", "pooch", "popcorn", "popular", + "porous", "porridge", "portion", "positive", "possible", "post", "potato", "potency", "pothole", + "potluck", "poultry", "poverty", "powder", "powerful", "pox", "practice", "prairie", "praline", + "prance", "prawn", "prayer", "preach", "precise", "predator", "prefer", "preheat", "prelude", + "premium", "prepare", "prequel", "pressure", "pretty", "prevent", "price", "priest", "primary", + "print", "priority", "prisoner", "privacy", "problem", "process", "produce", "profile", "program", + "prohibit", "project", "prologue", "promise", "pronoun", "proof", "property", "prorate", + "prospect", "protest", "proud", "provide", "prowl", "proxy", "prudish", "prune", "psalm", "pseudo", + "psychic", "puberty", "public", "pucker", "pudding", "pudgy", "puffy", "pugilist", "pulley", + "pulpit", "pulse", "puma", "pummel", "pumpkin", "puncture", "pundit", "pungent", "punish", "puny", + "pupil", "puppy", "purchase", "puree", "purity", "purple", "pursuit", "purveyor", "pushup", + "putrid", "putt", "puzzle", "pyramid", "pyre", "python", "quadrant", "quagmire", "quail", "quake", + "quality", "quantity", "quarter", "quasar", "queasy", "queen", "quell", "quench", "query", "quest", + "quick", "quiet", "quilted", "quirky", "quitter", "quiver", "quiz", "quote", "rabbit", "rabies", + "raccoon", "race", "radar", "radio", "radon", "rafter", "raggedy", "ragtag", "raider", "railroad", + "rainbow", "raisin", "rally", "rampage", "ramrod", "rancher", "random", "ransack", "rapid", + "rarity", "rascal", "raspy", "rat", "ration", "raunchy", "ravage", "raven", "ravioli", "razor", + "reaction", "ready", "realize", "reaper", "rebel", "rebound", "rebuttal", "receiver", "recharge", + "recipe", "reckless", "recliner", "recorder", "recruit", "rectify", "recycle", "redeemer", + "redhead", "redneck", "reduce", "redwood", "reef", "referee", "refinery", "reflect", "reform", + "refrain", "refugee", "regalia", "regency", "reggae", "region", "regret", "regular", "rehab", + "rehearse", "reject", "rejoice", "relaxed", "relevant", "relic", "reload", "remark", "remedy", + "reminder", "remnant", "removal", "render", "renegade", "renovate", "rent", "repair", "repeater", + "replica", "report", "reprisal", "reptile", "repute", "requiem", "rescue", "resemble", "resident", + "resonate", "response", "restroom", "result", "retailer", "retina", "retrieve", "reunion", + "reveler", "revive", "revolt", "reward", "rhapsody", "rhetoric", "rhino", "rhodium", "rhombus", + "rhubarb", "rhythm", "rib", "ribbon", "rich", "rickshaw", "ricochet", "riddle", "ride", "ridicule", + "riff", "rifle", "rightful", "rigid", "ringtone", "rinse", "riot", "ripple", "risk", "ritual", + "ritzy", "rival", "river", "roadwork", "roar", "roast", "robbery", "robe", "robin", "robot", + "robust", "rocky", "rodeo", "roguish", "romantic", "romp", "roofing", "rookie", "roommate", + "rosemary", "roster", "rotate", "rotten", "rouge", "roulette", "round", "routine", "rowboat", + "royal", "rubber", "rubric", "rucksack", "rudder", "rueful", "ruffian", "rugby", "rugged", "ruin", + "ruler", "ruminate", "rummage", "rumor", "rumple", "runner", "runoff", "runway", "rupture", + "rural", "rusted", "ruthless", "saber", "sabotage", "sadistic", "sadness", "safari", "safe", + "saffron", "saga", "said", "sailor", "saint", "salary", "salesman", "saliva", "salon", "salsa", + "salt", "salute", "salvage", "sampler", "samurai", "sanctum", "sandwich", "sanguine", "sanitize", + "sapling", "sapphire", "sarcasm", "sardine", "sassy", "satchel", "satisfy", "saturate", "sauce", + "sauna", "savanna", "savior", "savory", "savvy", "sawdust", "sawhorse", "sawmill", "scab", + "scaffold", "scale", "scamper", "scandal", "scapula", "scared", "scatter", "scavenge", "scenery", + "scepter", "scheme", "schism", "schnapps", "scholar", "science", "scimitar", "scissor", "scoff", + "scold", "scooter", "scope", "scorpion", "scotch", "scout", "scowl", "scramble", "screen", + "script", "scroll", "scrub", "scuba", "scuffed", "sculpt", "scumbag", "scurry", "scuttle", + "scythe", "seabird", "seafood", "sealant", "seamless", "seaplane", "search", "season", "seaweed", + "secluded", "second", "secret", "section", "security", "sedan", "sediment", "seek", "seepage", + "seesaw", "seethe", "segment", "seismic", "seizure", "seldom", "select", "self", "sellout", + "seltzer", "semantic", "semester", "seminar", "senator", "senior", "sensory", "sentence", "sepia", + "sepsis", "sequence", "serenade", "serfdom", "sergeant", "serious", "serpent", "serrated", "serum", + "service", "sesame", "session", "setback", "settle", "setup", "severity", "sewage", "sewing", + "sextant", "shabby", "shackle", "shadow", "shaft", "shaggy", "shaky", "shallow", "shame", "sharp", + "shawl", "sheathe", "sheet", "shelter", "shepherd", "sheriff", "shield", "shifty", "shimmy", + "shinbone", "shipyard", "shiver", "shock", "shoelace", "shop", "short", "shotgun", "shoulder", + "shove", "showoff", "shrapnel", "shred", "shrine", "shroud", "shrug", "shudder", "shuffle", + "shutdown", "sibling", "sickle", "sidewalk", "siege", "sierra", "signal", "silent", "silicone", + "silk", "silly", "silver", "similar", "simple", "simulate", "since", "sinew", "single", "sinkhole", + "sinus", "siphon", "siren", "sirloin", "sister", "sitcom", "size", "sizzle", "skate", "skeleton", + "skeptic", "sketch", "skewer", "skier", "skillet", "skimmed", "skin", "skirt", "skittish", "skull", + "skunk", "skydive", "skyline", "skyward", "slacker", "slalom", "slammer", "slant", "slather", + "sleaze", "sled", "sleepy", "slender", "sleuth", "slice", "slim", "slinky", "slipper", "slobber", + "slogan", "sloped", "sloth", "slouch", "sluggish", "slurp", "slush", "smart", "smash", "smear", + "smell", "smile", "smith", "smoke", "smolder", "smooth", "smother", "smudge", "smug", "snack", + "snag", "snake", "snapshot", "snarl", "snazzy", "sneaker", "sneeze", "snicker", "snide", "sniff", + "snip", "snobbish", "snooze", "snore", "snot", "snowball", "snuggle", "soaked", "soap", "sob", + "soccer", "society", "socket", "soda", "sodium", "soft", "soggy", "solar", "soldier", "solemn", + "solid", "soloist", "solstice", "solution", "solve", "sombrero", "somebody", "sonata", "songbird", + "sonic", "sooner", "soothe", "soprano", "sorbet", "sorcerer", "sorority", "sortie", "soulmate", + "source", "south", "souvenir", "soybean", "space", "spandex", "spark", "spasm", "spatula", "spawn", + "speak", "special", "speech", "spend", "spew", "sphere", "sphinx", "spicy", "spider", "spiffy", + "spigot", "spill", "spine", "spirit", "spit", "splash", "spleen", "splint", "splotchy", "splurge", + "spoil", "sponsor", "spoon", "sporty", "spotter", "spouse", "spray", "spreader", "sprinkle", + "sprout", "spruce", "spud", "spunky", "spurn", "spy", "spyglass", "square", "squeeze", "squirrel", + "sriracha", "stable", "staccato", "stadium", "stage", "stairway", "stalker", "stamp", "standard", + "stapler", "starve", "station", "staunch", "stay", "steady", "steer", "stellar", "stencil", + "stereo", "steward", "stick", "stifle", "stigma", "stilt", "stimulus", "stingray", "stipend", + "stir", "stockade", "stoic", "stolen", "stomach", "stone", "stool", "stopper", "storm", "stow", + "strategy", "street", "strike", "strong", "struggle", "stub", "stucco", "student", "stuff", + "stumble", "stunt", "stupor", "sturgeon", "stutter", "stylus", "stymie", "suave", "subdue", + "subject", "sublime", "submit", "subplot", "subsidy", "subtitle", "suburbia", "subvert", "subway", + "success", "sudden", "sudsy", "suffer", "sugar", "suggest", "suitable", "sulfur", "sullen", + "sultan", "summer", "sumo", "sunburn", "sundial", "sunken", "sunlight", "sunroof", "sunset", + "superior", "support", "supreme", "surface", "surgery", "surmount", "surname", "surprise", + "surround", "survive", "sushi", "suspect", "sustain", "swaddle", "swagger", "swampy", "swan", + "swarm", "swath", "sweater", "sweeper", "swerve", "swift", "swimmer", "swindler", "swipe", + "switch", "swivel", "swollen", "swoop", "sworn", "sycamore", "syllabus", "symbolic", "symmetry", + "sympathy", "synapse", "sync", "syndrome", "synergy", "synopsis", "syntax", "syringe", "syrup", + "system", "tablet", "taboo", "tacit", "tackle", "taco", "tactile", "tadpole", "taffy", "tag", + "tailpipe", "takeout", "talent", "talisman", "tall", "tamper", "tandem", "tangy", "tankard", + "tanned", "tantrum", "tapestry", "tapioca", "tardy", "target", "tariff", "tarmac", "tarnish", + "tarp", "tarrier", "tartar", "task", "tassel", "tasteful", "tattoo", "taunt", "tavern", "taxation", + "taxi", "teacher", "teal", "teamwork", "teapot", "teardrop", "teaser", "techno", "tedium", + "teenager", "teeth", "telecast", "teller", "temple", "tenant", "tendency", "tennis", "tenor", + "tension", "tentacle", "tenure", "tepid", "tequila", "terminal", "terrain", "terse", "tertiary", + "testify", "tetanus", "tether", "texture", "thank", "thatch", "thaw", "theater", "theft", "theme", + "theory", "therapy", "thesis", "thick", "thigh", "thimble", "thinner", "thirst", "thistle", + "thorn", "thought", "thrall", "threat", "thrive", "throaty", "thrum", "thud", "thumb", "thunder", + "thwart", "thyroid", "tiara", "tibia", "ticket", "tidal", "tidbit", "tidy", "tiger", "tight", + "timber", "timeline", "timid", "tinfoil", "tinker", "tinsel", "tinted", "tipsy", "tiptoe", + "tirade", "tired", "titanium", "title", "toad", "toaster", "tobacco", "toboggan", "today", + "toddler", "toenail", "tofu", "together", "toggle", "toilet", "token", "tolerate", "tomato", + "tomb", "tomcat", "tomorrow", "tonality", "toned", "tongs", "tonight", "tonnage", "tonsil", + "toolbox", "tooth", "topaz", "topology", "topple", "topsoil", "torch", "torment", "tornado", + "torpedo", "torque", "torrid", "torso", "torture", "torus", "total", "tote", "toucan", "tourist", + "toward", "tower", "township", "toxic", "track", "trader", "traffic", "tragic", "train", + "trample", "transfer", "trapeze", "trash", "trauma", "traveler", "treaty", "trek", "tremble", + "trend", "trespass", "trial", "tribe", "tricycle", "trident", "trigger", "trilogy", "trinket", + "trip", "triumph", "trivia", "trod", "troll", "trooper", "tropic", "trouble", "truant", "trucker", + "trudge", "truffle", "trumpet", "trunk", "trust", "truth", "try", "tsunami", "tuba", "tubby", + "tubular", "tuft", "tugboat", "tuition", "tulip", "tumbler", "tummy", "tumult", "tundra", "tuner", + "tungsten", "tunic", "tunnel", "turbine", "turf", "turkey", "turmoil", "turnip", "turret", + "turtle", "tussle", "tutor", "tutu", "tuxedo", "tweak", "tweet", "twerp", "twice", "twiddle", + "twilight", "twin", "twirl", "twist", "tycoon", "type", "typhoon", "typical", "tyrant", "ubiquity", + "ukulele", "ulcer", "ulterior", "ultimate", "ultra", "umbrella", "umpire", "uncle", "uncouth", + "under", "undo", "undulate", "unicycle", "uniform", "unique", "unisex", "unite", "universe", + "unkempt", "until", "unwieldy", "upbeat", "upchuck", "upcoming", "updraft", "upfront", "upgrade", + "upheaval", "uphill", "upkeep", "uplift", "upload", "upper", "upright", "uproar", "upscale", + "upset", "upstream", "uptake", "uptown", "upward", "uranium", "urban", "urchin", "urge", "useful", + "useless", "username", "usher", "usual", "usurper", "utensil", "utility", "utmost", "utopia", + "uvula", "vacant", "vaccine", "vacuum", "vagabond", "vagrant", "vague", "valet", "valid", + "valuable", "valve", "vampire", "vandal", "vanguard", "vanish", "vanquish", "vapor", "variety", + "varsity", "vascular", "vassal", "vast", "vector", "veer", "vegan", "veggie", "vehement", + "vehicle", "velocity", "velvet", "vendor", "veneer", "vengeful", "venison", "venom", "venture", + "venue", "veranda", "verb", "verdict", "verify", "vermin", "version", "vertebra", "vessel", + "vestige", "veteran", "veto", "viable", "viaduct", "vibrant", "vicinity", "victim", "video", + "view", "vigilant", "vigorous", "village", "vinegar", "vintage", "vinyl", "violence", "virtual", + "virus", "visa", "visceral", "visitor", "visor", "visual", "vital", "vitriol", "vivid", "vocal", + "voice", "volatile", "volcano", "volition", "volley", "voltage", "volume", "voodoo", "voter", + "voucher", "vowel", "voyage", "vulture", "wacky", "wafer", "waft", "waggle", "wagon", "waitress", + "waiver", "walkway", "wallet", "walnut", "walrus", "waltz", "wanderer", "wardrobe", "warhorse", + "warlord", "warmth", "warped", "warrior", "warthog", "washroom", "wasp", "wasted", "watch", + "waterbed", "wavy", "waxed", "wayward", "weaken", "wealthy", "weapon", "weary", "weather", + "weaver", "webbed", "webcam", "website", "wedding", "weedy", "weekend", "weep", "weird", "welcome", + "welfare", "western", "wetland", "whack", "whaler", "wharf", "wheeze", "whelp", "whiff", "whim", + "whinny", "whiplash", "whirl", "whisper", "white", "wicked", "widow", "wife", "wig", "wildfire", + "willowy", "wimpy", "windpipe", "winery", "wingtip", "winter", "wiper", "wireless", "wiry", + "wisdom", "wish", "wistful", "withdraw", "witness", "witty", "wizardry", "wobble", "woe", "wolf", + "woman", "wombat", "wonder", "woodwork", "woolen", "woozy", "work", "world", "wormhole", "worry", + "worship", "worthy", "wounded", "wraith", "wrangle", "wrapper", "wreath", "wreckage", "wrench", + "wrestle", "wretched", "wriggle", "wrinkle", "wrist", "written", "wrong", "xenon", "yacht", "yam", + "yank", "yarn", "year", "yelp", "yeoman", "yes", "yeti", "yield", "yodel", "yoga", "yogurt", + "yolk", "young", "yuletide", "yuppie", "zany", "zealot", "zebra", "zenith", "zeppelin", "zero", + "zesty", "zinc", "zipper", "zodiac", "zombie", "zoom", "zucchini", "zygote" + }; + + private static final Map WORD_MAP = new HashMap<>(DICT_SIZE); + static { + for (int i = 0; i < WORDS.length; i++) { + WORD_MAP.put(WORDS[i], i); + } + } + + private Mnemonic() {} + + public static byte[][] encode(byte[] seed) { + if (seed.length != 32) { + throw new IllegalArgumentException("Seed must be 32 bytes"); + } + + int[] allChunks = new int[MAX_DATA_CHUNKS]; + for (int i = 0; i < MAX_DATA_CHUNKS; i++) { + allChunks[i] = read12Bits(seed, i); + } + + // Find the first non-zero data chunk (leading zero suppression) + int firstNonZero = -1; + for (int i = 0; i < MAX_DATA_CHUNKS; i++) { + if (allChunks[i] != 0) { + firstNonZero = i; + break; + } + } + + int numDataChunks = (firstNonZero == -1) ? 0 : (MAX_DATA_CHUNKS - firstNonZero); + int checksum = 0; + for (int c : allChunks) { + checksum ^= c; + } + + byte[][] phrase = new byte[numDataChunks + 1][]; + phrase[0] = WORDS[checksum].getBytes(StandardCharsets.UTF_8); + for (int i = 0; i < numDataChunks; i++) { + phrase[i + 1] = WORDS[allChunks[firstNonZero + i]].getBytes(StandardCharsets.UTF_8); + } + return phrase; + } + + public static byte[] decode(byte[][] phrase) { + if (phrase.length < 1) { + throw new IllegalArgumentException("Phrase too short"); + } + + int[] chunks = new int[phrase.length]; + for (int i = 0; i < phrase.length; i++) { + String word = new String(phrase[i], StandardCharsets.UTF_8); + Integer idx = WORD_MAP.get(word); + if (idx == null) { + throw new IllegalArgumentException("Unknown word in mnemonic at index " + i); + } + chunks[i] = idx; + } + + int expectedChecksum = chunks[0]; + int actualChecksum = 0; + for (int i = 1; i < chunks.length; i++) { + actualChecksum ^= chunks[i]; + } + + if (expectedChecksum != actualChecksum) { + throw new RuntimeException("Mnemonic checksum failed"); + } + + int numDataChunks = chunks.length - 1; + byte[] result = new byte[32]; + for (int i = 0; i < numDataChunks; i++) { + int chunkIdx = MAX_DATA_CHUNKS - numDataChunks + i; + write12Bits(result, chunkIdx, chunks[i + 1]); + } + return result; + } + + + private static int read12Bits(byte[] seed, int chunkIdx) { + int bitStart = chunkIdx * WIDTH; + int val = 0; + for (int i = 0; i < WIDTH; i++) { + int pos = bitStart + i; + if (pos >= 8) { // Skip 8-bit leading zero padding + int seedBitPos = pos - 8; + int byteIdx = seedBitPos / 8; + int bitInByte = 7 - (seedBitPos % 8); // Big-endian bit order + if (((seed[byteIdx] & 0xFF) >> bitInByte & 1) == 1) { + val |= (1 << (11 - i)); + } + } + } + return val; + } + + private static void write12Bits(byte[] seed, int chunkIdx, int val) { + int bitStart = chunkIdx * WIDTH; + for (int i = 0; i < WIDTH; i++) { + int pos = bitStart + i; + if (pos >= 8) { + int seedBitPos = pos - 8; + int byteIdx = seedBitPos / 8; + int bitInByte = 7 - (seedBitPos % 8); + if (((val >> (11 - i)) & 1) == 1) { + seed[byteIdx] |= (byte) (1 << bitInByte); + } else { + seed[byteIdx] &= (byte) ~(1 << bitInByte); + } + } + } + } +} diff --git a/test/Testinator.java b/test/Testinator.java index 1fb9c4e..e774487 100644 --- a/test/Testinator.java +++ b/test/Testinator.java @@ -17,6 +17,7 @@ import swiss.qpq.gajumaru.core.crypto.Ed25519; import swiss.qpq.gajumaru.core.data.Id; import swiss.qpq.gajumaru.core.data.SpendTx; import swiss.qpq.gajumaru.core.data.SignedTx; +import swiss.qpq.gajumaru.core.encoding.Mnemonic; import swiss.qpq.gajumaru.core.tools.CryptoUtils; public class Testinator { @@ -43,6 +44,7 @@ public class Testinator { case "id_serialization" -> { System.out.print(id_serialization(args[1])); } case "spend_tx" -> { System.out.print(spend_tx(args[1])); } case "signed_tx" -> { System.out.print(signed_tx(args[1])); } + case "mnemonic" -> { System.out.print(mnemonic(args[1])); } case "fe_parity" -> { System.out.print(fe_parity(args[1])); } case "reduce_parity" -> { System.out.print(reduce_parity(args[1])); } case "smb_parity" -> { System.out.print(smb_parity(args[1])); } @@ -460,4 +462,35 @@ public class Testinator { Files.write(resPath, results); return resPath.toString(); } + + private static String mnemonic(String workingPath) throws IOException { + Path testPath = Path.of(workingPath, "mnemonic.test"); + Path resPath = Path.of(workingPath, "mnemonic.java.txt"); + List lines = Files.readAllLines(testPath); + List results = new ArrayList<>(); + for (String hex : lines) { + if (hex.trim().isEmpty()) continue; + byte[] seed = CryptoUtils.hexToBin(hex); + byte[][] phrase = Mnemonic.encode(seed); + + // Join words with space + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < phrase.length; i++) { + if (i > 0) sb.append(" "); + sb.append(new String(phrase[i], java.nio.charset.StandardCharsets.UTF_8)); + } + results.add(sb.toString()); + + // Roundtrip test + byte[] decoded = Mnemonic.decode(phrase); + if (!java.util.Arrays.equals(seed, decoded)) { + throw new RuntimeException("Mnemonic roundtrip failed in Java!"); + } + + CryptoUtils.wipe(seed); + for (byte[] word : phrase) CryptoUtils.wipe(word); + } + Files.write(resPath, results); + return resPath.toString(); + } } diff --git a/test/src/gmt.erl b/test/src/gmt.erl index 3da79f9..fe5b0fb 100644 --- a/test/src/gmt.erl +++ b/test/src/gmt.erl @@ -33,6 +33,7 @@ mods() -> "id_serialization" => fun id_serialization/0, "spend_tx" => fun spend_tx/0, "signed_tx" => fun signed_tx/0, + "mnemonic" => fun mnemonic/0, "fe_parity" => fun fe_parity/0, "reduce_parity" => fun reduce_parity/0, "smb_parity" => fun smb_parity/0, @@ -420,6 +421,20 @@ serialize_signed_tx(Fields) -> SigsHex ++ "|" ++ TxHex. +mnemonic() -> + Temp = temp_dir(), + TestFile = filename:join(Temp, "mnemonic.test"), + ok = filelib:ensure_dir(TestFile), + Cases = [rand:bytes(32) || _ <- lists:seq(1, 20)], + ok = file:write_file(TestFile, [[bin_to_hex(S), "\n"] || S <- Cases]), + Expected = [unicode:characters_to_list(hz_key_master:encode(S)) || S <- Cases], + Run = "bin/run mnemonic " ++ Temp, + Out = trim(os:cmd(Run)), + {ok, JOutContent} = file:read_file(trim(Out)), + JResults = string:split(trim(unicode:characters_to_list(JOutContent)), "\n", all), + same_same(Expected, JResults). + + fe_parity() -> Temp = temp_dir(), TestFile = filename:join(Temp, "fe.test"),