A minimal, fully offline utility for generating and validating BIP-39 cryptocurrency wallet recovery phrases.
Generation and validation work and are proven against 48 official BIP-39 test vectors in both directions. What is not done: the automated leak and no-network CI harnesses as required checks, extended fuzzing, and the two-builder reproducible signed-release pipeline.
Treat this as a working tool that has not yet been through release engineering. For real funds, wait for a signed reproducible release, or audit and build it yourself.
| Command | Purpose |
|---|---|
bip39 generate --words 24 |
New phrase from 256 bits of OS CSPRNG entropy (default) |
bip39 generate --words 12 |
New phrase from 128 bits |
bip39 validate |
Check a phrase, read from stdin — never from argv |
bip39 selftest |
Wordlist integrity plus known-answer round trips |
bip39 wordlist-info |
Wordlist digest and provenance |
validate accepts all five standard lengths (12, 15, 18, 21, 24). generate
offers only 12 and 24, which is what wallets expect, and says so if you ask for
another.
Phrases are generated from 128 or 256 bits of operating-system CSPRNG entropy, following BIP-39 exactly: SHA-256 checksum, 11-bit index split, official English wordlist. Validation checks word membership, word count, structure and checksum, locally, and retains nothing.
No wallet recovery, missing-word search, checksum repair, candidate enumeration or brute force. No balance lookup, blockchain access, address derivation or wallet scanning. No network access of any kind — no telemetry, analytics, crash reporting, update checks, remote fonts or CDNs. No clipboard integration.
These are not unimplemented. They are refused, and the design makes them awkward to add: a failed checksum reports only that it failed.
A recovery phrase is the wallet. Read docs/SECURITY.md
before trusting this with funds — in particular the sections on threats it does
not address and where memory clearing cannot be guaranteed.
crates/bip39-core/ BIP-39 logic. No I/O, no logging, no unsafe, no_std-compatible.
crates/bip39-cli/ The `bip39` binary: all I/O and platform code.
tests-vectors/ Official public BIP-39 test vectors, digest-pinned.
ci/ Provenance, boundary, fixture-policy and no-network harnesses.
docs/SECURITY.md Threat model and honest limitations.
docs/SUPPLY_CHAIN.md Dependency controls and audit log.
docs/plans/ Full implementation plan.
cargo build --workspace --release
cargo test --workspace
cargo test -p bip39-core --features test-vectors,seed # full vector conformanceThen, in a terminal:
./target/release/bip39 selftest
./target/release/bip39 generate --words 24generate refuses to run when stdout is redirected to a file or a pipe, because
that would store your phrase where other software can read it.
To build with no registry access, the way CI and releases do:
ci/vendor.sh # once, online
ci/no-network.sh cargo build --workspace --release --frozen --offlineci/verify-provenance.sh # vendored data matches pinned digests
ci/check-core-purity.sh # crate boundaries hold
python3 ci/fixture-policy-lint.py # no usable phrase is committed
python3 ci/fixture-policy-lint.py --selftest
cargo deny check # requires cargo-deny >= 0.20Two rules that are enforced mechanically and are not negotiable:
- Never commit a real recovery phrase. Not in tests, docs, screenshots,
fixtures or CI output. Demo material must be checksum-invalid and marked
INVALID -- NON-PRODUCTION.ci/fixture-policy-lint.pyfails the build. - Never add a dependency without an entry in
docs/SUPPLY_CHAIN.md. The committed dependency-tree snapshot is diffed on every CI run.
MIT. See LICENSE.