From ce26b3560a95389dea0f0aebdf14d8839f2fed96 Mon Sep 17 00:00:00 2001 From: LC mac Date: Wed, 7 Jan 2026 00:40:47 +0800 Subject: [PATCH] modified doc to v1.0.5 --- playbook.md | 422 +++++++++++++++++++++++++++------------------------- 1 file changed, 222 insertions(+), 200 deletions(-) diff --git a/playbook.md b/playbook.md index 9fc48c2..18ee3cb 100644 --- a/playbook.md +++ b/playbook.md @@ -1,284 +1,306 @@ -# pyhdwallet v1.0.3 +# pyhdwallet v1.0.5 (hdwalletpy) -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. +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 ` -h` for detailed options. -## Table of Contents - -- [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) +--- ## Features -- **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. -- **Off-screen mode**: Optional paranoid mode with memory zeroing, temp files, and no output printing for high-security use. +- **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 or higher -- Virtual environment (recommended) +- Python 3.11+ +- Recommended: a virtual environment ### Setup -1. Clone or download the repository. -2. Create a virtual environment: +```bash +python -m venv .venv +source .venv/bin/activate +python -m pip install -r requirements.txt +``` - ```bash - python -m venv .venv - source .venv/bin/activate # On Windows: .venv\Scripts\activate - ``` +### Dependencies (top-level intent) -3. Install dependencies: +- `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] - ```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: +### 1) Generate (debug/test; prints mnemonic) - ```bash - python ./src/pyhdwallet.py gen --chains ethereum solana --addresses 3 - ``` +```bash +python ./src/pyhdwallet.py gen +``` -2. Recover from a mnemonic: +### 2) Generate and save AES ZIP artifact to `.wallet/` - ```bash - python ./src/pyhdwallet.py recover --mnemonic "abandon abandon ... about" --chains bitcoin - ``` +```bash +python ./src/pyhdwallet.py gen --file +``` -3. Fetch a PGP key: +### 3) Generate with PGP encryption and ZIP (recommended for “at-rest” storage) - ```bash - python ./src/pyhdwallet.py fetchkey "https://example.com/key.asc" --out mykey.asc - ``` +```bash +python ./src/pyhdwallet.py gen --pgp-pubkey-file pubkeys/mykey.asc --file +``` -4. Use off-screen mode for high-security operations: +### 4) Recover (addresses) from mnemonic - ```bash - python ./src/pyhdwallet.py gen --off-screen --pgp-pubkey-file key.asc --chains ethereum --addresses 1 - ``` +```bash +python ./src/pyhdwallet.py recover --mnemonic "abandon abandon ... about" +``` -5. Run tests: +### 5) Recover with interactive input + ZIP artifact - ```bash - python ./src/pyhdwallet.py test - ``` +```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_.json`, zip name `test_wallet_.zip` +- With PGP: zip contains `encrypted_wallet_.asc`, zip name `encrypted_wallet_.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 +### fetchkey (online) Download and verify a PGP public key from a URL. -**Usage:** - ```bash -python ./src/pyhdwallet.py fetchkey [--out FILE] [--timeout SECONDS] +python ./src/pyhdwallet.py fetchkey [--out FILE] [--timeout SECONDS] [--off-screen] ``` -**Options:** +Options: -- `url`: URL to the ASCII-armored PGP key -- `--out FILE`: Save the key to a file -- `--timeout SECONDS`: Request timeout (default: 15) -- `--off-screen`: Enable off-screen mode (temp files, no extra output) +- `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 -**Example:** +--- -```bash -python ./src/pyhdwallet.py fetchkey "https://keys.openpgp.org/pks/lookup?op=get&search=user@example.com" --out key.asc -``` +### gen (offline) -### gen - -Generate a new BIP39 mnemonic and derive addresses. - -**Usage:** +Generate a BIP39 mnemonic and derive addresses. ```bash python ./src/pyhdwallet.py gen [options] ``` -**Options:** +Core 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`: Prompt for BIP39 passphrase interactively -- `--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 -- `--off-screen`: Enable off-screen mode (no printing, temp files, memory zeroing) +- `--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}` -**Examples:** +PGP options: -```bash -# Basic generation -python ./src/pyhdwallet.py gen +- `--pgp-pubkey-file FILE` (encrypt payload to pubkey; `.asc` content) [web:46] +- `--pgp-ignore-usage-flags` -# With off-screen mode -python ./src/pyhdwallet.py gen --off-screen --pgp-pubkey-file key.asc +Artifact options: -# With passphrase and encryption -python ./src/pyhdwallet.py gen --passphrase --pgp-pubkey-file key.asc --export-private +- `--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) -# JSON output to file -python ./src/pyhdwallet.py gen --chains ethereum --addresses 10 --output json --file wallet.json -``` +Safety options: -### recover +- `--off-screen` (don’t print sensitive output) +- `--force` (only for non-TTY stdout; see below) -Derive addresses from an existing mnemonic or seed. +--- -**Usage:** +### recover (offline) + +Recover addresses from mnemonic or seed and optionally write a ZIP artifact. ```bash python ./src/pyhdwallet.py recover [options] ``` -**Options:** Same as `gen`, plus: +Input options (choose one): -- `--mnemonic MNEMONIC`: BIP39 mnemonic phrase -- `--seed HEX_SEED`: 128-character hex seed -- `--interactive`: Prompt for mnemonic/seed interactively -- `--off-screen`: Enable off-screen mode (no printing, temp files, memory zeroing) +- `--mnemonic "..."` (not recommended due to shell history; prefer `--interactive`) +- `--seed HEX` (128 hex chars / 64 bytes) +- `--interactive` -**Examples:** +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 -# 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 +python ./src/pyhdwallet.py test ``` -### test +--- -Run minimal self-tests to verify functionality. +## When to use `--force` -**Usage:** +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 -python ./src/pyhdwallet.py test [--off-screen] +# 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 ``` -**Options:** +If running normally in your interactive terminal, stdout is a TTY and `--force` does nothing (output looks the same). -- `--off-screen`: Enable off-screen mode (no extra output) +--- -**Output:** Success/failure messages for derivation tests. +## Security notes (practical) -## Examples +- `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] -### 1. Generate and Encrypt a New Wallet - -```bash -# 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 -``` - -### 2. Recover from Mnemonic with Passphrase - -```bash -python ./src/pyhdwallet.py recover --mnemonic "word1 word2 ... word12" --passphrase --chains ethereum --addresses 10 --output json -``` - -### 3. Fetch and Use PGP Key - -```bash -# 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 -``` - -### 4. High-Security Operations with Off-Screen Mode - -```bash -# Generate without printing sensitive data -python ./src/pyhdwallet.py gen --off-screen --pgp-pubkey-file key.asc --chains ethereum --addresses 1 - -# Recover in off-screen mode -python ./src/pyhdwallet.py recover --off-screen --interactive --pgp-pubkey-file key.asc --export-private -``` - -### 5. Solana-Specific Derivation - -```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. -- **Off-screen mode**: Use `--off-screen` for paranoid operations—suppresses output, uses temp files with auto-deletion, and zeros memory. -- **Passphrase handling**: Passphrases are not stored; only hints are included. -- **Private key export**: Only export what's needed; treat encrypted payloads as sensitive. -- **File permissions**: Output files are set to owner-only (0o600) for security. -- **Memory zeroing**: In secure mode, sensitive variables are cleared after use. -- **Best practices**: - - Use `--interactive` to avoid command-line history exposure. - - Use `--off-screen` for high-risk operations. - - 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. -- **Secure mode issues**: Ensure temp files are deleted; check permissions on output files. -- **Version check**: Run `python ./src/pyhdwallet.py --version` +### “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.3**: Changed default behavior to always show help without subcommand; added README.md; updated documentation. -- **v1.0.2**: Security patches - added --off-screen, memory zeroing, file permission fixes, auto-deletion in off-screen mode, sanitized errors. -- **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. +- **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.