diff --git a/.gitignore b/.gitignore index 12ae2d5..5810a44 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ __pycache__/ logs/ *.log coverage/ +_toDelete/ dist/ build/ *.egg-info/ diff --git a/playbook.md b/playbook.md index 1170be6..aabda16 100644 --- a/playbook.md +++ b/playbook.md @@ -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 64‑byte 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 [--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/.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 (don’t 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 **don’t 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 you’re 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 don’t 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/.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 don’t 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) \ No newline at end of file +```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. diff --git a/requirements.txt b/requirements.txt index aad1a8b..3d7db8b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/src/pyhdwallet.py b/src/pyhdwallet.py index f31a9c1..76ec7ba 100644 --- a/src/pyhdwallet.py +++ b/src/pyhdwallet.py @@ -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 diff --git a/test_output.json b/test_output.json new file mode 100644 index 0000000..bccc9b9 --- /dev/null +++ b/test_output.json @@ -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" + } + ] + } +} \ No newline at end of file