Release v1.0.1: Add --version flag, update documentation to full README, exclude _toDelete in .gitignore, rename program to pyhdwallet

This commit is contained in:
LC
2026-01-05 06:07:48 +00:00
parent 4d1ec957c7
commit 2124b96446
5 changed files with 215 additions and 123 deletions

1
.gitignore vendored
View File

@@ -8,6 +8,7 @@ __pycache__/
logs/
*.log
coverage/
_toDelete/
dist/
build/
*.egg-info/

View File

@@ -1,174 +1,248 @@
Below is a practical playbook you can save as `PLAYBOOK.md` next to `hdwallet_recovery.py`.
# pyhdwallet v1.0.1
## Purpose (what this tool does)
- **Derive addresses** (ETH/SOL/BTC) from either a BIP39 mnemonic (+ optional passphrase) or a raw 64byte BIP39 seed hex.
- Optionally **encrypt a payload** to a PGP public key (ASCII armored) so secrets are not shown in plaintext on screen.
- `fetchkey` mode is the only mode that touches the network (downloads a PGP public key).
A command-line tool for generating and recovering HD wallets (BIP39) with support for Ethereum, Solana, and Bitcoin. Features offline operation, PGP encryption, and multi-chain address derivation.
## Prerequisites (software + packages)
## Table of Contents
### OS / Python
- Use **Python 3.12** (recommended for compatibility with `bip_utils`, `PGPy`, etc.).
- macOS: install Python 3.12 via Homebrew and create a clean venv.
- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Commands](#commands)
- [fetchkey](#fetchkey)
- [gen](#gen)
- [recover](#recover)
- [test](#test)
- [Examples](#examples)
- [Security](#security)
- [Troubleshooting](#troubleshooting)
- [Changelog](#changelog)
### Python packages
Install into a venv:
## Features
**Base (derive addresses + encrypt payload):**
- `bip-utils`
- `PGPy`
- **Offline-first**: Generate and recover wallets without internet access.
- **Multi-chain support**: Derive addresses for Ethereum, Solana, and Bitcoin.
- **PGP encryption**: Securely encrypt sensitive data (mnemonics, private keys) to PGP public keys.
- **Flexible input**: Accept BIP39 mnemonics or hex seeds.
- **BIP39 passphrase support**: Optional passphrase for additional security.
- **Private key export**: Export derived private keys in encrypted payloads.
- **Solana profiles**: Multiple derivation paths for Solana compatibility.
- **Self-testing**: Built-in tests to verify functionality.
**Only needed if you use `--export-private` and include `solana`:**
- `PyNaCl`
- `base58`
## Installation
### Prerequisites
- Python 3.11 or higher
- Virtual environment (recommended)
### Setup
1. Clone or download the repository.
2. Create a virtual environment:
```bash
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
```
3. Install dependencies:
```bash
pip install -r requirements.txt
```
### Dependencies
- `bip-utils`: BIP39 and derivation logic
- `PGPy`: PGP encryption
- `PyNaCl` & `base58`: Solana private key handling (optional for Solana private key export)
## Quick Start
1. Generate a new wallet:
```bash
python ./src/pyhdwallet.py gen --chains ethereum solana --addresses 3
```
2. Recover from a mnemonic:
```bash
python ./src/pyhdwallet.py recover --mnemonic "abandon abandon ... about" --chains bitcoin
```
3. Fetch a PGP key:
```bash
python ./src/pyhdwallet.py fetchkey "https://example.com/key.asc" --out mykey.asc
```
4. Run tests:
```bash
python ./src/pyhdwallet.py test
```
## Commands
### fetchkey
Download and verify a PGP public key from a URL.
**Usage:**
Example:
```bash
python -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install bip-utils PGPy
pip install PyNaCl base58 # only if exporting Solana private keys
python ./src/pyhdwallet.py fetchkey <url> [--out FILE] [--timeout SECONDS]
```
## Files you need
- `hdwallet_recovery.py` (the script)
- `kccleoc.asc` (or any `*.asc`) = ASCII-armored **PGP public key** used to encrypt the payload
**Options:**
## Operating modes
- `url`: URL to the ASCII-armored PGP key
- `--out FILE`: Save the key to a file
- `--timeout SECONDS`: Request timeout (default: 15)
### Mode A — fetchkey (online, no secrets allowed)
Use this to download a PGP public key from a URL and verify it.
**Example:**
**Command:**
```bash
python hdwallet_recovery.py fetchkey "https://github.com/<user>.gpg" --out key.asc
python ./src/pyhdwallet.py fetchkey "https://keys.openpgp.org/pks/lookup?op=get&search=user@example.com" --out key.asc
```
**What to check:**
- The script prints **SHA256** of the downloaded key and the **PGP fingerprint**.
- Independently verify the fingerprint matches the intended owner (dont trust only the URL).
### gen
**Safety rule:**
- Never pass mnemonic/seed/passphrase flags together with `fetchkey`. The script should refuse.
Generate a new BIP39 mnemonic and derive addresses.
***
**Usage:**
### Mode B — derive (offline, addresses only)
Derives addresses and prints them to stdout.
**Command (addresses only):**
```bash
python hdwallet_recovery.py \
--mnemonic '... your words ...' \
--passphrase '' \
--chains ethereum solana bitcoin \
--addresses 10
python ./src/pyhdwallet.py gen [options]
```
**Notes:**
- This prints addresses (safe) but still requires you to supply the mnemonic (sensitive) on the command line unless you use `--interactive`.
**Options:**
- `--words {12,15,18,21,24}`: Number of mnemonic words (default: 12)
- `--dice-rolls "1 2 3 ..."`: Space-separated dice rolls for entropy
- `--passphrase PASSPHRASE`: BIP39 passphrase
- `--passphrase-hint HINT`: Hint for the passphrase
- `--chains {ethereum,solana,bitcoin}`: Chains to derive (default: all)
- `--addresses N`: Number of addresses per chain (default: 5)
- `--sol-profile {phantom_bip44change,phantom_bip44,phantom_deprecated,solana_bip39_first32}`: Solana derivation profile
- `--output {text,json}`: Output format (default: text)
- `--file FILE`: Save output to file
- `--pgp-pubkey-file FILE`: Encrypt payload to PGP key
- `--pgp-ignore-usage-flags`: Ignore PGP key usage flags
- `--export-private`: Include private keys in encrypted payload
- `--include-source`: Include mnemonic in encrypted payload
- `--unsafe-print`: Print mnemonic even when encrypting
**Examples:**
**Preferred (avoid shell history):**
```bash
python hdwallet_recovery.py --interactive --chains ethereum solana bitcoin --addresses 10
# Basic generation
python ./src/pyhdwallet.py gen
# With passphrase and encryption
python ./src/pyhdwallet.py gen --passphrase "mysecret" --pgp-pubkey-file key.asc --export-private
# JSON output to file
python ./src/pyhdwallet.py gen --chains ethereum --addresses 10 --output json --file wallet.json
```
***
### recover
### Mode C — derive + PGP encrypt payload (recommended)
Derives addresses, prints addresses, and prints an **encrypted PGP block** containing recovery material.
Derive addresses from an existing mnemonic or seed.
**Usage:**
**Command (include mnemonic + passphrase):**
```bash
python hdwallet_recovery.py \
--interactive \
--chains ethereum solana bitcoin \
--addresses 10 \
--pgp-pubkey-file key.asc
python ./src/pyhdwallet.py recover [options]
```
**Decrypt later:**
- Use your PGP private key (on a safe machine) to decrypt the PGP message.
**Options:** Same as `gen`, plus:
***
- `--mnemonic MNEMONIC`: BIP39 mnemonic phrase
- `--seed HEX_SEED`: 128-character hex seed
- `--interactive`: Prompt for mnemonic/seed interactively
### Mode D — derive + export private keys (encrypted only)
This is for when you need to import per-account keys into hot wallets (e.g., Phantom) but **dont want to type the seed phrase into the app**.
**Examples:**
**Behavior (as implemented):**
- Still prints addresses to stdout.
- Produces a PGP-encrypted payload that includes:
- the mnemonic (so you can fully recover later)
- a note that a passphrase was used (but not the passphrase)
- **Ethereum private keys** (hex) for indices `0..--addresses-1`
- **Solana Phantom-compatible private keys** (base58 64-byte secret key) for indices `0..--addresses-1`
- **Bitcoin: addresses only** (no BTC private keys)
**Command:**
```bash
python hdwallet_recovery.py \
--interactive \
--chains ethereum solana bitcoin \
--addresses 10 \
--export-private \
--passphrase 'YOUR_PASSPHRASE' \
--passphrase-hint 'memory hint here' \
--pgp-pubkey-file key.asc
# From mnemonic
python ./src/pyhdwallet.py recover --mnemonic "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" --chains ethereum solana
# Interactive input
python ./src/pyhdwallet.py recover --interactive --chains bitcoin
# From seed
python ./src/pyhdwallet.py recover --seed "0123456789abcdef..." --chains solana
```
**Hot wallet import guidance:**
- Import only the specific derived account key you plan to treat as “hot”.
- Fund only that account/address.
- Assume the device/app is compromised eventually; rotate keys.
### test
## Verification checklist (before trusting results)
- Confirm youre using the expected Python and venv:
```bash
which python
python -V
pip show bip-utils PGPy
```
- Confirm the PGP public key fingerprint is correct (out-of-band verified).
- Confirm derived addresses match known wallet UI for the same mnemonic/passphrase (test with a small index range first).
Run minimal self-tests to verify functionality.
## Security warnings (read this every time)
- **Never** run derive mode on a machine you dont trust.
- Avoid passing mnemonics on the command line (`--mnemonic '...'`) because:
- shell history may capture it
- process lists can expose arguments
- Prefer `--interactive` so the mnemonic is hidden input.
- The encrypted payload printed to screen can still be:
- copied into scrollback logs
- captured by screen recording / monitoring
- saved by terminal multiplexer logs
Treat it as sensitive, even if encrypted.
- If `--export-private` is used, the encrypted payload contains a **bundle of hot private keys**. Anyone who decrypts it controls those accounts. Keep it offline and limit distribution.
- If a **passphrase** was used, losing it makes recovery impossible even with mnemonic and derived-address list. Store the passphrase separately and securely; the payload only stores a hint/reminder.
- Consider using a dedicated “hot” seed (separate mnemonic) for accounts intended for hot-wallet import, rather than exporting keys derived from your main long-term seed.
**Usage:**
## Quick command recipes
**1) Download and pin a key:**
```bash
python hdwallet_recovery.py fetchkey "https://github.com/<user>.gpg" --out key.asc
python ./src/pyhdwallet.py test
```
**2) Offline derive addresses (no encryption):**
**Output:** Success/failure messages for derivation tests.
## Examples
### 1. Generate and Encrypt a New Wallet
```bash
python hdwallet_recovery.py --interactive --chains ethereum solana bitcoin --addresses 10
# Generate with encryption
python ./src/pyhdwallet.py gen --pgp-pubkey-file key.asc --include-source --export-private --chains ethereum solana --addresses 5
# Decrypt the output later with GPG
echo "-----BEGIN PGP MESSAGE-----..." | gpg -d
```
**3) Offline derive + encrypt payload (no private key export):**
### 2. Recover from Mnemonic with Passphrase
```bash
python hdwallet_recovery.py --interactive --chains ethereum solana bitcoin --addresses 10 --pgp-pubkey-file key.asc
python ./src/pyhdwallet.py recover --mnemonic "word1 word2 ... word12" --passphrase "mypass" --chains ethereum --addresses 10 --output json
```
**4) Offline derive + encrypt payload + export ETH/SOL private keys:**
### 3. Fetch and Use PGP Key
```bash
python hdwallet_recovery.py --interactive --chains ethereum solana bitcoin --addresses 10 --export-private --passphrase-hint '...' --pgp-pubkey-file key.asc
# Fetch key
python ./src/pyhdwallet.py fetchkey "https://example.com/pubkey.asc" --out mykey.asc
# Use in recovery
python ./src/pyhdwallet.py recover --interactive --pgp-pubkey-file mykey.asc --export-private
```
If you want, the playbook can be turned into a `Makefile` (targets: `venv`, `fetchkey`, `derive`, `export`) so you dont have to remember flags.
### 4. Solana-Specific Derivation
[1](https://ppl-ai-file-upload.s3.amazonaws.com/web/direct-files/attachments/50321846/61044779-f904-4af9-9005-77f3f639e4d6/offline_HD_wallet_generator.html)
```bash
python ./src/pyhdwallet.py gen --chains solana --sol-profile phantom_bip44change --addresses 3
```
## Security
- **Offline operation**: `gen`, `recover`, and `test` commands block network access.
- **No plaintext secrets**: Mnemonics and private keys are never printed unless encrypted or `--unsafe-print` is used.
- **PGP encryption**: Use for secure storage of sensitive data.
- **Passphrase handling**: Passphrases are not stored; only hints are included.
- **Private key export**: Only export what's needed; treat encrypted payloads as sensitive.
- **Best practices**:
- Use `--interactive` to avoid command-line history exposure.
- Verify PGP fingerprints out-of-band.
- Run on trusted, offline machines.
## Troubleshooting
- **Missing dependencies**: Run `pip install -r requirements.txt`
- **Network errors in offline modes**: Ensure no internet access; the tool blocks it.
- **Invalid mnemonic**: Check word count and spelling.
- **PGP decryption fails**: Ensure you have the correct private key.
- **Version check**: Run `python ./src/pyhdwallet.py --version`
## Changelog
- **v1.0.1**: Renamed to pyhdwallet, added --version flag, updated documentation, excluded _toDelete in .gitignore.
- **v1.0.0**: Initial release with gen, recover, fetchkey, and test commands.

View File

@@ -2,7 +2,7 @@
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile ./requirements.in
# pip-compile
#
bip-utils==2.10.0
# via -r requirements.in

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""
hdwallet_recovery.py (Python 3.12)
pyhdwallet v1.0.1 (Python 3.11+)
Commands:
- fetchkey (online): download ASCII-armored PGP public key and print SHA256 + fingerprint
@@ -523,6 +523,7 @@ def cmd_test(args):
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="HD wallet gen/recover (offline) + fetchkey (online)")
parser.add_argument("--version", action="version", version="pyhdwallet v1.0.1")
sub = parser.add_subparsers(dest="cmd")
# fetchkey

16
test_output.json Normal file
View File

@@ -0,0 +1,16 @@
{
"master_fingerprint": "DD1449B7",
"passphrase_used": false,
"passphrase_hint": "",
"dice_rolls_used": false,
"solana_profile": "phantom_bip44change",
"addresses": {
"ethereum": [
{
"index": 0,
"path": "m/44'/60'/0'/0/0",
"address": "0x9d3e3540f4C507ca992035607326798130051e03"
}
]
}
}