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. SeeLICENSE.
- 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.
- 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).
--showprints 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-wordlists the valid final words for a partial seed;--completefinds the word that fixes a single mis-transcribed one --entropy HEXbuilds a mnemonic from entropy you supply (to reproduce or cross-check)
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
- Python 3, preinstalled on macOS, Linux, and Tails. On Windows use
pythoninstead ofpython3. english.txt, the official BIP39 English wordlist, must sit in the same folder.- No third-party packages. Python standard library only.
# 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 --coinsSee START-COMMANDS.md for a copy-paste command for every purpose.
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.pyBased 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). Nourllib,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 insecurerandommodule. --selftestvalidates 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.
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.
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.
MIT, see LICENSE.