Misc BIP39 related tools/scripts.
Run the automated tests from this directory with:
python3 -m unittest discover -s tests -vUpdate checksums
sha256sum *.py *.sh *.txt *.sed > SHA256SUMSTechnical specification for transforming dice rolls into seed words:
- The input is the dice-roll string as ASCII digits
1through6. Whitespace is ignored by the helper scripts. - For a 12-word use 50 rolls. For a 24-word seed, use 99 rolls.
- Compute
digest = SHA256(rolls_ascii)(not part of BIP39, but removes possiblle statistical bias from rolls). - Select the BIP39 entropy bytes from
digest:- 12 words:
entropy = digest[0:16], 128 bits. - 24 words:
entropy = digest[0:32], 256 bits.
- 12 words:
- Compute the BIP39 checksum from the selected entropy bytes, by taking the first len(entropy) // 32 bits of its SHA256:
checksum = SHA256(entropy).- 12 words: append the first 4 checksum bits.
- 24 words: append the first 8 checksum bits.
- Write
entropy || checksum_bitsas a big-endian bit string: most significant bit first for each byte. - Split that bit string into 11-bit groups. A 12-word seed has 132 bits and therefore 12 groups; a 24-word seed has 264 bits and therefore 24 groups.
- Interpret each 11-bit group as an unsigned integer
0..2047. - Use that integer as a zero-based index into
bip39-eng.txt; equivalently, word indexnis linen + 1. - Join the selected words with spaces.
# Generate 50 random dice faces for a 12-word seed test. Dependencies: tr, head.
tr -dc '1-6' </dev/urandom | head -c 50; echo
# Use python to convert the dice rolls into BIP39 mnemonic.
# Dependencies: python3+std libraries: hashlib.sha256, argparse, pathlib
./dice.py 12 44266664153554464254321232633466466235664323326523
# Use bash tools to convert the dice rolls to BIP39 mnemonic, by using
# bc base-2048 output and a checked-in sed map from base-2048 digits to BIP39 words.
# Dependencies: bash, sha256sum, cut, xxd, bc, xargs, sed, paste
./dice.sh 12 44266664153554464254321232633466466235664323326523
# Result:
# vacuum ethics glimpse cable grit comfort reason festival nothing balance grant design
# Regenerate the map of 11-bit integers 0..2047 into words if bip39-eng.txt ever changes.
awk '{printf "s/^%04d$/%s/\n", NR-1, $0}' bip39-eng.txt > bip39-bc2048.sedCompare these three methods to each other with:
python3 -m unittest tests/test_dice_word_mapping_fuzz.pybip85.py derives English BIP39 child mnemonics using the BIP85 BIP39
application path m/83696968'/39'/0'/{word_count}'/{index}'. The parent
mnemonic is converted to its BIP39 seed with an empty BIP39 passphrase.
# Prompts for the parent mnemonic.
python3 bip85.py 12 0Supported child word counts are 12, 15, 18, 21, and 24.
Run the tests:
python3 -m unittest tests/test_bip85.pyxprv.py derives a mainnet BIP32 root extended private key from a valid English
BIP39 mnemonic. It uses an empty BIP39 passphrase.
# Prompts for the mnemonic.
python3 xprv.pyThe resulting xprv contains the root private key and chain code. Wallet
addresses also depend on the address type and derivation path. For example, a
mainnet native Segwit single-signature wallet compatible with SeedSigner's
m/84'/0'/0' account uses this Bitcoin Core descriptor:
wpkh(XPRV/84h/0h/0h/<0;1>/*)
Replace XPRV with the printed value. Bitcoin Core requires a descriptor
checksum for importdescriptors; obtain it with getdescriptorinfo, append it
after #, and import the checksummed descriptor into a blank descriptor wallet.
Use timestamp 0 when recovering historical transactions, or "now" only for
a wallet that has never been used.
encrypt_backup.py creates a minimal encrypted 7z archive containing one
plaintext file named backup.txt. The file contains a generated comment with
a fresh random secret, followed by the mnemonic line:
# Generated by bip39tools/encrypt_backup.py. Nonce: <64 hex characters>
mnemonic = "word word ... word"
The scripts use only Python standard libraries and bip39-eng.txt.
# Create an encrypted backup archive. The mnemonic must be 12 or 24 BIP39 words.
python3 encrypt_backup.py backup.7z
# Prompts for the mnemonic, archive passphrase, and passphrase confirmation
# with hidden terminal input.
# Decrypt the archive and print backup.txt to stdout.
python3 decrypt_backup.py backup.7z
# Prompts for the archive passphrase with hidden terminal input.
# # Generated by bip39tools/encrypt_backup.py. Nonce: ...
# mnemonic = "abandon abandon abandon abandon abandon ..."The archive password is the encryption password for the 7z file. It is not a BIP39 passphrase, and no BIP39-passphrase wallet derivation is performed.
-
Normalize and validate the input mnemonic:
- split the provided words on whitespace,
- lowercase each word,
- require exactly 12 or 24 words,
- require every word to be present in
bip39-eng.txt.
-
Build the plaintext as UTF-8 bytes:
# Generated by bip39tools/encrypt_backup.py. Nonce: <64 hex characters> mnemonic = "word word ... word"The nonce is 32 random bytes encoded with
secrets.token_hex(32). The plaintext ends with one newline byte (0x0a). -
Generate independent 16-byte salts and 16-byte IVs with
secrets.token_bytesfor the file data stream and encoded header stream. -
Derive each AES-256 key with the 7z AES/SHA-256 KDF:
password_bytes = password.encode("utf-16-le") digest = hashlib.sha256() for i in range(16_777_216): # 1 << ROUNDS_POWER, where ROUNDS_POWER = 24 digest.update(salt) digest.update(password_bytes) digest.update(struct.pack("<Q", i)) key = digest.digest()
-
Pad the plaintext with zero bytes to at least 1024 bytes, then to a 16-byte boundary. The original plaintext length is stored only in the encrypted header, so standard extractors remove both AES block padding and cover padding during decryption.
-
Encrypt the padded plaintext with AES-256-CBC using the derived key and IV. No compression is used.
-
Build the normal single-file 7z header containing the file-data AES/SHA-256 coder (
06 f1 07 01), file-data KDF properties, original plaintext size, plaintext CRC32, and one UTF-16-LE filename entry forbackup.txt. -
Pad and encrypt that normal header as a second AES-256-CBC stream.
-
Write a minimal single-file 7z container with standard encoded-header metadata:
- file signature:
37 7a bc af 27 1c, - version: major
0, minor4, - next-header CRC32: CRC32 of the 20-byte section header,
- section header: little-endian
uint64offset to the final next header, little-endianuint64final next-header length, CRC32 of the final next header, - encrypted body: AES-256-CBC ciphertext from step 6,
- encrypted encoded-header body: AES-256-CBC ciphertext from step 8,
- final next header:
kEncodedHeader(0x17) describing how to decrypt the encrypted encoded-header body.
- file signature:
The generated archives use standard 7z header encryption, so standard archivers
can open them with the archive passphrase. Without the passphrase, the exact
plaintext size, plaintext CRC32, and backup.txt filename are not present in
the clear header. The outer 7z structure still reveals archive size and the
fixed 1024-byte encrypted file-data bucket used for these small backups.
All variable-width 7z integers generated by the script use its encode_var64
encoding.
python3 -m unittest tests/test_backup_archive.py