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

9.0 KiB
Raw Blame History

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

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)

python ./src/pyhdwallet.py gen

2) Generate and save AES ZIP artifact to .wallet/

python ./src/pyhdwallet.py gen --file
python ./src/pyhdwallet.py gen --pgp-pubkey-file pubkeys/mykey.asc --file

4) Recover (addresses) from mnemonic

python ./src/pyhdwallet.py recover --mnemonic "abandon abandon ... about"

5) Recover with interactive input + ZIP artifact

python ./src/pyhdwallet.py recover --interactive --file

6) Run tests

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.

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.

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.

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.

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:

# 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:

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:

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:

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.