One-time pad tool for Bitcoin seed mnemonic cold storage
UNLICENSE License
seed-otp
is a Python-based one-time pad CLI tool for storing your Bitcoin seed
mnemonic words securely using multi-factor auth. The simple, well-written code
can be easily audited, and the method itself can be applied using pen-and-paper,
rather than using a computer.
📹I made a presentation about this tool (YouTube)
You have an HD wallet such as a Trezor or Ledger for storing your Bitcoin, and you would like to store your seed mnemonic phrase. You may also want to store multiple copies of your seed in different places. Unfortunately, if any one of those copies of your seed becomes compromised, anyone with access to the seed can now take all your coins, and buy themselves a lambo.
Normally you would not need access to your seed mnemonic. However, should something happen to your wallet (perhaps you lose it, or it breaks), you may need to restore the wallet using the seed phrase.
Combine a one-time pad with multi-factor authentication.
By using mult-factor auth (something you know plus something you have) and one-time pad encryption, you have a simple yet extremely hard to crack solution. With your OTP key and seed mnemonic stored separately, it becomes onerous to obtain both. Even if someone does obtain either your mnemonic or OTP key, you would have time to move your coins to a new wallet with a brand new seed before anything happens to your coins. A one-time pad is considered perfect secrecy: it's nearly impossible to brute force attack so long as the key remains secret.
Your auth factors are:
There are a variety of other solutions to this problem, some of which may be more appropriate for your needs. Let's go over some of the alternatives and discuss why they might not be appropriate:
Before using this tool, you should have a few things:
After using the tool, make sure you test the seed restore process!
$ pip install seed-otp
$ seed-otp generate 12
{
"otp-key": "AAwCnwGIAe0EWABWAI4AkAMjAFQBLgZjB1T1PJtz",
"success": true
}
Store the key above in your password management tool.
$ seed-otp encrypt AAwCnwGIAe0EWABWAI4AkAMjAFQBLgZjB1T1PJtz abandon ability able about above absent absorb abstract absurd abuse access accident
{
"encrypted-words": [
"fault",
"couple",
"digital",
"merge",
"area",
"bar",
"barrel",
"grab",
"argue",
"cheap",
"soap",
"typical"
],
"success": true
}
Store the phrase above in your safe place.
$ seed-otp decrypt AAwCnwGIAe0EWABWAI4AkAMjAFQBLgZjB1T1PJtz fault couple digital merge area bar barrel grab argue cheap soap typical
{
"decrypted-words": [
"abandon",
"ability",
"able",
"about",
"above",
"absent",
"absorb",
"abstract",
"absurd",
"abuse",
"access",
"accident"
],
"success": true
}
Usage: seed-otp [OPTIONS] COMMAND [ARGS]...
Options:
-h, --help Show this message and exit.
Commands:
check-key Check OTP key for encoding or checksum errors.
decrypt Decrypt seed words using an OTP key.
encrypt Encrypt seed words using an OTP key.
generate Generate a secure OTP key for up to NUM_WORDS number of words.
Command output is usually formatted as JSON, so you can pipe the output to
other tools (such as jq
) and get wild.
The OTP key is a URL-safe base64 encoded key (without padding) composed of N subkeys, where N is the number of keys specified at creation time. The values are stored as big-endian short unsigned integers (2-bytes each). The last 4 bytes of the OTP key is the first 4 bytes of the SHA256 digest of the preceeding bytes.
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Keys |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\ /
/ Keylist (variable length) \
\ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Checksum +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
BIP-0039 uses 11 bits per word, but in this scheme we're using 16 bits per word. This is mainly for simplicity, with the trade-off of using more bytes. It also allows the possibilty of using larger wordlists (of up to 65536 words).
Below is some pseudocode for encrypting/decrypting. Assume that the words and keys are mapped to integers representing an index position in the wordlist.
To encrypt a word, the algorithm is as follows:
ciphertext = (word + key) mod 2048
To decrypt a word, do the following:
word = (ciphertext - key) mod 2048
You could perform the encryption/decryption using pen and paper if you feel the need to do so. This would prevent the necessity of typing your seed words into a computer. Naturally, you could also generate your own keys and store those offline as well. For practical purposes, however, this is probably unnecessary.