mnemonicode_inline.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. """
  2. Pure-Python implementation of mnemonicode encoding/decoding.
  3. Mnemonicode encodes arbitrary bytes as a sequence of human-readable words.
  4. Each group of 4 bytes maps to 3 words; shorter trailing groups use fewer words.
  5. The 24-bit trailing case (3 bytes) uses a distinct set of 7 short words to
  6. signal that a remainder word ends the group.
  7. Word list: version 0.7, 1626 base words + 7 remainder words = 1633 total.
  8. """
  9. import sys
  10. from functools import lru_cache
  11. MN_BASE = 1626
  12. _WORDLIST: tuple[list[str], str] = (
  13. [
  14. 'academy', 'acrobat', 'active', 'actor', 'adam', 'admiral', 'adrian',
  15. 'africa', 'agenda', 'agent', 'airline', 'airport', 'aladdin', 'alarm',
  16. 'alaska', 'albert', 'albino', 'album', 'alcohol', 'alex', 'algebra',
  17. 'alibi', 'alice', 'alien', 'alpha', 'alpine', 'amadeus', 'amanda',
  18. 'amazon', 'amber', 'america', 'amigo', 'analog', 'anatomy', 'angel',
  19. 'animal', 'antenna', 'antonio', 'apollo', 'april', 'archive', 'arctic',
  20. 'arizona', 'arnold', 'aroma', 'arthur', 'artist', 'asia', 'aspect',
  21. 'aspirin', 'athena', 'athlete', 'atlas', 'audio', 'august', 'austria',
  22. 'axiom', 'aztec', 'balance', 'ballad', 'banana', 'bandit', 'banjo',
  23. 'barcode', 'baron', 'basic', 'battery', 'belgium', 'berlin', 'bermuda',
  24. 'bernard', 'bikini', 'binary', 'bingo', 'biology', 'block', 'blonde',
  25. 'bonus', 'boris', 'boston', 'boxer', 'brandy', 'bravo', 'brazil',
  26. 'bronze', 'brown', 'bruce', 'bruno', 'burger', 'burma', 'cabinet',
  27. 'cactus', 'cafe', 'cairo', 'cake', 'calypso', 'camel', 'camera', 'campus',
  28. 'canada', 'canal', 'cannon', 'canoe', 'cantina', 'canvas', 'canyon',
  29. 'capital', 'caramel', 'caravan', 'carbon', 'cargo', 'carlo', 'carol',
  30. 'carpet', 'cartel', 'casino', 'castle', 'castro', 'catalog', 'caviar',
  31. 'cecilia', 'cement', 'center', 'century', 'ceramic', 'chamber', 'chance',
  32. 'change', 'chaos', 'charlie', 'charm', 'charter', 'chef', 'chemist',
  33. 'cherry', 'chess', 'chicago', 'chicken', 'chief', 'china', 'cigar',
  34. 'cinema', 'circus', 'citizen', 'city', 'clara', 'classic', 'claudia',
  35. 'clean', 'client', 'climax', 'clinic', 'clock', 'club', 'cobra',
  36. 'coconut', 'cola', 'collect', 'colombo', 'colony', 'color', 'combat',
  37. 'comedy', 'comet', 'command', 'compact', 'company', 'complex', 'concept',
  38. 'concert', 'connect', 'consul', 'contact', 'context', 'contour',
  39. 'control', 'convert', 'copy', 'corner', 'corona', 'correct', 'cosmos',
  40. 'couple', 'courage', 'cowboy', 'craft', 'crash', 'credit', 'cricket',
  41. 'critic', 'crown', 'crystal', 'cuba', 'culture', 'dallas', 'dance',
  42. 'daniel', 'david', 'decade', 'decimal', 'deliver', 'delta', 'deluxe',
  43. 'demand', 'demo', 'denmark', 'derby', 'design', 'detect', 'develop',
  44. 'diagram', 'dialog', 'diamond', 'diana', 'diego', 'diesel', 'diet',
  45. 'digital', 'dilemma', 'diploma', 'direct', 'disco', 'disney', 'distant',
  46. 'doctor', 'dollar', 'dominic', 'domino', 'donald', 'dragon', 'drama',
  47. 'dublin', 'duet', 'dynamic', 'east', 'ecology', 'economy', 'edgar',
  48. 'egypt', 'elastic', 'elegant', 'element', 'elite', 'elvis', 'email',
  49. 'energy', 'engine', 'english', 'episode', 'equator', 'escort', 'ethnic',
  50. 'europe', 'everest', 'evident', 'exact', 'example', 'exit', 'exotic',
  51. 'export', 'express', 'extra', 'fabric', 'factor', 'falcon', 'family',
  52. 'fantasy', 'fashion', 'fiber', 'fiction', 'fidel', 'fiesta', 'figure',
  53. 'film', 'filter', 'final', 'finance', 'finish', 'finland', 'flash',
  54. 'florida', 'flower', 'fluid', 'flute', 'focus', 'ford', 'forest',
  55. 'formal', 'format', 'formula', 'fortune', 'forum', 'fragile', 'france',
  56. 'frank', 'friend', 'frozen', 'future', 'gabriel', 'galaxy', 'gallery',
  57. 'gamma', 'garage', 'garden', 'garlic', 'gemini', 'general', 'genetic',
  58. 'genius', 'germany', 'global', 'gloria', 'golf', 'gondola', 'gong',
  59. 'good', 'gordon', 'gorilla', 'grand', 'granite', 'graph', 'green',
  60. 'group', 'guide', 'guitar', 'guru', 'hand', 'happy', 'harbor', 'harmony',
  61. 'harvard', 'havana', 'hawaii', 'helena', 'hello', 'henry', 'hilton',
  62. 'history', 'horizon', 'hotel', 'human', 'humor', 'icon', 'idea', 'igloo',
  63. 'igor', 'image', 'impact', 'import', 'index', 'india', 'indigo', 'input',
  64. 'insect', 'instant', 'iris', 'italian', 'jacket', 'jacob', 'jaguar',
  65. 'janet', 'japan', 'jargon', 'jazz', 'jeep', 'john', 'joker', 'jordan',
  66. 'jumbo', 'june', 'jungle', 'junior', 'jupiter', 'karate', 'karma',
  67. 'kayak', 'kermit', 'kilo', 'king', 'koala', 'korea', 'labor', 'lady',
  68. 'lagoon', 'laptop', 'laser', 'latin', 'lava', 'lecture', 'left', 'legal',
  69. 'lemon', 'level', 'lexicon', 'liberal', 'libra', 'limbo', 'limit',
  70. 'linda', 'linear', 'lion', 'liquid', 'liter', 'little', 'llama', 'lobby',
  71. 'lobster', 'local', 'logic', 'logo', 'lola', 'london', 'lotus', 'lucas',
  72. 'lunar', 'machine', 'macro', 'madam', 'madonna', 'madrid', 'maestro',
  73. 'magic', 'magnet', 'magnum', 'major', 'mama', 'mambo', 'manager', 'mango',
  74. 'manila', 'marco', 'marina', 'market', 'mars', 'martin', 'marvin',
  75. 'master', 'matrix', 'maximum', 'media', 'medical', 'mega', 'melody',
  76. 'melon', 'memo', 'mental', 'mentor', 'menu', 'mercury', 'message',
  77. 'metal', 'meteor', 'meter', 'method', 'metro', 'mexico', 'miami', 'micro',
  78. 'million', 'mineral', 'minimum', 'minus', 'minute', 'miracle', 'mirage',
  79. 'miranda', 'mister', 'mixer', 'mobile', 'model', 'modem', 'modern',
  80. 'modular', 'moment', 'monaco', 'monica', 'monitor', 'mono', 'monster',
  81. 'montana', 'morgan', 'motel', 'motif', 'motor', 'mozart', 'multi',
  82. 'museum', 'music', 'mustang', 'natural', 'neon', 'nepal', 'neptune',
  83. 'nerve', 'neutral', 'nevada', 'news', 'ninja', 'nirvana', 'normal',
  84. 'nova', 'novel', 'nuclear', 'numeric', 'nylon', 'oasis', 'object',
  85. 'observe', 'ocean', 'octopus', 'olivia', 'olympic', 'omega', 'opera',
  86. 'optic', 'optimal', 'orange', 'orbit', 'organic', 'orient', 'origin',
  87. 'orlando', 'oscar', 'oxford', 'oxygen', 'ozone', 'pablo', 'pacific',
  88. 'pagoda', 'palace', 'pamela', 'panama', 'panda', 'panel', 'panic',
  89. 'paradox', 'pardon', 'paris', 'parker', 'parking', 'parody', 'partner',
  90. 'passage', 'passive', 'pasta', 'pastel', 'patent', 'patriot', 'patrol',
  91. 'patron', 'pegasus', 'pelican', 'penguin', 'pepper', 'percent', 'perfect',
  92. 'perfume', 'period', 'permit', 'person', 'peru', 'phone', 'photo',
  93. 'piano', 'picasso', 'picnic', 'picture', 'pigment', 'pilgrim', 'pilot',
  94. 'pirate', 'pixel', 'pizza', 'planet', 'plasma', 'plaster', 'plastic',
  95. 'plaza', 'pocket', 'poem', 'poetic', 'poker', 'polaris', 'police',
  96. 'politic', 'polo', 'polygon', 'pony', 'popcorn', 'popular', 'postage',
  97. 'postal', 'precise', 'prefix', 'premium', 'present', 'price', 'prince',
  98. 'printer', 'prism', 'private', 'product', 'profile', 'program', 'project',
  99. 'protect', 'proton', 'public', 'pulse', 'puma', 'pyramid', 'queen',
  100. 'radar', 'radio', 'random', 'rapid', 'rebel', 'record', 'recycle',
  101. 'reflex', 'reform', 'regard', 'regular', 'relax', 'report', 'reptile',
  102. 'reverse', 'ricardo', 'ringo', 'ritual', 'robert', 'robot', 'rocket',
  103. 'rodeo', 'romeo', 'royal', 'russian', 'safari', 'salad', 'salami',
  104. 'salmon', 'salon', 'salute', 'samba', 'sandra', 'santana', 'sardine',
  105. 'school', 'screen', 'script', 'second', 'secret', 'section', 'segment',
  106. 'select', 'seminar', 'senator', 'senior', 'sensor', 'serial', 'service',
  107. 'sheriff', 'shock', 'sierra', 'signal', 'silicon', 'silver', 'similar',
  108. 'simon', 'single', 'siren', 'slogan', 'social', 'soda', 'solar', 'solid',
  109. 'solo', 'sonic', 'soviet', 'special', 'speed', 'spiral', 'spirit',
  110. 'sport', 'static', 'station', 'status', 'stereo', 'stone', 'stop',
  111. 'street', 'strong', 'student', 'studio', 'style', 'subject', 'sultan',
  112. 'super', 'susan', 'sushi', 'suzuki', 'switch', 'symbol', 'system',
  113. 'tactic', 'tahiti', 'talent', 'tango', 'tarzan', 'taxi', 'telex', 'tempo',
  114. 'tennis', 'texas', 'textile', 'theory', 'thermos', 'tiger', 'titanic',
  115. 'tokyo', 'tomato', 'topic', 'tornado', 'toronto', 'torpedo', 'total',
  116. 'totem', 'tourist', 'tractor', 'traffic', 'transit', 'trapeze', 'travel',
  117. 'tribal', 'trick', 'trident', 'trilogy', 'tripod', 'tropic', 'trumpet',
  118. 'tulip', 'tuna', 'turbo', 'twist', 'ultra', 'uniform', 'union', 'uranium',
  119. 'vacuum', 'valid', 'vampire', 'vanilla', 'vatican', 'velvet', 'ventura',
  120. 'venus', 'vertigo', 'veteran', 'victor', 'video', 'vienna', 'viking',
  121. 'village', 'vincent', 'violet', 'violin', 'virtual', 'virus', 'visa',
  122. 'vision', 'visitor', 'visual', 'vitamin', 'viva', 'vocal', 'vodka',
  123. 'volcano', 'voltage', 'volume', 'voyage', 'water', 'weekend', 'welcome',
  124. 'western', 'window', 'winter', 'wizard', 'wolf', 'world', 'xray',
  125. 'yankee', 'yoga', 'yogurt', 'yoyo', 'zebra', 'zero', 'zigzag', 'zipper',
  126. 'zodiac', 'zoom', 'abraham', 'action', 'address', 'alabama', 'alfred',
  127. 'almond', 'ammonia', 'analyze', 'annual', 'answer', 'apple', 'arena',
  128. 'armada', 'arsenal', 'atlanta', 'atomic', 'avenue', 'average', 'bagel',
  129. 'baker', 'ballet', 'bambino', 'bamboo', 'barbara', 'basket', 'bazaar',
  130. 'benefit', 'bicycle', 'bishop', 'blitz', 'bonjour', 'bottle', 'bridge',
  131. 'british', 'brother', 'brush', 'budget', 'cabaret', 'cadet', 'candle',
  132. 'capitan', 'capsule', 'career', 'cartoon', 'channel', 'chapter', 'cheese',
  133. 'circle', 'cobalt', 'cockpit', 'college', 'compass', 'comrade', 'condor',
  134. 'crimson', 'cyclone', 'darwin', 'declare', 'degree', 'delete', 'delphi',
  135. 'denver', 'desert', 'divide', 'dolby', 'domain', 'domingo', 'double',
  136. 'drink', 'driver', 'eagle', 'earth', 'echo', 'eclipse', 'editor',
  137. 'educate', 'edward', 'effect', 'electra', 'emerald', 'emotion', 'empire',
  138. 'empty', 'escape', 'eternal', 'evening', 'exhibit', 'expand', 'explore',
  139. 'extreme', 'ferrari', 'first', 'flag', 'folio', 'forget', 'forward',
  140. 'freedom', 'fresh', 'friday', 'fuji', 'galileo', 'garcia', 'genesis',
  141. 'gold', 'gravity', 'habitat', 'hamlet', 'harlem', 'helium', 'holiday',
  142. 'house', 'hunter', 'ibiza', 'iceberg', 'imagine', 'infant', 'isotope',
  143. 'jackson', 'jamaica', 'jasmine', 'java', 'jessica', 'judo', 'kitchen',
  144. 'lazarus', 'letter', 'license', 'lithium', 'loyal', 'lucky', 'magenta',
  145. 'mailbox', 'manual', 'marble', 'mary', 'maxwell', 'mayor', 'milk',
  146. 'monarch', 'monday', 'money', 'morning', 'mother', 'mystery', 'native',
  147. 'nectar', 'nelson', 'network', 'next', 'nikita', 'nobel', 'nobody',
  148. 'nominal', 'norway', 'nothing', 'number', 'october', 'office', 'oliver',
  149. 'opinion', 'option', 'order', 'outside', 'package', 'pancake', 'pandora',
  150. 'panther', 'papa', 'patient', 'pattern', 'pedro', 'pencil', 'people',
  151. 'phantom', 'philips', 'pioneer', 'pluto', 'podium', 'portal', 'potato',
  152. 'prize', 'process', 'protein', 'proxy', 'pump', 'pupil', 'python',
  153. 'quality', 'quarter', 'quiet', 'rabbit', 'radical', 'radius', 'rainbow',
  154. 'ralph', 'ramirez', 'ravioli', 'raymond', 'respect', 'respond', 'result',
  155. 'resume', 'retro', 'richard', 'right', 'risk', 'river', 'roger', 'roman',
  156. 'rondo', 'sabrina', 'salary', 'salsa', 'sample', 'samuel', 'saturn',
  157. 'savage', 'scarlet', 'scoop', 'scorpio', 'scratch', 'scroll', 'sector',
  158. 'serpent', 'shadow', 'shampoo', 'sharon', 'sharp', 'short', 'shrink',
  159. 'silence', 'silk', 'simple', 'slang', 'smart', 'smoke', 'snake',
  160. 'society', 'sonar', 'sonata', 'soprano', 'source', 'sparta', 'sphere',
  161. 'spider', 'sponsor', 'spring', 'acid', 'adios', 'agatha', 'alamo',
  162. 'alert', 'almanac', 'aloha', 'andrea', 'anita', 'arcade', 'aurora',
  163. 'avalon', 'baby', 'baggage', 'balloon', 'bank', 'basil', 'begin',
  164. 'biscuit', 'blue', 'bombay', 'brain', 'brenda', 'brigade', 'cable',
  165. 'carmen', 'cello', 'celtic', 'chariot', 'chrome', 'citrus', 'civil',
  166. 'cloud', 'common', 'compare', 'cool', 'copper', 'coral', 'crater',
  167. 'cubic', 'cupid', 'cycle', 'depend', 'door', 'dream', 'dynasty', 'edison',
  168. 'edition', 'enigma', 'equal', 'eric', 'event', 'evita', 'exodus',
  169. 'extend', 'famous', 'farmer', 'food', 'fossil', 'frog', 'fruit', 'geneva',
  170. 'gentle', 'george', 'giant', 'gilbert', 'gossip', 'gram', 'greek',
  171. 'grille', 'hammer', 'harvest', 'hazard', 'heaven', 'herbert', 'heroic',
  172. 'hexagon', 'husband', 'immune', 'inca', 'inch', 'initial', 'isabel',
  173. 'ivory', 'jason', 'jerome', 'joel', 'joshua', 'journal', 'judge',
  174. 'juliet', 'jump', 'justice', 'kimono', 'kinetic', 'leonid', 'lima',
  175. 'maze', 'medusa', 'member', 'memphis', 'michael', 'miguel', 'milan',
  176. 'mile', 'miller', 'mimic', 'mimosa', 'mission', 'monkey', 'moral',
  177. 'moses', 'mouse', 'nancy', 'natasha', 'nebula', 'nickel', 'nina', 'noise',
  178. 'orchid', 'oregano', 'origami', 'orinoco', 'orion', 'othello', 'paper',
  179. 'paprika', 'prelude', 'prepare', 'pretend', 'profit', 'promise',
  180. 'provide', 'puzzle', 'remote', 'repair', 'reply', 'rival', 'riviera',
  181. 'robin', 'rose', 'rover', 'rudolf', 'saga', 'sahara', 'scholar',
  182. 'shelter', 'ship', 'shoe', 'sigma', 'sister', 'sleep', 'smile', 'spain',
  183. 'spark', 'split', 'spray', 'square', 'stadium', 'star', 'storm', 'story',
  184. 'strange', 'stretch', 'stuart', 'subway', 'sugar', 'sulfur', 'summer',
  185. 'survive', 'sweet', 'swim', 'table', 'taboo', 'target', 'teacher',
  186. 'telecom', 'temple', 'tibet', 'ticket', 'tina', 'today', 'toga', 'tommy',
  187. 'tower', 'trivial', 'tunnel', 'turtle', 'twin', 'uncle', 'unicorn',
  188. 'unique', 'update', 'valery', 'vega', 'version', 'voodoo', 'warning',
  189. 'william', 'wonder', 'year', 'yellow', 'young', 'absent', 'absorb',
  190. 'accent', 'alfonso', 'alias', 'ambient', 'andy', 'anvil', 'appear',
  191. 'apropos', 'archer', 'ariel', 'armor', 'arrow', 'austin', 'avatar',
  192. 'axis', 'baboon', 'bahama', 'bali', 'balsa', 'bazooka', 'beach', 'beast',
  193. 'beatles', 'beauty', 'before', 'benny', 'betty', 'between', 'beyond',
  194. 'billy', 'bison', 'blast', 'bless', 'bogart', 'bonanza', 'book', 'border',
  195. 'brave', 'bread', 'break', 'broken', 'bucket', 'buenos', 'buffalo',
  196. 'bundle', 'button', 'buzzer', 'byte', 'caesar', 'camilla', 'canary',
  197. 'candid', 'carrot', 'cave', 'chant', 'child', 'choice', 'chris', 'cipher',
  198. 'clarion', 'clark', 'clever', 'cliff', 'clone', 'conan', 'conduct',
  199. 'congo', 'content', 'costume', 'cotton', 'cover', 'crack', 'current',
  200. 'danube', 'data', 'decide', 'desire', 'detail', 'dexter', 'dinner',
  201. 'dispute', 'donor', 'druid', 'drum', 'easy', 'eddie', 'enjoy', 'enrico',
  202. 'epoxy', 'erosion', 'except', 'exile', 'explain', 'fame', 'fast',
  203. 'father', 'felix', 'field', 'fiona', 'fire', 'fish', 'flame', 'flex',
  204. 'flipper', 'float', 'flood', 'floor', 'forbid', 'forever', 'fractal',
  205. 'frame', 'freddie', 'front', 'fuel', 'gallop', 'game', 'garbo', 'gate',
  206. 'gibson', 'ginger', 'giraffe', 'gizmo', 'glass', 'goblin', 'gopher',
  207. 'grace', 'gray', 'gregory', 'grid', 'griffin', 'ground', 'guest',
  208. 'gustav', 'gyro', 'hair', 'halt', 'harris', 'heart', 'heavy', 'herman',
  209. 'hippie', 'hobby', 'honey', 'hope', 'horse', 'hostel', 'hydro', 'imitate',
  210. 'info', 'ingrid', 'inside', 'invent', 'invest', 'invite', 'iron', 'ivan',
  211. 'james', 'jester', 'jimmy', 'join', 'joseph', 'juice', 'julius', 'july',
  212. 'justin', 'kansas', 'karl', 'kevin', 'kiwi', 'ladder', 'lake', 'laura',
  213. 'learn', 'legacy', 'legend', 'lesson', 'life', 'light', 'list', 'locate',
  214. 'lopez', 'lorenzo', 'love', 'lunch', 'malta', 'mammal', 'margo', 'marion',
  215. 'mask', 'match', 'mayday', 'meaning', 'mercy', 'middle', 'mike', 'mirror',
  216. 'modest', 'morph', 'morris', 'nadia', 'nato', 'navy', 'needle', 'neuron',
  217. 'never', 'newton', 'nice', 'night', 'nissan', 'nitro', 'nixon', 'north',
  218. 'oberon', 'octavia', 'ohio', 'olga', 'open', 'opus', 'orca', 'oval',
  219. 'owner', 'page', 'paint', 'palma', 'parade', 'parent', 'parole', 'paul',
  220. 'peace', 'pearl', 'perform', 'phoenix', 'phrase', 'pierre', 'pinball',
  221. 'place', 'plate', 'plato', 'plume', 'pogo', 'point', 'polite', 'polka',
  222. 'poncho', 'powder', 'prague', 'press', 'presto', 'pretty', 'prime',
  223. 'promo', 'quasi', 'quest', 'quick', 'quiz', 'quota', 'race', 'rachel',
  224. 'raja', 'ranger', 'region', 'remark', 'rent', 'reward', 'rhino', 'ribbon',
  225. 'rider', 'road', 'rodent', 'round', 'rubber', 'ruby', 'rufus', 'sabine',
  226. 'saddle', 'sailor', 'saint', 'salt', 'satire', 'scale', 'scuba', 'season',
  227. 'secure', 'shake', 'shallow', 'shannon', 'shave', 'shelf', 'sherman',
  228. 'shine', 'shirt', 'side', 'sinatra', 'sincere', 'size', 'slalom', 'slow',
  229. 'small', 'snow', 'sofia', 'song', 'sound', 'south', 'speech', 'spell',
  230. 'spend', 'spoon', 'stage', 'stamp', 'stand', 'state', 'stella', 'stick',
  231. 'sting', 'stock', 'store', 'sunday', 'sunset', 'support', 'sweden',
  232. 'swing', 'tape', 'think', 'thomas', 'tictac', 'time', 'toast', 'tobacco',
  233. 'tonight', 'torch', 'torso', 'touch', 'toyota', 'trade', 'tribune',
  234. 'trinity', 'triton', 'truck', 'trust', 'type', 'under', 'unit', 'urban',
  235. 'urgent', 'user', 'value', 'vendor', 'venice', 'verona', 'vibrate',
  236. 'virgo', 'visible', 'vista', 'vital', 'voice', 'vortex', 'waiter',
  237. 'watch', 'wave', 'weather', 'wedding', 'wheel', 'whiskey', 'wisdom',
  238. 'deal', 'null', 'nurse', 'quebec', 'reserve', 'reunion', 'roof', 'singer',
  239. 'verbal', 'amen', 'ego', 'fax', 'jet', 'job', 'rio', 'ski', 'yes',
  240. ],
  241. '0.7',
  242. )
  243. @lru_cache(maxsize=1)
  244. def _load() -> tuple[list[str], dict[str, int], str]:
  245. words, version = _WORDLIST
  246. index: dict[str, int] = {w: i for i, w in enumerate(words)}
  247. return words, index, version
  248. def wordlist_version() -> str:
  249. return _load()[2]
  250. def words_required(size: int) -> int:
  251. """Number of mnemonic words needed to encode `size` bytes."""
  252. return ((size + 1) * 3) // 4
  253. def encode(data: bytes) -> list[str]:
  254. """Encode bytes to a list of mnemonic words."""
  255. words, _, _ = _load()
  256. result = []
  257. for n in range(words_required(len(data))):
  258. grp = (n // 3) * 4
  259. remaining = min(len(data) - grp, 4)
  260. x = int.from_bytes(data[grp:grp + remaining], "little")
  261. pos = n % 3
  262. extra = 0
  263. if pos == 2:
  264. if remaining == 3:
  265. extra = MN_BASE # use one of the 7 short remainder words
  266. x //= MN_BASE * MN_BASE
  267. elif pos == 1:
  268. x //= MN_BASE
  269. result.append(words[x % MN_BASE + extra])
  270. return result
  271. def decode(encoded: list[str]) -> bytes:
  272. """Decode a list of mnemonic words back to bytes."""
  273. _, word_index, _ = _load()
  274. buf = bytearray(((len(encoded) // 3) + 1) * 4)
  275. offset = 0
  276. for word in encoded:
  277. try:
  278. idx = word_index[word.lower()]
  279. except KeyError:
  280. raise ValueError(f"unrecognized word: {word!r}") from None
  281. grp = offset & ~3
  282. x = int.from_bytes(buf[grp:grp + 4], "little")
  283. pos = offset % 4
  284. if pos == 3:
  285. raise ValueError("unexpected word after 24-bit remainder")
  286. elif pos == 0:
  287. x = idx
  288. offset += 1
  289. elif pos == 1:
  290. x += idx * MN_BASE
  291. offset += 1
  292. elif pos == 2:
  293. if idx >= MN_BASE: # 24-bit remainder word
  294. x += (idx - MN_BASE) * MN_BASE * MN_BASE
  295. offset += 1 # offset % 4 == 3 signals end-of-group next
  296. else:
  297. if idx > 1624 or (idx == 1624 and x > 1_312_671):
  298. raise ValueError("invalid encoding: value out of range")
  299. x += idx * MN_BASE * MN_BASE
  300. offset += 2 # skip to next group
  301. buf[grp:grp + 4] = x.to_bytes(4, "little")
  302. return bytes(buf[:offset])
  303. if __name__ == "__main__":
  304. import argparse
  305. mode = "decode" if "decode" in sys.argv[0] else "encode"
  306. parser = argparse.ArgumentParser(
  307. prog=sys.argv[0],
  308. description="Decode mnemonic words to bytes." if mode == "decode" else "Encode bytes to mnemonic words.",
  309. )
  310. parser.add_argument("-s", "--separator", default="-", help="word separator (default: -)")
  311. args = parser.parse_args()
  312. if mode == "encode":
  313. data = sys.stdin.buffer.read()
  314. print(args.separator.join(encode(data)))
  315. else:
  316. words = [w.strip() for w in sys.stdin.read().split(args.separator) if w.strip()]
  317. try:
  318. data = decode(words)
  319. except ValueError as e:
  320. print(f"error: {e}", file=sys.stderr)
  321. sys.exit(1)
  322. sys.stdout.buffer.write(data)