From 6fd7cd4e799f81e0aa13fecc9656cb5c98fa6356 Mon Sep 17 00:00:00 2001 From: LC mac Date: Thu, 8 Jan 2026 00:05:27 +0800 Subject: [PATCH] modified playbook.md --- playbook.md | 143 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 128 insertions(+), 15 deletions(-) diff --git a/playbook.md b/playbook.md index 338a370..f47f13b 100644 --- a/playbook.md +++ b/playbook.md @@ -1,10 +1,15 @@ # 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. +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 +- `tests/` — offline regression test suite + - `test_vectors.py` — pytest suite for derivation integrity + - `bootstrap_vectors.py` — one-time script to generate test vectors + - `vectors.json` — frozen expected values (committed) + - `data/recipient.asc` — test PGP key (committed) - `.wallet/` — generated wallet artifacts (should be gitignored) - `requirements.in`, `requirements.txt` - `README.md`, `playbook.md`, `LICENSE` @@ -21,6 +26,7 @@ Repository structure (current): - **AES-encrypted ZIP artifacts**: When `--file` is used, output is written as an AES-encrypted ZIP via `pyzipper`. - **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. +- **Regression test suite**: Offline tests with frozen vectors ensure derivation logic, PGP fingerprinting, and seed generation remain stable across code changes. --- @@ -45,6 +51,7 @@ python -m pip install -r requirements.txt - `PGPy` — encryption to OpenPGP public keys - `pynacl` + `base58` — Solana seed/key handling - `pyzipper` — AES-encrypted ZIP writing (only needed when using `--file`) +- `pytest` — test framework (development only) --- @@ -62,7 +69,7 @@ python ./src/pyhdwallet.py gen python ./src/pyhdwallet.py gen --file ``` -### 3) Generate with PGP encryption and ZIP (recommended for “at-rest” storage) +### 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 @@ -71,7 +78,8 @@ 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" +python ./src/pyhdwallet.py recover --mnemonic-stdin +# (then paste mnemonic and press Ctrl-D) ``` ### 5) Recover with interactive input + ZIP artifact @@ -80,12 +88,72 @@ python ./src/pyhdwallet.py recover --mnemonic "abandon abandon ... about" python ./src/pyhdwallet.py recover --interactive --file ``` -### 6) Run tests +### 6) Run built-in smoke test ```bash python ./src/pyhdwallet.py test ``` +### 7) Run full regression test suite + +```bash +pytest -v tests/test_vectors.py +``` + +--- + +## Testing + +### Regression Test Suite + +The project includes a comprehensive offline test suite that validates critical functionality without requiring network access. All tests use committed test vectors to ensure deterministic, repeatable results. + +#### Test Coverage + +- **PGP fingerprint calculation**: Verifies that PGP key fingerprinting logic produces expected results and enforces fingerprint matching +- **BIP39 seed derivation**: Ensures mnemonics (with and without passphrases) always produce the same seed hex +- **Multi-chain address derivation**: Validates Ethereum, Bitcoin (3 address types), and Solana (3 profiles) derivation paths remain stable +- **CLI integration**: Smoke tests for `recover` command with correct and incorrect fingerprints + +#### Running Tests + +```bash +# Run all tests +pytest -v tests/test_vectors.py + +# Run specific test +pytest -v tests/test_vectors.py::test_address_derivation_integrity + +# Run with detailed output +pytest -vv tests/test_vectors.py +``` + +#### Test Artifacts (Committed) + +- `tests/vectors.json` — Frozen expected values for all test cases +- `tests/data/recipient.asc` — Test PGP public key (safe to commit; public key only) +- `tests/bootstrap_vectors.py` — One-time script to regenerate vectors (run only when intentionally updating expected behavior) + +#### When to Regenerate Test Vectors + +Only regenerate `tests/vectors.json` when you **intentionally** change: + +- Derivation paths (e.g., switching from `m/44'/60'/0'/0/i` to a different path) +- Solana profile logic +- BIP39 seed generation + +To regenerate: + +```bash +rm tests/vectors.json +python3 tests/bootstrap_vectors.py +pytest -v tests/test_vectors.py # Verify all pass +git add tests/vectors.json +git commit -m "test: update vectors after intentional derivation change" +``` + +**Warning**: If tests fail after a code change and you didn't intend to change behavior, **do not regenerate vectors**. Fix the code regression instead. + --- ## Outputs and file structure @@ -130,7 +198,7 @@ Naming uses UTC timestamps (e.g. `20260106_161830Z`): Download and verify a PGP public key from a URL. ```bash -python ./src/pyhdwallet.py fetchkey [--out FILE] [--timeout SECONDS] [--off-screen] +python ./src/pyhdwallet.py fetchkey [--out FILE] [--timeout SECONDS] [--off-screen] [--expected-fingerprint FINGERPRINT] ``` Options: @@ -139,6 +207,7 @@ Options: - `--out FILE`: save key to file - `--timeout SECONDS`: request timeout (default 15) - `--off-screen`: reduced output / temp-file behavior +- `--expected-fingerprint`: refuse if downloaded key fingerprint doesn't match (40 hex chars) --- @@ -164,6 +233,7 @@ PGP options: - `--pgp-pubkey-file FILE` (encrypt payload to pubkey; `.asc` content) - `--pgp-ignore-usage-flags` +- `--expected-fingerprint FINGERPRINT` (refuse if PGP key fingerprint doesn't match) Artifact options: @@ -175,14 +245,14 @@ Artifact options: Safety options: -- `--off-screen` (don’t print sensitive output) +- `--off-screen` (don't 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. +Recover addresses from mnemonic and optionally write a ZIP artifact. ```bash python ./src/pyhdwallet.py recover [options] @@ -190,18 +260,19 @@ 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` +- `--mnemonic-stdin` (recommended; read from stdin to avoid shell history) +- `--interactive` (word-by-word guided entry with validation) Additional options: - same chain/PGP/ZIP/off-screen/force options as `gen` +- `--export-private` (requires `--pgp-pubkey-file`; includes private keys in encrypted payload) +- `--include-source` (includes mnemonic in payload; requires `--pgp-pubkey-file`) 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). +- `--include-source` controls whether mnemonic is included inside the encrypted payload (only meaningful when PGP is used). --- @@ -213,6 +284,12 @@ Run minimal self-tests. python ./src/pyhdwallet.py test ``` +For comprehensive regression testing, use the pytest suite: + +```bash +pytest -v tests/test_vectors.py +``` + --- ## When to use `--force` @@ -241,16 +318,17 @@ If running normally in your interactive terminal, stdout is a TTY and `--force` ## Security notes (practical) -- `gen` printing the mnemonic is intentionally “debug/test” behavior. Assume stdout can be recorded (scrollback, logging, screen recording, CI logs). +- `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`. - 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(...)`. +- Use `--expected-fingerprint` to enforce PGP key pinning and prevent key substitution attacks. --- ## Troubleshooting -### “Permission denied” running `./src/pyhdwallet.py` +### "Permission denied" running `./src/pyhdwallet.py` Run via python, or set executable bit: @@ -261,7 +339,7 @@ chmod +x ./src/pyhdwallet.py ./src/pyhdwallet.py gen ``` -### “Missing dependency: pyzipper” but pip says installed +### "Missing dependency: pyzipper" but pip says installed You likely installed into a different venv. Use: @@ -270,6 +348,14 @@ python -m pip install pyzipper python -c "import pyzipper; print(pyzipper.__version__)" ``` +### "Missing dependency: pytest" when running tests + +Install pytest in your virtualenv: + +```bash +python -m pip install pytest +``` + ### Unzipping AES ZIP issues Some `unzip` tools may not support AES-encrypted ZIPs. Use Python + pyzipper to extract if needed: @@ -289,6 +375,17 @@ PY AES ZIP support is the reason `pyzipper` is used. +### Test failures after code changes + +If `pytest -v tests/test_vectors.py` fails after you modified code: + +1. **Did you intentionally change derivation logic?** + - Yes → Regenerate vectors: `rm tests/vectors.json && python3 tests/bootstrap_vectors.py` + - No → You have a regression bug; revert your changes and fix the code + +2. **Is only one test failing?** + - Run with `-vv` for detailed diff: `pytest -vv tests/test_vectors.py::test_name` + --- ## Changelog @@ -299,8 +396,24 @@ AES ZIP support is the reason `pyzipper` is used. - `--file` is now a boolean flag that writes **only** AES-encrypted ZIP artifacts (no raw output files). - 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). - + - Added: `--expected-fingerprint` for PGP key pinning. + - Added: Offline regression test suite with frozen vectors (`tests/test_vectors.py`). + - Changed: `recover` now uses `--mnemonic-stdin` and `--interactive` instead of `--mnemonic` CLI arg. - **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. + +Key additions: + +1. **Testing section** with full coverage explanation +2. **Repository structure** updated to include `tests/` folder +3. **Test artifacts** documentation +4. **When to regenerate vectors** guidance +5. **Troubleshooting** section for test failures +6. **Changelog** updated with v1.0.5 test suite addition +7. **Quick Start** updated with correct `recover` command syntax (`--mnemonic-stdin`) +8. **Dependencies** updated to include pytest + +[1](https://ppl-ai-file-upload.s3.amazonaws.com/web/direct-files/attachments/50321846/e5caab46-cb40-4f76-8ea9-60ed65bf4927/pyhdwallet.py) +[2](https://ppl-ai-file-upload.s3.amazonaws.com/web/direct-files/attachments/images/50321846/085d5710-8637-45d5-8686-77c98df9dcdf/image.jpg)