Gem Version | Locale | Lexicon SHA |
---|---|---|
0.4.0 | Miami | 3bfbbf9 |
Locale will not change until v1.0 release
StoryKey is a proof of concept Brainwallet inspired by BIP39 written in Ruby. It converts an arbitrary string of data, such as a cryptocurrency private key, into an English paragraph intended for longterm human memory. It also assists in decoding the story back into its original form. Optionally, a visual representation of the paragraph is also provided using OpenAI DALL-E.
Each story is provided in multiple formats:
- Humanized Text
- Version locale header ("In Miami I saw...")
- Enumerated phrases
- Colorized parts of speech (adjectives, verbs, nouns)
- Grammatical filler (articles, prepositions, conjunctions, punctuation)
- Tokenized Text
- Ordered list of unique tokens
- Space-delimited lowercase alphanumeric/dash
- Useful as a seed phrase for generating derivative keys
- Graphical
- AI-generated images via DALL-E
- Requires OpenAI key
- Encodes arbitrary length data from 1 to 512 bits (default 256)
- Includes checksum for integrity
- Includes version slug to ensure accurate decoding
- Uses a repeating English grammar to aid in mnemonics
- Uses a lexicon curated for mental visualization
- Avoids word repetition
- Provides interactive command-line recovery
Each token of the story, which may be a single word or short compound phrase, encodes 10 bits. The checksum length is variable based on the input size and space available in the last two tokens after accounting for a 4-bit footer. Here are a few example key sizes along with their respective story and checksum sizes.
Key bits | Story tokens | Checksum bits |
---|---|---|
64 | 8 | 12 |
128 | 14 | 8 |
192 | 21 | 14 |
256 | 27 | 10 |
384 | 40 | 12 |
512 | 53 | 14 |
An example key with its associated story, seed phrase, and image are shown below.
Key:
CgCLLXvoch7sLaQWe5Y3Evtzety2Vr9XJGRmAq9YZUXY
Story:
In Miami I saw
1. a practical theorist eating toast with a shopkeeper,
2. a jobless macaw disowning a scientist,
3. a toothsome brother eating dumplings with Paul Cezanne,
4. a rabid outsider leveling a vulture,
5. a hysterical Marge Simpson threatening Moe Szyslak,
6. a rancid cyborg demanding a jeweler,
7. and a wife paging Jimi Hendrix.
Seed Phrase:
miami practical theorist eating-toast shopkeeper jobless macaw disowning scientist toothsome brother eating-dumplings paul-cezanne rabid outsider leveling vulture hysterical marge-simpson threatening moe-szyslak rancid cyborg demanding jeweler wife paging jimi-hendrix
This paragraph or seed phrase can be deterministically decoded back into its original form using the same version of StoryKey. The locale of the story (e.g. Miami
) identifies that version. During key recovery, an exception will be raised if:
- the
version slug
does not match the current version of StoryKey - the embedded
checksum
does not match the expected value
The lexicon was selected using the following criteria:
- Anthropomorphism. All parts of speech - adjective, noun, and verb - must fit logically when composed into phrases. To accommodate, entries were selected based on how closely they could produce a mental image of commonly known anthropomorphic entities interacting with one another. To produce enough verbs, compound actions such as "eat breakfast" were also used.
- Adjectives: personal physical qualities, moods, colors, textures
- Nouns: famous people/characters, professions, animals
- Verbs: physical actions connecting subject/object, favoring transitive, sometimes compound
- Visualization. Entries should be concrete vs abstract and convey vivid mental imagery.
- Cultural acceptability. Reject sexually suggestive and other controversial imagery.
- Eliminate similar base words across parts of speech.
- Balance brevity with clarity.
Add this line to your application's Gemfile:
gem 'story_key'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install story_key
This library may be used directly from the command line or by calling Ruby methods.
If you want to generate images of the story along with the text, create a file named .env
in the project directory and add your OpenAI key as an environment variable:
# .env
OPENAI_KEY=<your-api-key>
You must have ImageMagick installed locally.
Invoke the command line interface by running bin/storykey
.
StoryKey commands:
storykey decode [STORY] # Decode a story passed as an argument or from a file
storykey encode [KEY] # Encode a key passed as an argument or from a file
storykey help [COMMAND] # Describe available commands or one specific command
storykey new [BITSIZE] # Create a new key/story (default 256 bits, max 512)
storykey recover # Decode a story interactively
To see help on a specific command, run bin/storyke --help [command]
.
The command line also features an interactive recovery tool to aid in converting a story back into its source key. Run bin/storykey recover
to initiate the process:
After installing the gem, you may run bin/console
or require
the gem in your own project.
Generate a new random key and associated story.
# StoryKey.generate
=>
["4eqfoXzMDyqQW6p8zAQj7c8KkynK5K2BW6D5Vfp7xCaQ",
#<struct StoryKey::Story
text=
"In Miami I saw a dim Balrog eat hummus with an appraiser, a facetious scholar play badminton with an economist, a witty uncle insure Bruce Willis, an appreciative dolphin blare at a cyclist, a blissful James Bond undercut a connoisseur, a green Hugh Jackman eat cheese with a bison, and Elvis Presley snorkel with a counselor.",
humanized=
"In \e[31mMiami\e[0m I saw\n1. a \e[36mdim\e[0m \e[33mBalrog\e[0m \e[35meat hummus\e[0m with an \e[33mappraiser\e[0m,\n2. a \e[36mfacetious\e[0m \e[33mscholar\e[0m \e[35mplay badminton\e[0m with an \e[33meconomist\e[0m,\n3. a \e[36mwitty\e[0m \e[33muncle\e[0m \e[35minsure\e[0m \e[33mBruce Willis\e[0m,\n4. an \e[36mappreciative\e[0m \e[33mdolphin\e[0m \e[35mblare\e[0m at a \e[33mcyclist\e[0m,\n5. a \e[36mblissful\e[0m \e[33mJames Bond\e[0m \e[35mundercut\e[0m a \e[33mconnoisseur\e[0m,\n6. a \e[36mgreen\e[0m \e[33mHugh Jackman\e[0m \e[35meat cheese\e[0m with a \e[33mbison\e[0m,\n7. and \e[33mElvis Presley\e[0m \e[35msnorkel\e[0m with a \e[33mcounselor\e[0m.",
tokenized=
"dim balrog eat-hummus appraiser facetious scholar play-badminton economist witty uncle insure bruce-willis appreciative dolphin blare cyclist blissful james-bond undercut connoisseur green hugh-jackman eat-cheese bison elvis-presley snorkel counselor">]
Produce an English paragraph given input data (e.g. a cryptocurrency private key):
# StoryKey.encode(key: '4eqfoXzMDyqQW6p8zAQj7c8KkynK5K2BW6D5Vfp7xCaQ')
=>
#<struct StoryKey::Story
text=
"In Miami I saw a dim Balrog eat hummus with an appraiser, a facetious scholar play badminton with an economist, a witty uncle insure Bruce Willis, an appreciative dolphin blare at a cyclist, a blissful James Bond undercut a connoisseur, a green Hugh Jackman eat cheese with a bison, and Elvis Presley snorkel with a counselor.",
humanized=
"In \e[31mMiami\e[0m I saw\n1. a \e[36mdim\e[0m \e[33mBalrog\e[0m \e[35meat hummus\e[0m with an \e[33mappraiser\e[0m,\n2. a \e[36mfacetious\e[0m \e[33mscholar\e[0m \e[35mplay badminton\e[0m with an \e[33meconomist\e[0m,\n3. a \e[36mwitty\e[0m \e[33muncle\e[0m \e[35minsure\e[0m \e[33mBruce Willis\e[0m,\n4. an \e[36mappreciative\e[0m \e[33mdolphin\e[0m \e[35mblare\e[0m at a \e[33mcyclist\e[0m,\n5. a \e[36mblissful\e[0m \e[33mJames Bond\e[0m \e[35mundercut\e[0m a \e[33mconnoisseur\e[0m,\n6. a \e[36mgreen\e[0m \e[33mHugh Jackman\e[0m \e[35meat cheese\e[0m with a \e[33mbison\e[0m,\n7. and \e[33mElvis Presley\e[0m \e[35msnorkel\e[0m with a \e[33mcounselor\e[0m.",
tokenized=
"dim balrog eat-hummus appraiser facetious scholar play-badminton economist witty uncle insure bruce-willis appreciative dolphin blare cyclist blissful james-bond undercut connoisseur green hugh-jackman eat-cheese bison elvis-presley snorkel counselor">
key
may be in the form of a hexidecimal (ab29f3
), a binary string (1001101
), a decimal (230938
), or a base58 string (uMBca
). If not in the default base58, format
must be provided.
Recover source data (e.g. a cryptocurrency private key) based on the English paragraph:
# StoryKey.decode(story: 'In Miami I saw an official Benjamin Franklin transport Matt Damon')
=> "4NTM"
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
When editing the lexicon, be sure to:
- Run
rake lexicon:build
to re-generate the data file - Copy the lexicon SHA into
version.rb
as well as this README (if publishing) - Increment the semantic version of the gem (if publishing)
When incrementing the semantic version post-1.0, be sure to:
- Create a new
VERSION_SLUG
, adhering to the locale convention - Append a row to the version reference at the top of this README
Bug reports and pull requests are welcome on GitHub at https://github.com/jcraigk/story_key.
The gem is available as open source under the terms of the MIT License.