29 KiB
pyhdwallet v1.1.0 (hdwalletpy)
A command-line tool for generating and recovering HD wallets (BIP39) with support for Ethereum, Solana, and Bitcoin. It is designed for offline operation, optional PGP encryption, and writing deterministic AES-encrypted ZIP "wallet artifacts" into a local .wallet/ folder.
Repository structure (current):
src/pyhdwallet.py— main CLI scripttests/— offline regression test suitetest_vectors.py— pytest suite for derivation integritybootstrap_vectors.py— one-time script to generate test vectorsvectors.json— frozen expected values (committed)data/recipient.asc— test PGP key (committed)
vendor/— vendored Python wheels for offline installationmacos-arm64/— macOS Apple Silicon wheels (Python 3.12)linux-x86_64/— Linux x86_64 wheels (Python 3.12)
.wallet/— generated wallet artifacts (gitignored)requirements.in,requirements.txtinstall_offline.sh— automated offline installerDockerfile,Makefile— Docker-based build toolsREADME.md,playbook.md,LICENSE
Note: This tool requires a subcommand (e.g., gen, recover). Running without one displays help. Use <subcommand> -h for detailed options.
Features
- Offline-first:
gen,recover, andtestblock network access (best-effort in-process guard). - Multi-chain support: Derives addresses for Ethereum, Solana, and Bitcoin.
- PGP encryption: Encrypts a JSON payload to a PGP public key and outputs an ASCII-armored PGP message (typically saved as
.asc). - AES-encrypted ZIP artifacts: When
--fileis used, output is written as an AES-encrypted ZIP viapyzipper. - TTY safety guard + --force: If stdout is piped/redirected (non-TTY), the tool refuses to print sensitive data unless
--forceis explicitly set (to avoid accidental leaks into logs/files).isatty()is the classic way to detect whether stdout is connected to a terminal. - Off-screen mode:
--off-screensuppresses printing sensitive data to stdout. - Regression test suite: Offline tests with frozen vectors ensure derivation logic, PGP fingerprinting, and seed generation remain stable across code changes.
- Vendored dependencies: Pre-built wheels for macOS ARM64 and Linux x86_64 enable true air-gapped installation.
Installation
Prerequisites
- Python 3.12.x (required for vendored wheels)
- Git (for cloning repository)
Quick Installation (macOS/Linux with Internet)
# Clone repository
git clone https://github.com/<your-username>/hdwalletpy.git
cd hdwalletpy
# Run automated installer
./install_offline.sh
The script automatically:
- Detects your platform (macOS ARM64 or Linux x86_64)
- Creates Python 3.12 virtual environment
- Installs from vendored wheels (no PyPI access needed)
- Verifies installation with test suite
- Leaves you in activated venv
Air-Gapped Installation (No Internet)
For maximum security on an air-gapped machine (Tails OS, Ubuntu Live USB, etc.):
On online machine:
# Clone and verify
git clone https://github.com/<your-username>/hdwalletpy.git
cd hdwalletpy
# Verify checksums
cd vendor/linux-x86_64 # or macos-arm64 for Mac
sha256sum -c SHA256SUMS # Linux
shasum -a 256 -c SHA256SUMS # macOS
# Transfer entire repo to USB
On air-gapped machine:
# Ensure Python 3.12 is installed
python3.12 --version
# Navigate to repository
cd hdwalletpy
# Run installer (creates venv, installs offline)
./install_offline.sh
# Generate wallet
python src/pyhdwallet.py gen --off-screen --file
Manual Installation (Without Script)
# Create venv
python3.12 -m venv .venv
source .venv/bin/activate
# Install from vendored wheels
# For macOS ARM64:
pip install --no-index --find-links=vendor/macos-arm64 -r requirements.txt
# For Linux x86_64:
pip install --no-index --find-links=vendor/linux-x86_64 -r requirements.txt
# Verify installation
pytest -v tests/test_vectors.py
python src/pyhdwallet.py test
Dependencies (Top-Level Intent)
bip-utils— BIP39 + BIP derivation logicPGPy— encryption to OpenPGP public keyspynacl+base58— Solana seed/key handlingpyzipper— AES-encrypted ZIP writingpytest— test framework (development only)
All dependencies are pre-downloaded in vendor/ for offline use.
Quick Start
1) Generate (debug/test; prints mnemonic)
python ./src/pyhdwallet.py gen
2) Generate and save AES ZIP artifact to .wallet/
python ./src/pyhdwallet.py gen --file
3) Generate with PGP encryption and ZIP (recommended for "at-rest" storage)
python ./src/pyhdwallet.py gen --pgp-pubkey-file pubkeys/mykey.asc --file
4) Recover (addresses) from mnemonic
python ./src/pyhdwallet.py recover --mnemonic-stdin
# (then paste mnemonic and press Ctrl-D)
5) Recover with interactive input + ZIP artifact
python ./src/pyhdwallet.py recover --interactive --file
6) Run built-in smoke test
python ./src/pyhdwallet.py test
7) Run full regression test suite
pytest -v tests/test_vectors.py
Testing
Regression Test Suite
The project includes a comprehensive offline test suite that validates critical functionality without requiring network access. All tests use committed test vectors to ensure deterministic, repeatable results.
Test Coverage
- PGP fingerprint calculation: Verifies that PGP key fingerprinting logic produces expected results and enforces fingerprint matching
- BIP39 seed derivation: Ensures mnemonics (with and without passphrases) always produce the same seed hex
- Multi-chain address derivation: Validates Ethereum, Bitcoin (3 address types), and Solana (3 profiles) derivation paths remain stable
- BIP85 child mnemonic derivation: Verifies BIP85 entropy generation, path construction, and child mnemonic output against official test vectors
- CLI integration: Smoke tests for
recoverandgen-childcommands with correct and incorrect fingerprints
Running Tests
# Run all tests
pytest -v tests/test_vectors.py
# Run specific test
pytest -v tests/test_vectors.py::test_address_derivation_integrity
# Run with detailed output
pytest -vv tests/test_vectors.py
Test Artifacts (Committed)
tests/vectors.json— Frozen expected values for all test casestests/data/recipient.asc— Test PGP public key (safe to commit; public key only)tests/bootstrap_vectors.py— One-time script to regenerate vectors (run only when intentionally updating expected behavior)
When to Regenerate Test Vectors
Only regenerate tests/vectors.json when you intentionally change:
- Derivation paths (e.g., switching from
m/44'/60'/0'/0/ito a different path) - Solana profile logic
- BIP39 seed generation
To regenerate:
rm tests/vectors.json
python3 tests/bootstrap_vectors.py
pytest -v tests/test_vectors.py # Verify all pass
git add tests/vectors.json
git commit -m "test: update vectors after intentional derivation change"
Warning: If tests fail after a code change and you didn't intend to change behavior, do not regenerate vectors. Fix the code regression instead.
Vendored Dependencies
What is Vendoring?
This repository includes pre-built Python wheels in the vendor/ directory, enabling installation without internet access or PyPI. This is critical for air-gapped security.
Supported platforms:
- macOS ARM64 (M1/M2/M3/M4) - Python 3.12
- Linux x86_64 (Ubuntu, Tails, Debian) - Python 3.12
Building Vendor Wheels (Maintainers)
If you need to update dependencies or add new platforms:
# Using Makefile (recommended)
make vendor-all # Build for both macOS and Linux
make vendor-macos # macOS ARM64 only
make vendor-linux # Linux x86_64 only (requires Docker)
make verify-vendor # Test offline installation
# Commit updated wheels
git add vendor/
git commit -m "vendor: update dependencies to latest versions"
Manual build (macOS):
mkdir -p vendor/macos-arm64
source .venv312/bin/activate
pip download --dest vendor/macos-arm64 -r requirements.txt
cd vendor/macos-arm64 && shasum -a 256 *.whl > SHA256SUMS
Manual build (Linux via Docker):
docker run --rm -v $(pwd):/work -w /work python:3.12-slim bash -c "
apt-get update && apt-get install -y gcc g++ make libffi-dev
pip install --upgrade pip
mkdir -p vendor/linux-x86_64
pip download --dest vendor/linux-x86_64 -r requirements.txt
cd vendor/linux-x86_64 && sha256sum *.whl > SHA256SUMS
"
Verifying Vendor Integrity
# Check checksums before using on air-gapped machine
cd vendor/linux-x86_64
sha256sum -c SHA256SUMS # Linux
shasum -a 256 -c SHA256SUMS # macOS
Outputs and File Structure
Stdout Behavior
genprints the mnemonic + derived addresses by default (intended for debug/test comparisons).--off-screensuppresses printing sensitive data to stdout.
--file Behavior (Deterministic, Secured Output)
If --file is present, the tool writes only an AES-encrypted ZIP file (no raw .json/.asc file is left on disk). AES ZIP is implemented using pyzipper.
Default output folder:
./.wallet/
Override folder:
--wallet-location /path/to/folder
Naming uses UTC timestamps (e.g. 20260108_011830Z):
- No PGP: zip contains
test_wallet_<UTC>.json, zip nametest_wallet_<UTC>.zip - With PGP: zip contains
encrypted_wallet_<UTC>.asc, zip nameencrypted_wallet_<UTC>.zip
Password Handling for ZIP
- Default:
--zip-password-mode promptprompts for the ZIP password (attempts hidden entry). - If hidden entry is not supported in the environment, it falls back to visible
input()with loud warnings. - Optional:
--zip-password-mode autogenerates a Base58 password (length controlled by--zip-password-len). - If auto mode is used, password is shown only if
--show-generated-passwordis set, and it prints to stderr (not stdout) to reduce accidental capture when stdout is redirected.
pyzipper supports AES encryption via AESZipFile and password-setting APIs.
Commands
gen-child (offline) - NEW in v1.1.0
Derive a child BIP39 mnemonic from a master mnemonic using BIP85 deterministic entropy.
What is BIP85?
BIP85 allows you to derive unlimited child mnemonics from a single master seed. Each child mnemonic is:
- Deterministic: Same master + index always produces the same child
- Isolated: Compromise of one child doesn't affect others or the master
- Interoperable: Works with Coldcard, Ian Coleman tool, and other BIP85-compatible wallets
- Portable: Transfer child mnemonics to hot wallets without exposing your master seed
Use cases:
- Generate separate wallets for different exchanges/services
- Create disposable wallets for testing
- Delegate wallets to family members or business partners
- Maintain one master backup while having multiple operational wallets
python ./src/pyhdwallet.py gen-child [options]
Input options (choose one - required):
--interactive— Word-by-word guided master mnemonic entry (English-only, per-word validation)--mnemonic-stdin— Read master mnemonic from stdin (recommended; avoids shell history)
Child parameters:
--words {12,15,18,21,24}— Child mnemonic word count (default: 12)--index N— BIP85 derivation index (default: 0; use different indexes for different children)--passphrase— Prompt for master BIP39 passphrase (optional but recommended if your master uses one)--passphrase-hint HINT— Store hint only (requires--passphrase)
Output options:
--off-screen— Suppress printing child mnemonic to stdout (prints metadata only)--force— Allow printing to non-TTY stdout (dangerous; see security notes)
File output options:
--file— Write AES-encrypted ZIP artifact to.wallet/(or--wallet-location)--pgp-pubkey-file FILE— PGP-encrypt the inner JSON payload--expected-fingerprint FINGERPRINT— Enforce PGP key pinning--wallet-location PATH— Override default.wallet/directory--zip-password-mode {prompt,auto}— ZIP password entry mode--zip-password-len N— Auto-generated password length (default: 12)--show-generated-password— Print auto-generated ZIP password to stderr
BIP85 Derivation Path:
The tool uses the standard BIP85 path for BIP39 application:
m/83696968'/39'/0'/{words}'/{index}'
83696968'— BIP85 magic constant39'— BIP39 application0'— English language (only supported language in v1.1.0){words}'— Child mnemonic word count (12/15/18/21/24){index}'— Your chosen index
Examples:
# 1. Basic: Derive 12-word child at index 0 (interactive entry)
python src/pyhdwallet.py gen-child --interactive --words 12 --index 0
# 2. Derive 24-word child with passphrase (from stdin)
echo "your master mnemonic words here" | \
python src/pyhdwallet.py gen-child \
--mnemonic-stdin \
--passphrase \
--words 24 \
--index 0
# 3. Secure: Off-screen + PGP encryption + ZIP file
python src/pyhdwallet.py gen-child \
--interactive \
--words 12 \
--index 1 \
--pgp-pubkey-file pubkeys/recipient.asc \
--expected-fingerprint A27B96F2B169B5491013D2DA892B822C14A9AA18 \
--off-screen \
--file
# 4. Multiple children: Derive index 0, 1, 2 for different purposes
for i in 0 1 2; do
echo "master mnemonic" | python src/pyhdwallet.py gen-child \
--mnemonic-stdin --words 12 --index $i --off-screen --file
done
Security Notes:
- Master seed safety: Never expose your master mnemonic to online systems. Use
gen-childonly on air-gapped machines. - Index tracking: Keep a record of which index corresponds to which purpose (e.g., index 0 = exchange A, index 1 = exchange B).
- Passphrase consistency: If your master seed uses a BIP39 passphrase, you MUST provide the same passphrase when deriving children.
- Interoperability verification: Always test child mnemonics in a separate wallet before sending funds to ensure compatibility.
Interoperability:
This implementation follows BIP85 specification and has been verified against official test vectors. Child mnemonics are compatible with:
- Coldcard hardware wallet (BIP85 menu)
- Ian Coleman BIP85 calculator (https://iancoleman.io/bip39/)
- Trezor (when BIP85 support is added)
- Any other BIP85-compliant tool
Output Payload Structure:
When using --file, the encrypted ZIP contains a JSON payload with:
{
"version": "pyhdwallet v1.1.0",
"purpose": "BIP85 derived child mnemonic",
"master_fingerprint": "ABCD1234",
"master_word_count": 24,
"passphrase_used": true,
"passphrase_hint": "my hint",
"bip85_metadata": {
"path": "m/83696968'/39'/0'/12'/0'",
"index": 0,
"language": 0,
"child_word_count": 12
},
"child_mnemonic": "word1 word2 ...",
"interoperability_note": "..."
}
fetchkey (online)
Download and verify a PGP public key from a URL.
python ./src/pyhdwallet.py fetchkey <url> [--out FILE] [--timeout SECONDS] [--off-screen] [--expected-fingerprint FINGERPRINT]
Options:
url: URL to the ASCII-armored PGP public key--out FILE: save key to file--timeout SECONDS: request timeout (default 15)--off-screen: reduced output / temp-file behavior--expected-fingerprint: refuse if downloaded key fingerprint doesn't match (40 hex chars)
gen (offline)
Generate a BIP39 mnemonic and derive addresses.
python ./src/pyhdwallet.py gen [options]
Core options:
--words {12,15,18,21,24}--dice-rolls "1 2 3 ..."(space-separated; adds user entropy)--passphrase(prompts for BIP39 passphrase)--passphrase-hint HINT--chains ethereum solana bitcoin--addresses N--sol-profile {phantom_bip44change,phantom_bip44,phantom_deprecated,solana_bip39_first32}
PGP options:
--pgp-pubkey-file FILE(encrypt payload to pubkey;.asccontent)--pgp-ignore-usage-flags--expected-fingerprint FINGERPRINT(refuse if PGP key fingerprint doesn't match)
Artifact options:
--file(write AES ZIP only)--wallet-location PATH(default:./.wallet)--zip-password-mode {prompt,auto}--zip-password-len N(default: 12)--show-generated-password(auto mode only; prints password to stderr)
Safety options:
--off-screen(don't print sensitive output)--force(only for non-TTY stdout; see below)
recover (offline)
Recover addresses from mnemonic and optionally write a ZIP artifact.
python ./src/pyhdwallet.py recover [options]
Input options (choose one):
--mnemonic-stdin(recommended; read from stdin to avoid shell history)--interactive(word-by-word guided entry with validation)
Additional options:
- same chain/PGP/ZIP/off-screen/force options as
gen --export-private(requires--pgp-pubkey-file; includes private keys in encrypted payload)--include-source(includes mnemonic in payload; requires--pgp-pubkey-file)
Notes:
--export-privaterequires--pgp-pubkey-file(private keys only travel inside encrypted payload).--include-sourcecontrols whether mnemonic is included inside the encrypted payload (only meaningful when PGP is used).
test (offline)
Run minimal self-tests.
python ./src/pyhdwallet.py test
For comprehensive regression testing, use the pytest suite:
pytest -v tests/test_vectors.py
When to Use --force
Use --force only if you intentionally want to print sensitive output while stdout is being piped/redirected.
Why it exists:
- When you pipe/redirect output (e.g.,
>or|), stdout is typically not a TTY. - The program checks
sys.stdout.isatty()and refuses to print secrets by default to avoid accidental leakage into files/logs. isatty()is commonly used to detect terminal vs pipe/redirect.
Examples:
# This redirects stdout to a file (non-TTY). The tool will refuse unless forced.
python ./src/pyhdwallet.py gen > out.txt
# Explicitly override the safety guard (dangerous).
python ./src/pyhdwallet.py gen --force > out.txt
If running normally in your interactive terminal, stdout is a TTY and --force does nothing (output looks the same).
Security Notes (Practical)
genprinting the mnemonic is intentionally "debug/test" behavior. Assume stdout can be recorded (scrollback, logging, screen recording, CI logs).- Prefer
--off-screenfor reduced exposure. - Prefer
--fileso artifacts go into.wallet/and are AES-encrypted viapyzipper. - For stronger at-rest security: combine
--pgp-pubkey-file+--fileso the ZIP contains only an encrypted.ascpayload. PGPy encryption style followsPGPMessage.new(...)thenpubkey.encrypt(...). - Use
--expected-fingerprintto enforce PGP key pinning and prevent key substitution attacks. - Install from vendored wheels on air-gapped machines to avoid supply chain attacks.
Offline Security Best Practices
This tool is designed for offline use, but true security depends on the environment where you run it. Below are operational security recommendations for generating keys you can trust.
Air-Gapped Setup (Highest Security)
For maximum security when generating production wallets, use an air-gapped computer—a device that has never been and will never be connected to any network.
Recommended procedure:
-
Prepare a clean machine:
- Use a dedicated laptop or bootable USB with a fresh Linux installation (e.g., Tails, Ubuntu Live USB)
- Never connect this device to WiFi, Ethernet, or Bluetooth
- Physically disable network interfaces if possible (remove WiFi card, tape over Ethernet port)
-
Transfer repository offline:
# On trusted online machine, clone and verify git clone https://github.com/<your-username>/hdwalletpy.git cd hdwalletpy # Verify checksums cd vendor/linux-x86_64 sha256sum -c SHA256SUMS # Transfer entire repo to USB -
Verify code integrity on air-gapped machine:
# Check checksums again cd vendor/linux-x86_64 sha256sum -c SHA256SUMS # Run test suite to verify derivation logic ./install_offline.sh pytest -v tests/test_vectors.py -
Generate wallet:
python ./src/pyhdwallet.py gen --pgp-pubkey-file pubkeys/recipient.asc --file --off-screen -
Transfer output safely:
- Copy only the
.wallet/*.zipfile to USB (never copypyhdwallet.pyor Python environment back to online machine) - The ZIP is AES-encrypted; the inner
.ascis PGP-encrypted
- Copy only the
-
Destroy or securely wipe the USB after transfer if it contained unencrypted secrets
Threats Air-Gapping Mitigates
- Remote attacks: Malware cannot exfiltrate keys over the network
- Clipboard hijacking: No clipboard manager or remote access tool can intercept data
- Browser/OS telemetry: No accidental upload of terminal history or crash dumps
Threats Air-Gapping Does NOT Fully Mitigate
Research shows that sophisticated attackers with physical access can potentially exfiltrate data from air-gapped systems via covert channels (acoustic, electromagnetic, optical). However:
- These attacks require physical proximity and pre-installed malware
- For individual users (not nation-state targets), air-gapping remains highly effective
- Countermeasures: Use the machine in a secure location, inspect for unfamiliar USB devices, verify software integrity before installation
Physical Security
Mnemonic handling:
- Write the mnemonic on paper immediately; never store it digitally on the air-gapped machine
- Use a metal backup (e.g., Cryptosteel) for fire/water resistance
- Split storage across multiple secure locations if desired (Shamir's Secret Sharing for advanced users)
Device handling:
- After generating the wallet, optionally wipe the air-gapped device or destroy the bootable USB
- If reusing the device, use secure-erase tools (not just
rm)
Verification Through Testing
Why the test suite matters for trust:
The committed test suite (tests/test_vectors.py) allows you to verify that derivation logic hasn't been tampered with before generating production keys:
# On the air-gapped machine, before generating your wallet:
pytest -v tests/test_vectors.py
If all tests pass, you have cryptographic proof that:
- BIP39 seed derivation matches the well-known test vector ("abandon abandon..." → known seed)
- Derivation paths produce addresses matching public BIP39/BIP44 test vectors
- PGP fingerprinting logic works correctly
If tests fail, do not generate keys—the code may be compromised or buggy.
Entropy Sources
Built-in entropy (default):
- Python's
secrets.token_bytes()uses OS-provided CSPRNG (/dev/urandomon Linux,CryptGenRandomon Windows) - This is cryptographically secure for typical use
Additional user entropy (optional):
python ./src/pyhdwallet.py gen --dice-rolls "4 2 6 1 3 5 ..." --file
- Roll a die 50+ times and input the results
- Your dice rolls are mixed with OS entropy via SHA256
- Protects against potential CSPRNG backdoors (theoretical concern)
PGP Key Fingerprint Pinning
Always use --expected-fingerprint when encrypting to a PGP key to prevent key substitution attacks:
# First, get and verify the fingerprint of your recipient key
python ./src/pyhdwallet.py fetchkey https://example.com/mykey.asc --out mykey.asc
# Manually verify fingerprint matches what you expect (check via another channel)
# Then use it with pinning:
python ./src/pyhdwallet.py gen \
--pgp-pubkey-file mykey.asc \
--expected-fingerprint A27B96F2B169B5491013D2DA892B822C14A9AA18 \
--file
Without --expected-fingerprint, an attacker who controls the filesystem could swap mykey.asc with their own key.
Operational Checklist
Before generating production wallets:
- Running on air-gapped machine or fresh Live USB
- Network physically disabled (no WiFi/Ethernet/Bluetooth)
- Code integrity verified (checksums + test suite passes)
- Using
--off-screento minimize terminal scrollback exposure - Using
--fileto avoid leaving unencrypted files on disk - Using
--pgp-pubkey-filewith--expected-fingerprintfor key pinning - Paper/metal backup prepared for mnemonic
- Output ZIP password stored separately from ZIP file
- Plan for USB secure wipe after transfer
Lower-Risk Scenarios
Air-gapping is overkill for:
- Learning/testing: Use your regular laptop with
--off-screenand--file - Small amounts: Generate on a clean, updated machine with minimal software
- Testnet wallets: Standard laptop is fine
Air-gapping is recommended for:
- Life savings or business funds
- Long-term cold storage (multi-year hold)
- Institutional custody scenarios
Trust But Verify
The only way to fully trust this tool (or any wallet software) is to:
- Read the source code (
src/pyhdwallet.pyis ~1400 lines, single file) - Verify test vectors match published BIP39 test data
- Run the test suite on your air-gapped machine
- Generate a test wallet and verify addresses on a block explorer using a separate tool
Never trust wallet software blindly—especially for significant funds.
Troubleshooting
"Permission denied" running ./src/pyhdwallet.py
Run via python, or set executable bit:
python ./src/pyhdwallet.py gen
# or
chmod +x ./src/pyhdwallet.py
./src/pyhdwallet.py gen
Python 3.12 not found
Vendored wheels require Python 3.12:
# macOS
brew install python@3.12
# Ubuntu/Debian
sudo apt-get install python3.12 python3.12-venv
# Verify
python3.12 --version
"Missing dependency" errors
Make sure you're installing from the correct vendor directory:
# Check your platform
uname -sm
# Install for macOS ARM64
pip install --no-index --find-links=vendor/macos-arm64 -r requirements.txt
# Install for Linux x86_64
pip install --no-index --find-links=vendor/linux-x86_64 -r requirements.txt
Checksum verification failures
If SHA256SUMS verification fails, the wheels may be corrupted or tampered with:
cd vendor/linux-x86_64
sha256sum -c SHA256SUMS
# If failures occur, re-download from trusted source
# DO NOT use corrupted wheels for production wallets
Unzipping AES ZIP issues
Some unzip tools may not support AES-encrypted ZIPs. Use Python + pyzipper to extract:
python - <<'PY'
import pyzipper
zip_path = ".wallet/your_wallet.zip"
out_dir = "unzip_out"
pw = input("ZIP password: ").encode()
with pyzipper.AESZipFile(zip_path) as zf:
zf.pwd = pw
zf.extractall(out_dir)
print("Extracted to", out_dir)
PY
Test failures after code changes
If pytest -v tests/test_vectors.py fails after you modified code:
-
Did you intentionally change derivation logic?
- Yes → Regenerate vectors:
rm tests/vectors.json && python3 tests/bootstrap_vectors.py - No → You have a regression bug; revert your changes and fix the code
- Yes → Regenerate vectors:
-
Is only one test failing?
- Run with
-vvfor detailed diff:pytest -vv tests/test_vectors.py::test_name
- Run with
Development
Building Vendor Wheels
See Vendored Dependencies section above.
Running Tests (Dev)
# Quick test
python src/pyhdwallet.py test
# Full regression suite
pytest -v tests/test_vectors.py
# Specific test
pytest -v tests/test_vectors.py::test_address_derivation_integrity
# With coverage
pytest --cov=src tests/
Docker Development Environment
# Build image
make build-image
# Start warm container
make up
make shell
# Inside container
python src/pyhdwallet.py test
Changelog
-
v1.1.0 (2026-01-09)
- Added: BIP85 child mnemonic derivation via new
gen-childcommand - Supports deriving 12/15/18/21/24-word children from master seed
- Optional master BIP39 passphrase support
- Full interoperability with BIP85-compatible wallets (Coldcard, Ian Coleman tool)
- Verified against official BIP85 test vectors
- Added BIP85 regression tests to test suite
- No new dependencies required (uses existing
bip-utils+ stdlib) - BIP85 path:
m/83696968'/39'/0'/{words}'/{index}'
- Added: BIP85 child mnemonic derivation via new
-
v1.0.5
--unsafe-printremoved;genprints mnemonic by default (debug/test behavior).--outputremoved; file payload is always JSON (unencrypted) or.asc(PGP encrypted).--fileis now a boolean flag that writes only AES-encrypted ZIP artifacts (no raw output files).- Added:
--wallet-location,--zip-password-mode,--zip-password-len,--show-generated-password. - Added:
--forceto override the non-TTY printing safety guard (use only when redirecting/piping). - Added:
--expected-fingerprintfor PGP key pinning. - Added: Offline regression test suite with frozen vectors (
tests/test_vectors.py). - Added: Vendored dependencies for macOS ARM64 and Linux x86_64 (Python 3.12).
- Added:
install_offline.shautomated installer for air-gapped deployment. - Changed:
recovernow uses--mnemonic-stdinand--interactiveinstead of--mnemonicCLI arg. - Updated: Dockerfile and Makefile now support vendoring workflow.
-
v1.0.3 Documentation and CLI behavior updates.
-
v1.0.2 Added off-screen behaviors and security improvements.
-
v1.0.1 Renamed to pyhdwallet and added version flag.
-
v1.0.0 Initial release.