Files
pyhdwallet/playbook.md
2026-01-07 00:40:47 +08:00

307 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# pyhdwallet v1.0.5 (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 script
- `.wallet/` — generated wallet artifacts (should be gitignored)
- `requirements.in`, `requirements.txt`
- `README.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`, and `test` block 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`). [web:46]
- **AES-encrypted ZIP artifacts**: When `--file` is used, output is written as an AES-encrypted ZIP via `pyzipper`. [web:102]
- **TTY safety guard + --force**: If stdout is piped/redirected (non-TTY), the tool refuses to print sensitive data unless `--force` is 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-screen` suppresses printing sensitive data to stdout.
---
## Installation
### Prerequisites
- Python 3.11+
- Recommended: a virtual environment
### Setup
```bash
python -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
```
### Dependencies (top-level intent)
- `bip-utils` — BIP39 + BIP derivation logic
- `PGPy` — encryption to OpenPGP public keys [web:46]
- `pynacl` + `base58` — Solana seed/key handling
- `pyzipper` — AES-encrypted ZIP writing (only needed when using `--file`) [web:102]
---
## Quick Start
### 1) Generate (debug/test; prints mnemonic)
```bash
python ./src/pyhdwallet.py gen
```
### 2) Generate and save AES ZIP artifact to `.wallet/`
```bash
python ./src/pyhdwallet.py gen --file
```
### 3) Generate with PGP encryption and ZIP (recommended for “at-rest” storage)
```bash
python ./src/pyhdwallet.py gen --pgp-pubkey-file pubkeys/mykey.asc --file
```
### 4) Recover (addresses) from mnemonic
```bash
python ./src/pyhdwallet.py recover --mnemonic "abandon abandon ... about"
```
### 5) Recover with interactive input + ZIP artifact
```bash
python ./src/pyhdwallet.py recover --interactive --file
```
### 6) Run tests
```bash
python ./src/pyhdwallet.py test
```
---
## Outputs and file structure
### Stdout behavior
- `gen` prints the mnemonic + derived addresses by default (intended for debug/test comparisons).
- `--off-screen` suppresses 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`. [web:102]
Default output folder:
- `./.wallet/`
Override folder:
- `--wallet-location /path/to/folder`
Naming uses UTC timestamps (e.g. `20260106_161830Z`):
- No PGP: zip contains `test_wallet_<UTC>.json`, zip name `test_wallet_<UTC>.zip`
- With PGP: zip contains `encrypted_wallet_<UTC>.asc`, zip name `encrypted_wallet_<UTC>.zip`
### Password handling for ZIP
- Default: `--zip-password-mode prompt` prompts 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 auto` generates a Base58 password (length controlled by `--zip-password-len`).
- If auto mode is used, password is shown **only if** `--show-generated-password` is 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. [web:102]
---
## Commands
### fetchkey (online)
Download and verify a PGP public key from a URL.
```bash
python ./src/pyhdwallet.py fetchkey <url> [--out FILE] [--timeout SECONDS] [--off-screen]
```
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
---
### gen (offline)
Generate a BIP39 mnemonic and derive addresses.
```bash
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; `.asc` content) [web:46]
- `--pgp-ignore-usage-flags`
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` (dont print sensitive output)
- `--force` (only for non-TTY stdout; see below)
---
### recover (offline)
Recover addresses from mnemonic or seed and optionally write a ZIP artifact.
```bash
python ./src/pyhdwallet.py recover [options]
```
Input options (choose one):
- `--mnemonic "..."` (not recommended due to shell history; prefer `--interactive`)
- `--seed HEX` (128 hex chars / 64 bytes)
- `--interactive`
Additional options:
- same chain/PGP/ZIP/off-screen/force options as `gen`
Notes:
- `--export-private` requires `--pgp-pubkey-file` (private keys only travel inside encrypted payload).
- `--include-source` controls whether mnemonic/seed is included inside the encrypted payload (only meaningful when PGP is used).
---
### test (offline)
Run minimal self-tests.
```bash
python ./src/pyhdwallet.py test
```
---
## 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:
```bash
# 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)
- `gen` printing the mnemonic is intentionally “debug/test” behavior. Assume stdout can be recorded (scrollback, logging, screen recording, CI logs).
- Prefer `--off-screen` for reduced exposure.
- Prefer `--file` so artifacts go into `.wallet/` and are AES-encrypted via `pyzipper`. [web:102]
- For stronger at-rest security: combine `--pgp-pubkey-file` + `--file` so the ZIP contains only an encrypted `.asc` payload. PGPy encryption style follows `PGPMessage.new(...)` then `pubkey.encrypt(...)`. [web:46]
---
## Troubleshooting
### “Permission denied” running `./src/pyhdwallet.py`
Run via python, or set executable bit:
```bash
python ./src/pyhdwallet.py gen
# or
chmod +x ./src/pyhdwallet.py
./src/pyhdwallet.py gen
```
### “Missing dependency: pyzipper” but pip says installed
You likely installed into a different venv. Use:
```bash
python -m pip install pyzipper
python -c "import pyzipper; print(pyzipper.__version__)"
```
### Unzipping AES ZIP issues
Some `unzip` tools may not support AES-encrypted ZIPs. Use Python + pyzipper to extract if needed:
```bash
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
```
AES ZIP support is the reason `pyzipper` is used. [web:102]
---
## Changelog
- **v1.0.5**
- `--unsafe-print` removed; `gen` prints mnemonic by default (debug/test behavior).
- `--output` removed; file payload is always JSON (unencrypted) or `.asc` (PGP encrypted). [web:46]
- `--file` is now a boolean flag that writes **only** AES-encrypted ZIP artifacts (no raw output files). [web:102]
- Added: `--wallet-location`, `--zip-password-mode`, `--zip-password-len`, `--show-generated-password`.
- Added: `--force` to override the non-TTY printing safety guard (use only when redirecting/piping).
- **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.