Skip to content

StoryKey is a proof of concept Brainwallet that converts an arbitrary string of data, such as a cryptocurrency private key, into an English paragraph intended for longterm human memory.

License

Notifications You must be signed in to change notification settings

jcraigk/storykey

Repository files navigation

Gem Version Build Status Maintainability

Story Key Logo

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 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

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 KeyStory Example Image

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:

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 as an environment variable:

# .env
OPENAI_KEY=<your-api-key>

You must have ImageMagick 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

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.

About

StoryKey is a proof of concept Brainwallet that converts an arbitrary string of data, such as a cryptocurrency private key, into an English paragraph intended for longterm human memory.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published