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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ __pycache__/
|
|||||||
logs/
|
logs/
|
||||||
*.log
|
*.log
|
||||||
coverage/
|
coverage/
|
||||||
|
_toDelete/
|
||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
|
|||||||
316
playbook.md
316
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)
|
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.
|
||||||
- **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).
|
|
||||||
|
|
||||||
## Prerequisites (software + packages)
|
## Table of Contents
|
||||||
|
|
||||||
### OS / Python
|
- [Features](#features)
|
||||||
- Use **Python 3.12** (recommended for compatibility with `bip_utils`, `PGPy`, etc.).
|
- [Installation](#installation)
|
||||||
- macOS: install Python 3.12 via Homebrew and create a clean venv.
|
- [Quick Start](#quick-start)
|
||||||
|
- [Commands](#commands)
|
||||||
|
- [fetchkey](#fetchkey)
|
||||||
|
- [gen](#gen)
|
||||||
|
- [recover](#recover)
|
||||||
|
- [test](#test)
|
||||||
|
- [Examples](#examples)
|
||||||
|
- [Security](#security)
|
||||||
|
- [Troubleshooting](#troubleshooting)
|
||||||
|
- [Changelog](#changelog)
|
||||||
|
|
||||||
### Python packages
|
## Features
|
||||||
Install into a venv:
|
|
||||||
|
|
||||||
**Base (derive addresses + encrypt payload):**
|
- **Offline-first**: Generate and recover wallets without internet access.
|
||||||
- `bip-utils`
|
- **Multi-chain support**: Derive addresses for Ethereum, Solana, and Bitcoin.
|
||||||
- `PGPy`
|
- **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`:**
|
## Installation
|
||||||
- `PyNaCl`
|
|
||||||
- `base58`
|
### 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
|
```bash
|
||||||
python -m venv .venv
|
python ./src/pyhdwallet.py fetchkey <url> [--out FILE] [--timeout SECONDS]
|
||||||
source .venv/bin/activate
|
|
||||||
pip install -U pip
|
|
||||||
pip install bip-utils PGPy
|
|
||||||
pip install PyNaCl base58 # only if exporting Solana private keys
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Files you need
|
**Options:**
|
||||||
- `hdwallet_recovery.py` (the script)
|
|
||||||
- `kccleoc.asc` (or any `*.asc`) = ASCII-armored **PGP public key** used to encrypt the payload
|
|
||||||
|
|
||||||
## 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)
|
**Example:**
|
||||||
Use this to download a PGP public key from a URL and verify it.
|
|
||||||
|
|
||||||
**Command:**
|
|
||||||
```bash
|
```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:**
|
### gen
|
||||||
- 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).
|
|
||||||
|
|
||||||
**Safety rule:**
|
Generate a new BIP39 mnemonic and derive addresses.
|
||||||
- Never pass mnemonic/seed/passphrase flags together with `fetchkey`. The script should refuse.
|
|
||||||
|
|
||||||
***
|
**Usage:**
|
||||||
|
|
||||||
### Mode B — derive (offline, addresses only)
|
|
||||||
Derives addresses and prints them to stdout.
|
|
||||||
|
|
||||||
**Command (addresses only):**
|
|
||||||
```bash
|
```bash
|
||||||
python hdwallet_recovery.py \
|
python ./src/pyhdwallet.py gen [options]
|
||||||
--mnemonic '... your words ...' \
|
|
||||||
--passphrase '' \
|
|
||||||
--chains ethereum solana bitcoin \
|
|
||||||
--addresses 10
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Notes:**
|
**Options:**
|
||||||
- This prints addresses (safe) but still requires you to supply the mnemonic (sensitive) on the command line unless you use `--interactive`.
|
|
||||||
|
- `--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
|
```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)
|
Derive addresses from an existing mnemonic or seed.
|
||||||
Derives addresses, prints addresses, and prints an **encrypted PGP block** containing recovery material.
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
**Command (include mnemonic + passphrase):**
|
|
||||||
```bash
|
```bash
|
||||||
python hdwallet_recovery.py \
|
python ./src/pyhdwallet.py recover [options]
|
||||||
--interactive \
|
|
||||||
--chains ethereum solana bitcoin \
|
|
||||||
--addresses 10 \
|
|
||||||
--pgp-pubkey-file key.asc
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Decrypt later:**
|
**Options:** Same as `gen`, plus:
|
||||||
- Use your PGP private key (on a safe machine) to decrypt the PGP message.
|
|
||||||
|
|
||||||
***
|
- `--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)
|
**Examples:**
|
||||||
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**.
|
|
||||||
|
|
||||||
**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
|
```bash
|
||||||
python hdwallet_recovery.py \
|
# From mnemonic
|
||||||
--interactive \
|
python ./src/pyhdwallet.py recover --mnemonic "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" --chains ethereum solana
|
||||||
--chains ethereum solana bitcoin \
|
|
||||||
--addresses 10 \
|
# Interactive input
|
||||||
--export-private \
|
python ./src/pyhdwallet.py recover --interactive --chains bitcoin
|
||||||
--passphrase 'YOUR_PASSPHRASE' \
|
|
||||||
--passphrase-hint 'memory hint here' \
|
# From seed
|
||||||
--pgp-pubkey-file key.asc
|
python ./src/pyhdwallet.py recover --seed "0123456789abcdef..." --chains solana
|
||||||
```
|
```
|
||||||
|
|
||||||
**Hot wallet import guidance:**
|
### test
|
||||||
- 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.
|
|
||||||
|
|
||||||
## Verification checklist (before trusting results)
|
Run minimal self-tests to verify functionality.
|
||||||
- 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).
|
|
||||||
|
|
||||||
## Security warnings (read this every time)
|
**Usage:**
|
||||||
- **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.
|
|
||||||
|
|
||||||
## Quick command recipes
|
|
||||||
|
|
||||||
**1) Download and pin a key:**
|
|
||||||
```bash
|
```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
|
```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
|
```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
|
```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)
|
```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.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# This file is autogenerated by pip-compile with Python 3.11
|
# This file is autogenerated by pip-compile with Python 3.11
|
||||||
# by the following command:
|
# by the following command:
|
||||||
#
|
#
|
||||||
# pip-compile ./requirements.in
|
# pip-compile
|
||||||
#
|
#
|
||||||
bip-utils==2.10.0
|
bip-utils==2.10.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
hdwallet_recovery.py (Python 3.12)
|
pyhdwallet v1.0.1 (Python 3.11+)
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
- fetchkey (online): download ASCII-armored PGP public key and print SHA256 + fingerprint
|
- 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:
|
def build_parser() -> argparse.ArgumentParser:
|
||||||
parser = argparse.ArgumentParser(description="HD wallet gen/recover (offline) + fetchkey (online)")
|
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")
|
sub = parser.add_subparsers(dest="cmd")
|
||||||
|
|
||||||
# fetchkey
|
# fetchkey
|
||||||
|
|||||||
16
test_output.json
Normal file
16
test_output.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user