Skip to content

Repository files navigation

EntropyForge, offline Bitcoin seed, passphrase & wallet toolkit

A small, self-contained pure-Python tool (one script, bip39gen.py, no dependencies) that generates Bitcoin BIP39 seed phrases and passphrases completely offline, from the OS CSPRNG XOR your own dice rolls or coin flips, and also derives addresses (legacy / segwit / taproot, mainnet or testnet), exports a watch-only xpub + QR, does Shamir split/recover, and recovers a mis-written seed. A local, auditable stand-in for the Ian Coleman BIP39 tool: no website, no install, no network.

I am not a professional developer. This tool has been carefully audited (see Audit it yourself below), and I use it myself, on the offline Tails setup in TAILS-SETUP.md, to secure my own crypto assets. That's my own confidence in it, not a promise for you: verify it yourself before trusting it with real funds. Provided as-is, with no warranty. See LICENSE.


Read this first: security

  • Run it OFFLINE only. The safest method is to boot Tails from a USB stick (no network, no persistent storage), run the tool, write the words on paper by hand, then shut down. Even a normal computer with Wi-Fi off can leave traces on disk.
  • Never type a real seed or passphrase into an online machine, no website, no notes app, no password manager, no screenshot, no cloud. No legitimate tool ever asks you to.
  • The machine matters more than the code. This tool writes nothing to disk and sends nothing over the network, but if the computer is already compromised, nothing can save you. Use a clean, offline machine.
  • Write results on paper or metal, by hand. Store a passphrase separately from the seed. Together they are the wallet; apart, each is useless to a thief.

What it does

  • BIP39 mnemonics (12/15/18/21/24 words) from the OS CSPRNG, dice, or coin flips
  • BIP39 passphrase (the “25th word”): word-based (--passphrase-words) or a random-character string (--passphrase-chars, e.g. 50 chars for a Trezor), from dice or coins
  • At least 2 independent sources, the device RNG is always XOR-mixed in, and you must supply at least one physical source (--dice/--coins). A secret is never generated from a single source (result is strong if any one source is good)
  • Secrets print MASKED by default on an interactive terminal, hold the SPACE bar to reveal, release to re-hide (press R to keep it shown, Enter/Q to wipe the screen). --show prints in the clear. Masking helps against shoulder-surfing and scrollback; it does not stop screen-capture malware, only an offline OS does
  • Addresses: legacy (BIP44), wrapped segwit (BIP49), native segwit (BIP84), and taproot (BIP86, bc1p...), on mainnet or --testnet (tb1..., for zero-risk practice), all round-trip-verified against the official test vectors
  • Recovery helpers: --last-word lists the valid final words for a partial seed; --complete finds the word that fixes a single mis-transcribed one
  • --entropy HEX builds a mnemonic from entropy you supply (to reproduce or cross-check)

Built-in entropy floors (you can’t make a weak secret by accident)

The tool refuses to produce anything below a safe minimum:

Output Minimum Enforced by
Seed 128 bits (12 words) only 12/15/18/21/24 words are accepted
Word passphrase 99 bits (9 words) blocked below 9
Character passphrase 100 bits (20 chars) blocked below 20

These floors are absolute, --force does not lower them. The lowest-entropy secret this tool can be made to produce, by any combination of flags, is 99 bits. That matters most for passphrases: a BIP39 passphrase is protected only by PBKDF2-HMAC-SHA512 at 2048 iterations, which is cheap to brute-force, so a short one is genuinely crackable no matter how good the entropy source was. --force still overrides the dice/coin quality check and the two-source requirement, neither of which can weaken the result (the device RNG is always XOR-mixed in).

Never a single source. Every secret is XOR-mixed from at least two independent entropy sources, the device RNG (always) plus at least one physical source you provide (--dice/--coins). If a source were biased or even backdoored, the result is still strong as long as any one source is good. Single-source generation is refused unless you pass --force.

  • Address derivation (BIP44/49/84/86 taproot, mainnet + testnet); account xpub + QR
  • Shamir backup (split / recover), backup fingerprint, dice/coin quality checks
  • --selftest, verifies the code against official BIP39 / BIP32 / BIP84 / BIP86 vectors

Requirements

  • Python 3, preinstalled on macOS, Linux, and Tails. On Windows use python instead of python3.
  • english.txt, the official BIP39 English wordlist, must sit in the same folder.
  • No third-party packages. Python standard library only.

Quick start (offline!)

# 1. Confirm the file is the audited version
sha256sum bip39gen.py
# -> 9cfadcbe702b6a80383ac998cd965464140adacf280aabe368c15f957a0d9d57

# 2. Prove the code is correct (matches official test vectors)
python3 bip39gen.py --selftest        # expect: SELFTEST PASSED

# 3. Generate, example: 12-word passphrase from coins (device RNG auto-mixed in)
python3 bip39gen.py --passphrase-words 12 --coins

See START-COMMANDS.md for a copy-paste command for every purpose.


Audit it yourself before trusting it

You don’t have to take my word for it. Two ways:

1. Ask any AI, paste the whole bip39gen.py and ask:

  • “Does this script make any network connection or send data anywhere?”
  • “Does it write any secret to disk, or does it only print to the screen?”
  • “Is all randomness from a cryptographically secure source (Python’s secrets)?”
  • “Any hidden or obfuscated code, eval, exec, base64, pickle, subprocess?”
  • “Does the BIP39 / BIP32 logic match the official specs and test vectors?”

2. Check it by hand, these should return nothing (except the offline air-gap check):

# network / exec / obfuscation / file-writes
grep -nE "socket|urllib|request|http|subprocess|eval\(|exec\(|base64|open\([^)]*['\"][wa]" bip39gen.py
# randomness must come from 'secrets', never the insecure 'random' module
grep -nE "random|secrets" bip39gen.py

Security design: why no data leaves

Based on a line-by-line audit of this version:

  • Output only to the screen. It never writes a seed or passphrase to disk. Every open() in the code is read-only (the wordlist, and the script itself for the integrity check).
  • No network. The only network-capable call is an optional check that detects an active network route without sending a single packet (a UDP connect() to a reserved, non-routable address just reads the routing table). No urllib, requests, http, or any transmitting socket.
  • Standard library only, nothing to pip install, nothing fetched, nothing hidden.
  • Randomness from the OS CSPRNG (secrets), never the insecure random module.
  • --selftest validates the whole pipeline against official BIP39/BIP32/BIP84 test vectors before you trust it with anything.

Even so: verify the hash, run --selftest, audit it yourself, and run it offline.


Known limitations

Two features work correctly but carry caveats you must understand, plus one honest point about the project as a whole.

1. The Shamir split is custom, NOT SLIP-39. --split uses my own secret sharing implementation over GF(256). The round trip is self-tested on every run, but the share format is not a standard. No Trezor, Keystone, or any other wallet or tool can restore these shares. Only this script's --recover can read them. If you use it: keep a copy of bip39gen.py stored with your shares, and unless you fully accept that dependency, also keep an ordinary written copy of the seed words.

2. The QR is spec-verified; scanning depends on your terminal. --selftest decodes every QR the tool renders with its own pure-Python decoder, a real xpub plus a string at every version's capacity boundary, and requires an exact round-trip, so the data is validated end-to-end (this even caught and fixed a capacity-boundary bug). The QR is drawn theme-independently (explicit black/white, proper 4-module quiet zone), but to actually scan it you need a 256-colour terminal and a large enough window, a small window, low contrast, or a colour-limited terminal can stop a camera reading it. If it won't scan, it doesn't matter: an xpub is public (watch-only, cannot spend), so copy the text and import that (Sparrow / Electrum / BlueWallet). The printed text is always authoritative.

3. This code is young. The BIP39, BIP32, secp256k1, RIPEMD-160, Base58Check, and bech32 routines are written from scratch here and verified against the official test vectors. Established tools build on libraries that have absorbed years of public adversarial review, and that scrutiny is itself a security property this project has not earned yet. Passing test vectors proves the correct path is correct; it does not prove the absence of subtle edge cases.

What to do about #3. Cross-verify before funding anything: generate your mnemonic here, then, on the same offline machine, enter it into an independent implementation (offline Ian Coleman HTML, Electrum, or restore it onto a hardware wallet) and confirm you get the same addresses. If two independent implementations agree, you are no longer trusting this code alone for correctness. Better still, use a seed from this tool as one key in a 2-of-3 multisig rather than for sole custody.


Threat model: what this tool protects against

This tool only generates a secret. It is not a signing device and it stores nothing on a chip, so the hardware-wallet attack classes work very differently here.

Protected / not applicable

  • Malicious signing firmware (e.g. Dark Skippy). Not applicable: this tool never signs transactions, so there are no signatures to hide a leaked seed in.
  • Physical chip extraction / side channels. Not applicable: no secure element holds your seed. After Tails shuts down, the only copy is your paper.
  • Weak RNG / low entropy (e.g. Milk Sad). Actively defended: CSPRNG plus your dice/coins, always XOR-mixed (at least two independent sources), entropy floors, and quality checks. A generated secret is not the weak link.
  • Data exfiltration by the tool. None: no network sends, no disk writes, standard library only (see AUDIT.md).

Where risk still lives (and the mitigation)

  • A compromised machine at generation time. Malware on the OS can capture the secret as it appears. Mitigation: generate on Tails, offline, no persistent storage (see TAILS-SETUP.md).
  • A tampered copy of this tool, or a fake Tails. The equivalent of a counterfeit device. Mitigation: the SHA-256 check, --verify-self, --selftest, and verifying the Tails download.
  • The wallet you later load the seed into. To spend, you import the seed into a signing device, and that device is where firmware attacks like Dark Skippy actually live. No generator can protect that step.

The structural defense. No single seed or device is immune to everything. To make any single compromise survivable, use 2-of-3 multisig across different vendors, so a backdoored signer or a leaked seed cannot move funds on its own. A seed from this tool can be one key in that multisig.


License

MIT, see LICENSE.

About

Offline, air-gapped Bitcoin BIP39 seed & passphrase generator and wallet toolkit, dice/coin entropy XOR OS CSPRNG, taproot, testnet, Shamir backup, seed recovery. Pure-Python, no dependencies.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages