README.md
[![Gem Version](https://badge.fury.io/rb/story_key.svg)](https://badge.fury.io/rb/story_key)
[![Build Status](https://travis-ci.org/jcraigk/story_key.svg?branch=main)](https://travis-ci.org/jcraigk/story_key)
[![Maintainability](https://api.codeclimate.com/v1/badges/6046413814d7f6417ce9/maintainability)](https://codeclimate.com/github/jcraigk/story_key/maintainability)
![Story Key Logo](https://user-images.githubusercontent.com/104095/160752597-45ab3b7b-a3a3-43ef-b546-9c163f389927.png)
| Gem Version | Locale | Lexicon SHA |
|-------------|--------|-------------|
| 0.4.0 | Miami | 3bfbbf9 |
Locale will not change until v1.0 release
# StoryKey
StoryKey is a proof of concept [Brainwallet](https://en.bitcoin.it/wiki/Brainwallet) inspired by [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) written in [Ruby](https://www.ruby-lang.org/en/). It converts an arbitrary string of data, such as a [cryptocurrency private key](https://en.bitcoin.it/wiki/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](https://openai.com/dall-e-2).
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](https://openai.com/dall-e-2)
* Requires OpenAI key
## Features
* 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.
![KeyStory Example Text](https://user-images.githubusercontent.com/104095/210197560-45494c50-6382-465b-8163-a61b3783ac76.png)
![KeyStory Example Image](https://user-images.githubusercontent.com/104095/210197577-11e1d6ac-a32f-4438-b7eb-f766bf62f0f2.png)
```
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
### Lexicon Curation
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.
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'story_key'
```
And then execute:
```
$ bundle install
```
Or install it yourself as:
```
$ gem install story_key
```
## Usage
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](https://beta.openai.com/account/api-keys) as an environment variable:
```
# .env
OPENAI_KEY=<your-api-key>
```
You must have [ImageMagick](https://imagemagick.org/index.php) installed locally.
### Command Line Usage
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:
![Key/Story Example](https://user-images.githubusercontent.com/104095/161376334-4a591100-e3fc-41ce-b931-4773bebc23fd.png)
### Ruby Usage
After installing the gem, you may run `bin/console` or `require` the gem in your own project.
### Generate new key/story
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">]
```
### Encode an existing key
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.
### Decode an existing story
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"
```
## Development
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:
1. Run `rake lexicon:build` to re-generate the data file
2. Copy the lexicon SHA into `version.rb` as well as this README (if publishing)
3. Increment the semantic version of the gem (if publishing)
When incrementing the semantic version post-1.0, be sure to:
1. Create a new `VERSION_SLUG`, adhering to the locale convention
2. Append a row to the version reference at the top of this README
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/jcraigk/story_key.
## License
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).