diff --git a/BestPractices.md b/BestPractices.md deleted file mode 100644 index dac436c..0000000 --- a/BestPractices.md +++ /dev/null @@ -1,174 +0,0 @@ -# Offline Security Best Practices - -This tool is designed for offline use, but true security depends on the **environment** where you run it. Below are operational security recommendations for generating keys you can trust. - -## Air-Gapped Setup (Highest Security) - -For maximum security when generating production wallets, use an **air-gapped computer**—a device that has never been and will never be connected to any network. - -**Recommended procedure:** - -1. **Prepare a clean machine**: - - Use a dedicated laptop or bootable USB with a fresh Linux installation (e.g., Tails, Ubuntu Live USB) - - Never connect this device to WiFi, Ethernet, or Bluetooth [web:14] - - Physically disable network interfaces if possible (remove WiFi card, tape over Ethernet port) - -2. **Transfer dependencies offline**: - - ```bash - # On an online machine, download dependencies - pip download -r requirements.txt -d ./offline-deps - - # Transfer ./offline-deps to air-gapped machine via USB - # On air-gapped machine: - pip install --no-index --find-links=./offline-deps -r requirements.txt - ``` - -3. **Verify code integrity**: - - Check the git commit hash matches the version you reviewed - - Optionally run `pytest -v tests/test_vectors.py` on the air-gapped machine to verify derivation logic - -4. **Generate wallet**: - - ```bash - python ./src/pyhdwallet.py gen --pgp-pubkey-file pubkeys/recipient.asc --file --off-screen - ``` - -5. **Transfer output safely**: - - Copy only the `.wallet/*.zip` file to USB (never copy `pyhdwallet.py` or Python environment back to online machine) - - The ZIP is AES-encrypted; the inner `.asc` is PGP-encrypted [web:19] - -6. **Destroy or securely wipe** the USB after transfer if it contained unencrypted secrets - -### Threats Air-Gapping Mitigates - -- **Remote attacks**: Malware cannot exfiltrate keys over the network -- **Clipboard hijacking**: No clipboard manager or remote access tool can intercept data -- **Browser/OS telemetry**: No accidental upload of terminal history or crash dumps - -### Threats Air-Gapping Does NOT Fully Mitigate - -Research shows that sophisticated attackers with physical access can potentially exfiltrate data from air-gapped systems via covert channels (acoustic, electromagnetic, optical) [web:18]. However: - -- These attacks require physical proximity and pre-installed malware [web:18] -- For individual users (not nation-state targets), air-gapping remains highly effective -- Countermeasures: Use the machine in a secure location, inspect for unfamiliar USB devices, verify software integrity before installation [web:18] - -### Physical Security - -**Mnemonic handling:** - -- Write the mnemonic on paper immediately; never store it digitally on the air-gapped machine -- Use a metal backup (e.g., Cryptosteel) for fire/water resistance -- Split storage across multiple secure locations if desired (Shamir's Secret Sharing for advanced users) - -**Device handling:** - -- After generating the wallet, optionally wipe the air-gapped device or destroy the bootable USB -- If reusing the device, use secure-erase tools (not just `rm`) - -### Verification Through Testing - -**Why the test suite matters for trust:** - -The committed test suite (`tests/test_vectors.py`) allows you to verify that derivation logic hasn't been tampered with before generating production keys: - -```bash -# On the air-gapped machine, before generating your wallet: -pytest -v tests/test_vectors.py -``` - -If all tests pass, you have cryptographic proof that: - -- BIP39 seed derivation matches the well-known test vector ("abandon abandon..." → known seed) [file:2] -- Derivation paths produce addresses matching public BIP39/BIP44 test vectors -- PGP fingerprinting logic works correctly - -If tests fail, **do not generate keys**—the code may be compromised or buggy. - -### Entropy Sources - -**Built-in entropy (default):** - -- Python's `secrets.token_bytes()` uses OS-provided CSPRNG (`/dev/urandom` on Linux, `CryptGenRandom` on Windows) -- This is cryptographically secure for typical use - -**Additional user entropy (optional):** - -```bash -python ./src/pyhdwallet.py gen --dice-rolls "4 2 6 1 3 5 ..." --file -``` - -- Roll a die 50+ times and input the results -- Your dice rolls are mixed with OS entropy via SHA256 [file:2] -- Protects against potential CSPRNG backdoors (theoretical concern) - -### PGP Key Fingerprint Pinning - -Always use `--expected-fingerprint` when encrypting to a PGP key to prevent key substitution attacks [file:2]: - -```bash -# First, get and verify the fingerprint of your recipient key -python ./src/pyhdwallet.py fetchkey https://example.com/mykey.asc --out mykey.asc -# Manually verify fingerprint matches what you expect (check via another channel) - -# Then use it with pinning: -python ./src/pyhdwallet.py gen \ - --pgp-pubkey-file mykey.asc \ - --expected-fingerprint A27B96F2B169B5491013D2DA892B822C14A9AA18 \ - --file -``` - -Without `--expected-fingerprint`, an attacker who controls the filesystem could swap `mykey.asc` with their own key. - -### Operational Checklist - -Before generating production wallets: - -- [ ] Running on air-gapped machine or fresh Live USB -- [ ] Network physically disabled (no WiFi/Ethernet/Bluetooth) -- [ ] Code integrity verified (git commit hash + test suite passes) -- [ ] Using `--off-screen` to minimize terminal scrollback exposure -- [ ] Using `--file` to avoid leaving unencrypted files on disk -- [ ] Using `--pgp-pubkey-file` with `--expected-fingerprint` for key pinning -- [ ] Paper/metal backup prepared for mnemonic -- [ ] Output ZIP password stored separately from ZIP file -- [ ] Plan for USB secure wipe after transfer - -### Lower-Risk Scenarios - -Air-gapping is overkill for: - -- **Learning/testing**: Use your regular laptop with `--off-screen` and `--file` -- **Small amounts**: Generate on a clean, updated machine with minimal software -- **Testnet wallets**: Standard laptop is fine - -Air-gapping is recommended for: - -- **Life savings or business funds** -- **Long-term cold storage** (multi-year hold) -- **Institutional custody scenarios** - -### Trust But Verify - -The only way to fully trust this tool (or any wallet software) is to: - -1. **Read the source code** (`src/pyhdwallet.py` is ~1400 lines, single file) -2. **Verify test vectors** match published BIP39 test data -3. **Run the test suite** on your air-gapped machine -4. **Generate a test wallet** and verify addresses on a block explorer using a separate tool - -Never trust wallet software blindly—especially for significant funds - ---- -**Key additions:** - -1. **Air-gapped setup procedure** with offline dependency installation -2. **Threat model** (what air-gapping protects against and doesn't) -3. **Physical security** best practices for mnemonic backup -4. **Test suite as verification tool** before generating production keys -5. **Entropy sources** explanation (dice rolls for paranoid users) -6. **PGP fingerprint pinning** rationale -7. **Operational security checklist** for production use -8. **Risk-based guidance** (when air-gapping is overkill vs. essential) -9. **"Trust but verify"** philosophy for crypto software diff --git a/Dockerfile b/Dockerfile index 53e3001..8ec3ca4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ - # Dockerfile -# Base build environment for hdwalletpy: Python 3.11 + build tools + venv support -FROM python:3.11-slim +# Build environment for hdwalletpy: Python 3.12 + build tools + venv support +# Used for: (1) Building Linux x86_64 wheels, (2) Development container +FROM python:3.12-slim # Install build tools, headers, and venv module RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -9,8 +9,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3-dev \ libffi-dev \ python3-venv \ - && rm -rf /var/lib/apt/lists/* + && rm -rf /var/lib/apt/lists/* # Set working directory for bind mounts WORKDIR /app +# Default command (can be overridden) +CMD ["/bin/bash"] diff --git a/Makefile b/Makefile index af9ba9c..5c624b8 100644 --- a/Makefile +++ b/Makefile @@ -1,90 +1,169 @@ - # Makefile for hdwalletpy workflow -# - Build a reusable Docker image +# - Build reusable Docker image (Python 3.12) +# - Vendor multi-platform wheels for offline air-gapped use # - Compile wheels in container (wheelhouse) -# - Create host venv and install from wheelhouse +# - Create host venv and install from wheelhouse or vendor/ # - Optionally run inside a warm container # ---------- Config ---------- -IMAGE := python-build-env:3.11 -CONTAINER := hdwallet +IMAGE := hdwallet-build:3.12 +CONTAINER := hdwallet-dev WORKDIR := /app -VENV_HOST := .venv_host +VENV_HOST := .venv VENV_CONTAINER := /opt/venv WHEELHOUSE := wheelhouse +# Vendor directories for air-gapped deployment +VENDOR_MACOS := vendor/macos-arm64 +VENDOR_LINUX := vendor/linux-x86_64 + # ---------- Help ---------- .PHONY: help help: - @echo "Targets:" - @echo " make build-image - Build Docker image with build tools and python3-venv" - @echo " make wheels - Build dependency wheels into ./$(WHEELHOUSE)" - @echo " make venv-host - Create host venv $(VENV_HOST) and install from wheelhouse" - @echo " make up - Start a warm container named $(CONTAINER)" - @echo " make shell - Open a shell in the warm container" - @echo " make venv-container - Create venv inside container at $(VENV_CONTAINER) and install requirements" - @echo " make down - Stop and remove the warm container" - @echo " make clean-venv - Remove host venv $(VENV_HOST)" - @echo " make clean-wheelhouse - Remove local wheel cache $(WHEELHOUSE)" - @echo " make clean - Clean venv and wheelhouse" + @echo "Vendoring (for offline/air-gapped use):" + @echo " make vendor-macos - Build macOS ARM64 wheels (native)" + @echo " make vendor-linux - Build Linux x86_64 wheels (Docker)" + @echo " make vendor-all - Build wheels for both platforms" + @echo " make verify-vendor - Test offline installation from vendor/" + @echo "" + @echo "Development workflow:" + @echo " make build-image - Build Docker image (Python 3.12)" + @echo " make wheels - Build wheels into ./$(WHEELHOUSE)" + @echo " make install - Create venv and install dependencies" + @echo " make test - Run test suite" + @echo " make up - Start warm dev container" + @echo " make shell - Open shell in warm container" + @echo " make down - Stop and remove dev container" + @echo "" + @echo "Cleanup:" + @echo " make clean - Remove venv, wheelhouse, vendor/" + @echo " make clean-vendor - Remove vendor/ only" # ---------- Build reusable image ---------- .PHONY: build-image build-image: - docker build -t $(IMAGE) . + docker build -t $(IMAGE) . -# ---------- Build wheels in container ---------- +# ---------- Vendoring for Air-Gapped Use ---------- +.PHONY: vendor-macos +vendor-macos: requirements.txt + @echo "Building macOS ARM64 wheels (native)..." + @if [ ! -f ".venv312/bin/pip" ]; then \ + echo "ERROR: .venv312 not found. Create it first:"; \ + echo " python3.12 -m venv .venv312 && source .venv312/bin/activate"; \ + exit 1; \ + fi + mkdir -p $(VENDOR_MACOS) + .venv312/bin/pip download --dest $(VENDOR_MACOS) -r requirements.txt + cd $(VENDOR_MACOS) && shasum -a 256 *.whl > SHA256SUMS + @echo "✓ macOS ARM64 wheels: $(VENDOR_MACOS)/" + +.PHONY: vendor-linux +vendor-linux: requirements.txt build-image + @echo "Building Linux x86_64 wheels (Docker)..." + mkdir -p $(VENDOR_LINUX) + docker run --rm \ + -v "$$PWD":$(WORKDIR) \ + -w $(WORKDIR) \ + $(IMAGE) \ + bash -c " \ + pip install --upgrade pip && \ + pip download --dest $(VENDOR_LINUX) -r requirements.txt && \ + pip wheel --wheel-dir $(VENDOR_LINUX) --no-deps $(VENDOR_LINUX)/*.tar.gz 2>/dev/null || true && \ + rm -f $(VENDOR_LINUX)/*.tar.gz && \ + cd $(VENDOR_LINUX) && sha256sum *.whl > SHA256SUMS \ + " + @echo "✓ Linux x86_64 wheels: $(VENDOR_LINUX)/" + +.PHONY: vendor-all +vendor-all: vendor-macos vendor-linux + @echo "" + @echo "✓ All platforms vendored:" + @echo " macOS ARM64: $(VENDOR_MACOS)/ ($$(ls $(VENDOR_MACOS)/*.whl 2>/dev/null | wc -l | xargs) wheels)" + @echo " Linux x86_64: $(VENDOR_LINUX)/ ($$(ls $(VENDOR_LINUX)/*.whl 2>/dev/null | wc -l | xargs) wheels)" + @echo "" + @echo "Commit with: git add vendor/ && git commit -m 'vendor: update wheels'" + +.PHONY: verify-vendor +verify-vendor: + @echo "Testing offline installation from vendor/..." + @bash -c ' \ + if [[ "$$OSTYPE" == "darwin"* ]]; then \ + PLATFORM="macos-arm64"; \ + else \ + PLATFORM="linux-x86_64"; \ + fi; \ + echo "Platform: $$PLATFORM"; \ + python3.12 -m venv .venv-verify && \ + source .venv-verify/bin/activate && \ + pip install --no-index --find-links=vendor/$$PLATFORM -r requirements.txt && \ + pytest -v tests/test_vectors.py && \ + python src/pyhdwallet.py test && \ + echo "" && \ + echo "✅ Vendor installation verified!" && \ + deactivate && \ + rm -rf .venv-verify \ + ' + +# ---------- Development Workflow ---------- .PHONY: wheels -wheels: requirements.txt - docker run --rm -it -v "$$PWD":$(WORKDIR) -w $(WORKDIR) $(IMAGE) bash -lc '\ - python -m pip install --upgrade pip setuptools wheel && \ - mkdir -p $(WHEELHOUSE) && \ - python -m pip wheel -r requirements.txt -w $(WHEELHOUSE) \ - ' +wheels: requirements.txt build-image + docker run --rm \ + -v "$$PWD":$(WORKDIR) \ + -w $(WORKDIR) \ + $(IMAGE) \ + bash -c " \ + pip install --upgrade pip setuptools wheel && \ + mkdir -p $(WHEELHOUSE) && \ + pip wheel -r requirements.txt -w $(WHEELHOUSE) \ + " -# ---------- Host venv from wheelhouse ---------- -.PHONY: venv-host -venv-host: requirements.txt - python3 -m venv $(VENV_HOST) - . $(VENV_HOST)/bin/activate && \ - python -m pip install --upgrade pip setuptools wheel && \ - python -m pip install --no-index --find-links=$(WHEELHOUSE) -r requirements.txt && \ - python --version +.PHONY: install +install: requirements.txt + @if [ ! -d "$(VENV_HOST)" ]; then \ + echo "Creating venv: $(VENV_HOST)"; \ + python3.12 -m venv $(VENV_HOST); \ + fi + . $(VENV_HOST)/bin/activate && \ + pip install --upgrade pip && \ + pip install -r requirements.txt && \ + echo "✓ Virtual environment ready: $(VENV_HOST)" + +.PHONY: test +test: + @if [ ! -d "$(VENV_HOST)" ]; then \ + echo "ERROR: Run 'make install' first"; \ + exit 1; \ + fi + . $(VENV_HOST)/bin/activate && \ + pytest -v tests/test_vectors.py && \ + python src/pyhdwallet.py test # ---------- Warm container lifecycle ---------- .PHONY: up -up: - docker run -dit -v "$$PWD":$(WORKDIR) -w $(WORKDIR) --name $(CONTAINER) $(IMAGE) bash +up: build-image + docker run -dit \ + -v "$$PWD":$(WORKDIR) \ + -w $(WORKDIR) \ + --name $(CONTAINER) \ + $(IMAGE) \ + bash .PHONY: shell shell: - docker exec -it $(CONTAINER) bash - -.PHONY: venv-container -venv-container: requirements.txt - docker exec -it $(CONTAINER) bash -lc '\ - test -x $(VENV_CONTAINER)/bin/python || ( \ - python -m venv $(VENV_CONTAINER) && \ - . $(VENV_CONTAINER)/bin/activate && \ - python -m pip install --upgrade pip setuptools wheel && \ - python -m pip install -r requirements.txt \ - ) && \ - . $(VENV_CONTAINER)/bin/activate && python --version \ - ' + docker exec -it $(CONTAINER) bash .PHONY: down down: - - docker rm -f $(CONTAINER) + - docker rm -f $(CONTAINER) # ---------- Cleanup ---------- -.PHONY: clean-venv -clean-venv: - rm -rf $(VENV_HOST) - -.PHONY: clean-wheelhouse -clean-wheelhouse: - rm -rf $(WHEELHOUSE) +.PHONY: clean-vendor +clean-vendor: + rm -rf vendor/ .PHONY: clean -clean: clean-venv clean-wheelhouse - +clean: down + rm -rf $(VENV_HOST) $(WHEELHOUSE) vendor/ .venv-verify .venv-offline-test + find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true + find . -type d -name "*.egg-info" -exec rm -rf {} + 2>/dev/null || true diff --git a/README.md b/README.md index 3d2de53..6d9ac07 100644 --- a/README.md +++ b/README.md @@ -1,151 +1,172 @@ -# hdwalletpy – Setup & Usage Guide +# pyhdwallet – Secure HD Wallet Tool -This project provides a Python-based HD Wallet tool with optional Docker-based build automation for fast, reproducible environments. +A Python command-line tool for generating and recovering BIP39 HD wallets with support for Ethereum, Solana, and Bitcoin. Designed for offline operation with optional PGP encryption and AES-encrypted ZIP artifacts. --- -## 📦 Installation Options +## 📦 Installation -### **Option 1: Standard Python Setup (Host Machine)** +### **Quick Start (macOS/Linux with Internet)** -1. Clone the repository: +```bash +# Clone repository +git clone https://github.com//hdwalletpy.git +cd hdwalletpy + +# Install using automated script +./install_offline.sh +``` + +The script automatically: + +- Creates Python 3.12 virtual environment +- Installs from vendored wheels (offline-capable) +- Verifies installation with test suite +- Leaves you in activated venv + +--- + +### **Air-Gapped Installation (No Internet)** + +This repository includes pre-built Python wheels for offline use. + +**Supported platforms:** + +- macOS ARM64 (M1/M2/M3) - Python 3.12 +- Linux x86_64 (Ubuntu/Tails) - Python 3.12 + +**Steps:** + +1. On an online machine, clone and verify: ```bash git clone https://github.com//hdwalletpy.git cd hdwalletpy + + # Verify checksums + cd vendor/linux-x86_64 # or macos-arm64 + sha256sum -c SHA256SUMS # Linux + shasum -a 256 -c SHA256SUMS # macOS ``` -2. Create a virtual environment: +2. Transfer entire repo to USB drive + +3. On air-gapped machine: ```bash - python -m venv .venv - source .venv/bin/activate + cd hdwalletpy + ./install_offline.sh ``` -3. Install dependencies: +4. Generate wallet: ```bash - python -m pip install --upgrade pip - python -m pip install -r requirements.txt + python src/pyhdwallet.py gen --off-screen --file ``` --- -### **Option 2: Fast Setup Using Docker + Makefile** +### **Developer Installation (with Docker)** -This method avoids installing compilers on your host and speeds up dependency builds. - -#### **Step 1: Build Docker Image** +For development or building wheels for other platforms: ```bash +# Build Docker image make build-image -``` -Creates a reusable image `python-build-env:3.11` with build tools and `python3-venv`. +# Build wheels for all platforms +make vendor-all -#### **Step 2: Compile Wheels in Container** +# Install development environment +make install -```bash -make wheels -``` - -Builds all dependency wheels into `./wheelhouse` for fast installs. - -#### **Step 3: Create Host Virtual Environment** - -```bash -make venv-host -source .venv_host/bin/activate -``` - -Installs dependencies from `wheelhouse` (no compilation needed). - -#### **Optional: Work Inside a Warm Container** - -```bash -make up # Start container -make shell # Open shell inside container -make venv-container # Create container-only venv at /opt/venv -``` - -#### **Cleanup** - -```bash -make clean # Remove host venv and wheelhouse -make down # Stop/remove container +# Run tests +make test ``` --- ## ✅ Basic Usage -Run the tool with subcommands. For help, use `-h` (e.g., `gen -h`, `recover -h`). - -Examples: - ```bash -# Generate mnemonic (prints to stdout; for debug/test) -python ./src/pyhdwallet.py gen +# Generate wallet (prints mnemonic - debug mode) +python src/pyhdwallet.py gen -# Generate and save AES-encrypted ZIP artifact into ./.wallet (password prompted) -python ./src/pyhdwallet.py gen --file +# Generate with off-screen mode + encrypted ZIP +python src/pyhdwallet.py gen --off-screen --file -# Generate with PGP encryption + ZIP artifact (ZIP contains only encrypted .asc payload) -python ./src/pyhdwallet.py gen --pgp-pubkey-file pubkeys/mykey.asc --file +# Generate with PGP encryption + ZIP +python src/pyhdwallet.py gen \ + --pgp-pubkey-file pubkeys/mykey.asc \ + --expected-fingerprint A27B96F2B169B5491013D2DA892B822C14A9AA18 \ + --off-screen \ + --file -# Recover from mnemonic (prefer --interactive to avoid shell history) -python ./src/pyhdwallet.py recover --interactive +# Recover wallet from mnemonic +python src/pyhdwallet.py recover --interactive --file -# Recover and save AES ZIP artifact -python ./src/pyhdwallet.py recover --interactive --file - -# Fetch PGP public key (online) -python ./src/pyhdwallet.py fetchkey "https://example.com/key.asc" --out mykey.asc +# Fetch PGP public key (requires internet) +python src/pyhdwallet.py fetchkey "https://example.com/key.asc" --out mykey.asc # Run tests -python ./src/pyhdwallet.py test +python src/pyhdwallet.py test +pytest -v tests/test_vectors.py ``` --- -## 🔐 Notes on `--file`, AES ZIP, and PGP +## 🔐 Security Features -- `--file` writes **only** an AES-encrypted ZIP (no raw `.json`/`.asc` left on disk), using `pyzipper`. -- If `--pgp-pubkey-file` is set, the ZIP contains a single ASCII-armored PGP message (`.asc`) created with PGPy-style `PGPMessage.new(...)` then `pubkey.encrypt(...)`. +- **Offline-first**: Network access blocked during key generation/recovery +- **Test suite**: Regression tests with frozen vectors ensure derivation logic integrity +- **PGP fingerprint pinning**: Prevents key substitution attacks +- **TTY safety guard**: Refuses to print secrets when stdout is piped/redirected +- **AES-encrypted outputs**: Wallet artifacts encrypted with `pyzipper` +- **No shell history leaks**: Use `--interactive` or `--mnemonic-stdin` for recovery --- -## ⚠️ About `--force` +## 🛠️ Makefile Targets -The tool includes a safety guard: if stdout is piped/redirected (non-TTY), it refuses to print sensitive output unless `--force` is set. Checking whether stdout is a terminal is commonly done via `isatty()`. - -Example: +**Vendoring (for air-gapped deployment):** ```bash -python ./src/pyhdwallet.py gen > out.txt # likely refused (non-TTY) -python ./src/pyhdwallet.py gen --force > out.txt # allowed +make vendor-macos # Build macOS ARM64 wheels +make vendor-linux # Build Linux x86_64 wheels (Docker) +make vendor-all # Build for both platforms +make verify-vendor # Test offline installation ``` ---- - -## ✅ Why Use Makefile? - -- **Speed:** Avoid repeated `apt-get` installs; wheels cached locally. -- **Reproducibility:** Same Docker image for builds; no environment drift. -- **Convenience:** One-liner tasks (`make wheels`, `make venv-host`, `make shell`). -- **Separation:** Host venv vs container venv for clean workflows. - ---- - -## Common Makefile Targets +**Development:** ```bash -make build-image # Build Docker image with build tools -make wheels # Compile wheels into ./wheelhouse -make venv-host # Host venv install from wheelhouse -make up # Start warm container -make shell # Shell into warm container -make venv-container # Container venv at /opt/venv -make down # Stop/remove container -make clean # Remove .venv_host and wheelhouse +make install # Create venv and install dependencies +make test # Run test suite +make build-image # Build Docker image +make shell # Open shell in Docker container +make clean # Remove venvs and build artifacts ``` + +--- + +## 📖 Full Documentation + +- **[playbook.md](playbook.md)** - Complete command reference and operational guide +- **[tests/](tests/)** - Regression test suite documentation + +--- + +## 🔒 Recommended Air-Gapped Setup + +For maximum security when generating production wallets: + +1. Use fresh Ubuntu Live USB or Tails OS +2. Never connect to network after booting +3. Transfer this repository via separate USB +4. Run `./install_offline.sh` +5. Generate wallet: `python src/pyhdwallet.py gen --off-screen --file` +6. Write mnemonic to paper/metal backup +7. Transfer encrypted ZIP to secure storage +8. Wipe USB drives securely + +See [playbook.md](playbook.md) for detailed air-gapped procedures. diff --git a/build-all-platforms.sh b/build-all-platforms.sh new file mode 100755 index 0000000..1fa5483 --- /dev/null +++ b/build-all-platforms.sh @@ -0,0 +1,115 @@ +#!/bin/bash +set -e + +echo "Building wheels for all platforms using Python 3.12..." +echo "" + +# Check Docker is running +if ! docker info > /dev/null 2>&1; then + echo "ERROR: Docker is not running. Please start Docker Desktop." + exit 1 +fi + +# 1. macOS ARM64 (via Docker with Python 3.12) +echo "==> Building macOS ARM64 wheels (Docker + Python 3.12)..." +docker run --rm \ + --platform linux/arm64 \ + -v $(pwd):/work \ + -w /work \ + python:3.12-slim \ + bash -c " + apt-get update -qq && apt-get install -y -qq gcc g++ make libffi-dev > /dev/null + pip install --upgrade pip > /dev/null + mkdir -p vendor/macos-arm64-docker + pip download --dest vendor/macos-arm64-docker -r requirements.txt + pip wheel --wheel-dir vendor/macos-arm64-docker --no-deps vendor/macos-arm64-docker/*.tar.gz 2>/dev/null || true + rm -f vendor/macos-arm64-docker/*.tar.gz + " +echo "✓ macOS ARM64: $(ls vendor/macos-arm64-docker/*.whl 2>/dev/null | wc -l | xargs) wheels" + +# Actually, let's use native Mac Python 3.12 if available, otherwise use what we have +echo "" +echo "==> Building macOS ARM64 wheels (native - using your .venv312)..." +mkdir -p vendor/macos-arm64 + +# Use the Python from your working venv +if [ -f ".venv312/bin/pip" ]; then + echo "Using Python from .venv312..." + .venv312/bin/pip download --dest vendor/macos-arm64 -r requirements.txt + echo "✓ macOS ARM64: $(ls vendor/macos-arm64/*.whl 2>/dev/null | wc -l | xargs) wheels" +else + echo "ERROR: .venv312 not found!" + echo "Please activate your working venv first:" + echo " source .venv312/bin/activate" + echo " pip download --dest vendor/macos-arm64 -r requirements.txt" + exit 1 +fi + +# 2. Linux x86_64 (via Docker) +echo "" +echo "==> Building Linux x86_64 wheels (Docker + Python 3.12)..." +docker run --rm \ + -v $(pwd):/work \ + -w /work \ + python:3.12-slim \ + bash -c " + apt-get update -qq && apt-get install -y -qq gcc g++ make libffi-dev > /dev/null + pip install --upgrade pip > /dev/null + mkdir -p vendor/linux-x86_64 + pip download --dest vendor/linux-x86_64 -r requirements.txt + pip wheel --wheel-dir vendor/linux-x86_64 --no-deps vendor/linux-x86_64/*.tar.gz 2>/dev/null || true + rm -f vendor/linux-x86_64/*.tar.gz + " +echo "✓ Linux x86_64: $(ls vendor/linux-x86_64/*.whl 2>/dev/null | wc -l | xargs) wheels" + +# 3. Generate SHA256 checksums +echo "" +echo "==> Generating checksums..." +for platform in macos-arm64 linux-x86_64; do + if [ -d "vendor/$platform" ] && [ "$(ls -A vendor/$platform/*.whl 2>/dev/null)" ]; then + (cd vendor/$platform && shasum -a 256 *.whl > SHA256SUMS) + echo "✓ Checksums for $platform" + fi +done + +# 4. Create provenance file +echo "" +echo "==> Creating provenance documentation..." +cat > vendor/PROVENANCE.md << EOF +# Dependency Provenance + +All wheels generated on: $(date -u +"%Y-%m-%d %H:%M:%S UTC") +Python version: 3.12 +Build machine: $(uname -s) $(uname -m) +Docker used: Yes (python:3.12-slim) + +## Platforms +- macOS ARM64: Built using .venv312 (Python 3.12) +- Linux x86_64: Built using Docker python:3.12-slim + +## Package Versions +$(cat requirements.txt | head -20) + +## Integrity Verification +Each platform directory contains SHA256SUMS for verification: +\`\`\`bash +cd vendor/linux-x86_64 +shasum -a 256 -c SHA256SUMS +\`\`\` + +Last updated: $(date -u +"%Y-%m-%d") +Built by: $(git config user.name) <$(git config user.email)> +Commit: $(git rev-parse --short HEAD 2>/dev/null || echo "not in git") +EOF + +echo "" +echo "✓ All platforms built successfully!" +echo "" +echo "Summary:" +echo " macOS ARM64: vendor/macos-arm64/ ($(ls vendor/macos-arm64/*.whl 2>/dev/null | wc -l | xargs) wheels)" +echo " Linux x86_64: vendor/linux-x86_64/ ($(ls vendor/linux-x86_64/*.whl 2>/dev/null | wc -l | xargs) wheels)" +echo "" +echo "Next steps:" +echo " 1. Verify: ls vendor/*/SHA256SUMS" +echo " 2. Test: ./install_offline.sh" +echo " 3. Commit: git add vendor/ && git commit -m 'vendor: add multi-platform wheels'" diff --git a/install_offline.sh b/install_offline.sh new file mode 100755 index 0000000..db085fb --- /dev/null +++ b/install_offline.sh @@ -0,0 +1,117 @@ +#!/bin/bash +set -e + +# Detect platform +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + PLATFORM="linux-x86_64" +elif [[ "$OSTYPE" == "darwin"* ]]; then + ARCH=$(uname -m) + if [[ "$ARCH" == "arm64" ]]; then + PLATFORM="macos-arm64" + else + echo "ERROR: Only macOS ARM64 is supported" + exit 1 + fi +else + echo "ERROR: Unsupported platform: $OSTYPE" + exit 1 +fi + +echo "Detected platform: $PLATFORM" + +# Check if vendor directory exists +if [ ! -d "vendor/$PLATFORM" ]; then + echo "ERROR: vendor/$PLATFORM not found!" + echo "Run ./build-vendors-simple.sh to generate wheels" + exit 1 +fi + +# Check for Python 3.12 +if ! command -v python3.12 &> /dev/null; then + echo "ERROR: Python 3.12 not found!" + echo "Please install Python 3.12:" + echo " macOS: brew install python@3.12" + echo " Linux: apt-get install python3.12 python3.12-venv" + exit 1 +fi + +# Check if already in a venv +if [ -n "$VIRTUAL_ENV" ]; then + CURRENT_PYTHON=$(python --version 2>&1 | grep -oE '3\.[0-9]+') + if [[ "$CURRENT_PYTHON" != "3.12" ]]; then + echo "WARNING: You are in a venv with Python $CURRENT_PYTHON" + echo "Deactivating and creating new Python 3.12 venv..." + deactivate || true + else + echo "Already in Python 3.12 venv, using it..." + fi +fi + +# Create or use existing venv +VENV_DIR=".venv" +if [ ! -d "$VENV_DIR" ] || [ ! -f "$VENV_DIR/bin/python3.12" ]; then + echo "Creating Python 3.12 virtual environment..." + python3.12 -m venv "$VENV_DIR" + echo "✓ Virtual environment created: $VENV_DIR" +else + echo "Using existing venv: $VENV_DIR" +fi + +# Activate venv +source "$VENV_DIR/bin/activate" + +# Verify Python version +PYTHON_VERSION=$(python --version 2>&1 | grep -oE '3\.[0-9]+') +echo "Using Python $PYTHON_VERSION from: $(which python)" + +if [[ "$PYTHON_VERSION" != "3.12" ]]; then + echo "ERROR: Expected Python 3.12, got $PYTHON_VERSION" + exit 1 +fi + +# Verify checksums +if [ -f "vendor/$PLATFORM/SHA256SUMS" ]; then + echo "" + echo "Verifying checksums..." + (cd vendor/$PLATFORM && shasum -a 256 -c SHA256SUMS --quiet) || { + echo "ERROR: Checksum verification failed!" + exit 1 + } + echo "✓ Checksums verified" +fi + +# Install +echo "" +echo "Installing from vendor/$PLATFORM..." +pip install --upgrade pip --quiet +pip install --no-index --find-links=vendor/$PLATFORM -r requirements.txt + +# Test +echo "" +echo "Running tests..." +python -m pytest -v tests/test_vectors.py || { + echo "ERROR: Tests failed!" + exit 1 +} + +echo "" +echo "Running built-in smoke test..." +python src/pyhdwallet.py test || { + echo "ERROR: Smoke test failed!" + exit 1 +} + +echo "" +echo "==========================================" +echo "✓ Installation complete and verified!" +echo "==========================================" +echo "" +echo "Virtual environment: $VENV_DIR" +echo "Python version: $(python --version)" +echo "" +echo "To activate this environment:" +echo " source $VENV_DIR/bin/activate" +echo "" +echo "Generate a wallet with:" +echo " python src/pyhdwallet.py gen --off-screen --file" +echo "" diff --git a/playbook.md b/playbook.md index f47f13b..4c1c352 100644 --- a/playbook.md +++ b/playbook.md @@ -10,13 +10,18 @@ Repository structure (current): - `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) +- `vendor/` — vendored Python wheels for offline installation + - `macos-arm64/` — macOS Apple Silicon wheels (Python 3.12) + - `linux-x86_64/` — Linux x86_64 wheels (Python 3.12) +- `.wallet/` — generated wallet artifacts (gitignored) - `requirements.in`, `requirements.txt` +- `install_offline.sh` — automated offline installer +- `Dockerfile`, `Makefile` — Docker-based build tools - `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. ---- +*** ## Features @@ -27,33 +32,101 @@ Repository structure (current): - **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. +- **Vendored dependencies**: Pre-built wheels for macOS ARM64 and Linux x86_64 enable true air-gapped installation. ---- +*** ## Installation ### Prerequisites -- Python 3.11+ -- Recommended: a virtual environment +- Python 3.12.x (required for vendored wheels) +- Git (for cloning repository) -### Setup +### Quick Installation (macOS/Linux with Internet) ```bash -python -m venv .venv -source .venv/bin/activate -python -m pip install -r requirements.txt +# Clone repository +git clone https://github.com//hdwalletpy.git +cd hdwalletpy + +# Run automated installer +./install_offline.sh ``` -### Dependencies (top-level intent) +The script automatically: + +- Detects your platform (macOS ARM64 or Linux x86_64) +- Creates Python 3.12 virtual environment +- Installs from vendored wheels (no PyPI access needed) +- Verifies installation with test suite +- Leaves you in activated venv + +### Air-Gapped Installation (No Internet) + +For maximum security on an air-gapped machine (Tails OS, Ubuntu Live USB, etc.): + +**On online machine:** + +```bash +# Clone and verify +git clone https://github.com//hdwalletpy.git +cd hdwalletpy + +# Verify checksums +cd vendor/linux-x86_64 # or macos-arm64 for Mac +sha256sum -c SHA256SUMS # Linux +shasum -a 256 -c SHA256SUMS # macOS + +# Transfer entire repo to USB +``` + +**On air-gapped machine:** + +```bash +# Ensure Python 3.12 is installed +python3.12 --version + +# Navigate to repository +cd hdwalletpy + +# Run installer (creates venv, installs offline) +./install_offline.sh + +# Generate wallet +python src/pyhdwallet.py gen --off-screen --file +``` + +### Manual Installation (Without Script) + +```bash +# Create venv +python3.12 -m venv .venv +source .venv/bin/activate + +# Install from vendored wheels +# For macOS ARM64: +pip install --no-index --find-links=vendor/macos-arm64 -r requirements.txt + +# For Linux x86_64: +pip install --no-index --find-links=vendor/linux-x86_64 -r requirements.txt + +# Verify installation +pytest -v tests/test_vectors.py +python src/pyhdwallet.py test +``` + +### Dependencies (Top-Level Intent) - `bip-utils` — BIP39 + BIP derivation logic - `PGPy` — encryption to OpenPGP public keys - `pynacl` + `base58` — Solana seed/key handling -- `pyzipper` — AES-encrypted ZIP writing (only needed when using `--file`) +- `pyzipper` — AES-encrypted ZIP writing - `pytest` — test framework (development only) ---- +All dependencies are pre-downloaded in `vendor/` for offline use. + +*** ## Quick Start @@ -100,7 +173,7 @@ python ./src/pyhdwallet.py test pytest -v tests/test_vectors.py ``` ---- +*** ## Testing @@ -154,16 +227,75 @@ 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 +## Vendored Dependencies -### Stdout behavior +### What is Vendoring? + +This repository includes pre-built Python wheels in the `vendor/` directory, enabling installation without internet access or PyPI. This is critical for air-gapped security. + +**Supported platforms:** + +- macOS ARM64 (M1/M2/M3/M4) - Python 3.12 +- Linux x86_64 (Ubuntu, Tails, Debian) - Python 3.12 + +### Building Vendor Wheels (Maintainers) + +If you need to update dependencies or add new platforms: + +```bash +# Using Makefile (recommended) +make vendor-all # Build for both macOS and Linux +make vendor-macos # macOS ARM64 only +make vendor-linux # Linux x86_64 only (requires Docker) +make verify-vendor # Test offline installation + +# Commit updated wheels +git add vendor/ +git commit -m "vendor: update dependencies to latest versions" +``` + +**Manual build (macOS):** + +```bash +mkdir -p vendor/macos-arm64 +source .venv312/bin/activate +pip download --dest vendor/macos-arm64 -r requirements.txt +cd vendor/macos-arm64 && shasum -a 256 *.whl > SHA256SUMS +``` + +**Manual build (Linux via Docker):** + +```bash +docker run --rm -v $(pwd):/work -w /work python:3.12-slim bash -c " + apt-get update && apt-get install -y gcc g++ make libffi-dev + pip install --upgrade pip + mkdir -p vendor/linux-x86_64 + pip download --dest vendor/linux-x86_64 -r requirements.txt + cd vendor/linux-x86_64 && sha256sum *.whl > SHA256SUMS +" +``` + +### Verifying Vendor Integrity + +```bash +# Check checksums before using on air-gapped machine +cd vendor/linux-x86_64 +sha256sum -c SHA256SUMS # Linux +shasum -a 256 -c SHA256SUMS # macOS +``` + +*** + +## 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) +### `--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`. @@ -175,12 +307,12 @@ Override folder: - `--wallet-location /path/to/folder` -Naming uses UTC timestamps (e.g. `20260106_161830Z`): +Naming uses UTC timestamps (e.g. `20260108_011830Z`): - 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 +### 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. @@ -189,7 +321,7 @@ Naming uses UTC timestamps (e.g. `20260106_161830Z`): `pyzipper` supports AES encryption via `AESZipFile` and password-setting APIs. ---- +*** ## Commands @@ -209,7 +341,7 @@ Options: - `--off-screen`: reduced output / temp-file behavior - `--expected-fingerprint`: refuse if downloaded key fingerprint doesn't match (40 hex chars) ---- +*** ### gen (offline) @@ -248,7 +380,7 @@ Safety options: - `--off-screen` (don't print sensitive output) - `--force` (only for non-TTY stdout; see below) ---- +*** ### recover (offline) @@ -274,7 +406,7 @@ Notes: - `--export-private` requires `--pgp-pubkey-file` (private keys only travel inside encrypted payload). - `--include-source` controls whether mnemonic is included inside the encrypted payload (only meaningful when PGP is used). ---- +*** ### test (offline) @@ -290,9 +422,9 @@ For comprehensive regression testing, use the pytest suite: pytest -v tests/test_vectors.py ``` ---- +*** -## When to use `--force` +## When to Use `--force` Use `--force` only if you **intentionally** want to print sensitive output while stdout is being piped/redirected. @@ -314,17 +446,193 @@ 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) +## 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`. - 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. +- Install from vendored wheels on air-gapped machines to avoid supply chain attacks. ---- +*** + +## Offline Security Best Practices + +This tool is designed for offline use, but true security depends on the **environment** where you run it. Below are operational security recommendations for generating keys you can trust. + +### Air-Gapped Setup (Highest Security) + +For maximum security when generating production wallets, use an **air-gapped computer**—a device that has never been and will never be connected to any network. + +**Recommended procedure:** + +1. **Prepare a clean machine**: + - Use a dedicated laptop or bootable USB with a fresh Linux installation (e.g., Tails, Ubuntu Live USB) + - Never connect this device to WiFi, Ethernet, or Bluetooth + - Physically disable network interfaces if possible (remove WiFi card, tape over Ethernet port) + +2. **Transfer repository offline**: + + ```bash + # On trusted online machine, clone and verify + git clone https://github.com//hdwalletpy.git + cd hdwalletpy + + # Verify checksums + cd vendor/linux-x86_64 + sha256sum -c SHA256SUMS + + # Transfer entire repo to USB + ``` + +3. **Verify code integrity on air-gapped machine**: + + ```bash + # Check checksums again + cd vendor/linux-x86_64 + sha256sum -c SHA256SUMS + + # Run test suite to verify derivation logic + ./install_offline.sh + pytest -v tests/test_vectors.py + ``` + +4. **Generate wallet**: + + ```bash + python ./src/pyhdwallet.py gen --pgp-pubkey-file pubkeys/recipient.asc --file --off-screen + ``` + +5. **Transfer output safely**: + - Copy only the `.wallet/*.zip` file to USB (never copy `pyhdwallet.py` or Python environment back to online machine) + - The ZIP is AES-encrypted; the inner `.asc` is PGP-encrypted + +6. **Destroy or securely wipe** the USB after transfer if it contained unencrypted secrets + +### Threats Air-Gapping Mitigates + +- **Remote attacks**: Malware cannot exfiltrate keys over the network +- **Clipboard hijacking**: No clipboard manager or remote access tool can intercept data +- **Browser/OS telemetry**: No accidental upload of terminal history or crash dumps + +### Threats Air-Gapping Does NOT Fully Mitigate + +Research shows that sophisticated attackers with physical access can potentially exfiltrate data from air-gapped systems via covert channels (acoustic, electromagnetic, optical). However: + +- These attacks require physical proximity and pre-installed malware +- For individual users (not nation-state targets), air-gapping remains highly effective +- Countermeasures: Use the machine in a secure location, inspect for unfamiliar USB devices, verify software integrity before installation + +### Physical Security + +**Mnemonic handling:** + +- Write the mnemonic on paper immediately; never store it digitally on the air-gapped machine +- Use a metal backup (e.g., Cryptosteel) for fire/water resistance +- Split storage across multiple secure locations if desired (Shamir's Secret Sharing for advanced users) + +**Device handling:** + +- After generating the wallet, optionally wipe the air-gapped device or destroy the bootable USB +- If reusing the device, use secure-erase tools (not just `rm`) + +### Verification Through Testing + +**Why the test suite matters for trust:** + +The committed test suite (`tests/test_vectors.py`) allows you to verify that derivation logic hasn't been tampered with before generating production keys: + +```bash +# On the air-gapped machine, before generating your wallet: +pytest -v tests/test_vectors.py +``` + +If all tests pass, you have cryptographic proof that: + +- BIP39 seed derivation matches the well-known test vector ("abandon abandon..." → known seed) +- Derivation paths produce addresses matching public BIP39/BIP44 test vectors +- PGP fingerprinting logic works correctly + +If tests fail, **do not generate keys**—the code may be compromised or buggy. + +### Entropy Sources + +**Built-in entropy (default):** + +- Python's `secrets.token_bytes()` uses OS-provided CSPRNG (`/dev/urandom` on Linux, `CryptGenRandom` on Windows) +- This is cryptographically secure for typical use + +**Additional user entropy (optional):** + +```bash +python ./src/pyhdwallet.py gen --dice-rolls "4 2 6 1 3 5 ..." --file +``` + +- Roll a die 50+ times and input the results +- Your dice rolls are mixed with OS entropy via SHA256 +- Protects against potential CSPRNG backdoors (theoretical concern) + +### PGP Key Fingerprint Pinning + +Always use `--expected-fingerprint` when encrypting to a PGP key to prevent key substitution attacks: + +```bash +# First, get and verify the fingerprint of your recipient key +python ./src/pyhdwallet.py fetchkey https://example.com/mykey.asc --out mykey.asc +# Manually verify fingerprint matches what you expect (check via another channel) + +# Then use it with pinning: +python ./src/pyhdwallet.py gen \ + --pgp-pubkey-file mykey.asc \ + --expected-fingerprint A27B96F2B169B5491013D2DA892B822C14A9AA18 \ + --file +``` + +Without `--expected-fingerprint`, an attacker who controls the filesystem could swap `mykey.asc` with their own key. + +### Operational Checklist + +Before generating production wallets: + +- [ ] Running on air-gapped machine or fresh Live USB +- [ ] Network physically disabled (no WiFi/Ethernet/Bluetooth) +- [ ] Code integrity verified (checksums + test suite passes) +- [ ] Using `--off-screen` to minimize terminal scrollback exposure +- [ ] Using `--file` to avoid leaving unencrypted files on disk +- [ ] Using `--pgp-pubkey-file` with `--expected-fingerprint` for key pinning +- [ ] Paper/metal backup prepared for mnemonic +- [ ] Output ZIP password stored separately from ZIP file +- [ ] Plan for USB secure wipe after transfer + +### Lower-Risk Scenarios + +Air-gapping is overkill for: + +- **Learning/testing**: Use your regular laptop with `--off-screen` and `--file` +- **Small amounts**: Generate on a clean, updated machine with minimal software +- **Testnet wallets**: Standard laptop is fine + +Air-gapping is recommended for: + +- **Life savings or business funds** +- **Long-term cold storage** (multi-year hold) +- **Institutional custody scenarios** + +### Trust But Verify + +The only way to fully trust this tool (or any wallet software) is to: + +1. **Read the source code** (`src/pyhdwallet.py` is ~1400 lines, single file) +2. **Verify test vectors** match published BIP39 test data +3. **Run the test suite** on your air-gapped machine +4. **Generate a test wallet** and verify addresses on a block explorer using a separate tool + +Never trust wallet software blindly—especially for significant funds. + +*** ## Troubleshooting @@ -339,26 +647,51 @@ chmod +x ./src/pyhdwallet.py ./src/pyhdwallet.py gen ``` -### "Missing dependency: pyzipper" but pip says installed +### Python 3.12 not found -You likely installed into a different venv. Use: +Vendored wheels require Python 3.12: ```bash -python -m pip install pyzipper -python -c "import pyzipper; print(pyzipper.__version__)" +# macOS +brew install python@3.12 + +# Ubuntu/Debian +sudo apt-get install python3.12 python3.12-venv + +# Verify +python3.12 --version ``` -### "Missing dependency: pytest" when running tests +### "Missing dependency" errors -Install pytest in your virtualenv: +Make sure you're installing from the correct vendor directory: ```bash -python -m pip install pytest +# Check your platform +uname -sm + +# Install for macOS ARM64 +pip install --no-index --find-links=vendor/macos-arm64 -r requirements.txt + +# Install for Linux x86_64 +pip install --no-index --find-links=vendor/linux-x86_64 -r requirements.txt +``` + +### Checksum verification failures + +If `SHA256SUMS` verification fails, the wheels may be corrupted or tampered with: + +```bash +cd vendor/linux-x86_64 +sha256sum -c SHA256SUMS + +# If failures occur, re-download from trusted source +# DO NOT use corrupted wheels for production wallets ``` ### Unzipping AES ZIP issues -Some `unzip` tools may not support AES-encrypted ZIPs. Use Python + pyzipper to extract if needed: +Some `unzip` tools may not support AES-encrypted ZIPs. Use Python + pyzipper to extract: ```bash python - <<'PY' @@ -373,8 +706,6 @@ print("Extracted to", out_dir) 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: @@ -386,7 +717,45 @@ If `pytest -v tests/test_vectors.py` fails after you modified code: 2. **Is only one test failing?** - Run with `-vv` for detailed diff: `pytest -vv tests/test_vectors.py::test_name` ---- +*** + +## Development + +### Building Vendor Wheels + +See [Vendored Dependencies](#vendored-dependencies) section above. + +### Running Tests (Dev) + +```bash +# Quick test +python src/pyhdwallet.py test + +# Full regression suite +pytest -v tests/test_vectors.py + +# Specific test +pytest -v tests/test_vectors.py::test_address_derivation_integrity + +# With coverage +pytest --cov=src tests/ +``` + +### Docker Development Environment + +```bash +# Build image +make build-image + +# Start warm container +make up +make shell + +# Inside container +python src/pyhdwallet.py test +``` + +*** ## Changelog @@ -398,22 +767,12 @@ If `pytest -v tests/test_vectors.py` fails after you modified code: - 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`). + - Added: Vendored dependencies for macOS ARM64 and Linux x86_64 (Python 3.12). + - Added: `install_offline.sh` automated installer for air-gapped deployment. - Changed: `recover` now uses `--mnemonic-stdin` and `--interactive` instead of `--mnemonic` CLI arg. + - Updated: Dockerfile and Makefile now support vendoring workflow. + - **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) diff --git a/requirements-dev.in b/requirements-dev.in new file mode 100644 index 0000000..7374d90 --- /dev/null +++ b/requirements-dev.in @@ -0,0 +1,2 @@ +-r requirements.txt # Include production deps +pytest>=9.0.0 diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..517552f --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,81 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile requirements-dev.in +# +base58==2.1.1 + # via -r requirements.txt +bip-utils==2.10.0 + # via -r requirements.txt +cbor2==5.8.0 + # via + # -r requirements.txt + # bip-utils +cffi==2.0.0 + # via + # -r requirements.txt + # cryptography + # pynacl +coincurve==21.0.0 + # via + # -r requirements.txt + # bip-utils +crcmod==1.7 + # via + # -r requirements.txt + # bip-utils +cryptography==46.0.3 + # via + # -r requirements.txt + # pgpy +ecdsa==0.19.1 + # via + # -r requirements.txt + # bip-utils +ed25519-blake2b==1.4.1 + # via + # -r requirements.txt + # bip-utils +iniconfig==2.3.0 + # via pytest +packaging==25.0 + # via pytest +pgpy==0.6.0 + # via -r requirements.txt +pluggy==1.6.0 + # via pytest +py-sr25519-bindings==0.2.3 + # via + # -r requirements.txt + # bip-utils +pyasn1==0.6.1 + # via + # -r requirements.txt + # pgpy +pycparser==2.23 + # via + # -r requirements.txt + # cffi +pycryptodome==3.23.0 + # via + # -r requirements.txt + # bip-utils +pycryptodomex==3.23.0 + # via + # -r requirements.txt + # pyzipper +pygments==2.19.2 + # via pytest +pynacl==1.6.2 + # via + # -r requirements.txt + # bip-utils +pytest==9.0.2 + # via -r requirements-dev.in +pyzipper==0.3.6 + # via -r requirements.txt +six==1.17.0 + # via + # -r requirements.txt + # ecdsa diff --git a/requirements.in b/requirements.in index 5c3207a..e8e4382 100644 --- a/requirements.in +++ b/requirements.in @@ -1,6 +1,8 @@ -base58==2.1.1 -bip-utils==2.10.0 -pgpy==0.6.0 -pip-chill==1.0.3 -pip-tools==7.5.2 -pyzipper==0.3.6 +# Core crypto libraries +bip-utils>=2.9.0 +PGPy>=0.6.0 +pynacl>=1.5.0 +base58>=2.1.1 + +# ZIP encryption +pyzipper>=0.3.6 diff --git a/vendor/PROVENANCE.md b/vendor/PROVENANCE.md new file mode 100644 index 0000000..6daf9cd --- /dev/null +++ b/vendor/PROVENANCE.md @@ -0,0 +1,43 @@ +# Dependency Provenance + +All wheels generated on: 2026-01-07 17:06:51 UTC +Python version: 3.12 +Build machine: Darwin arm64 +Docker used: Yes (python:3.12-slim) + +## Platforms +- macOS ARM64: Built using .venv312 (Python 3.12) +- Linux x86_64: Built using Docker python:3.12-slim + +## Package Versions +base58==2.1.1 +bip_utils==2.10.0 +build==1.3.0 +cbor2==5.8.0 +cffi==2.0.0 +click==8.3.1 +coincurve==21.0.0 +crcmod==1.7 +cryptography==46.0.3 +ecdsa==0.19.1 +ed25519-blake2b==1.4.1 +iniconfig==2.3.0 +packaging==25.0 +PGPy==0.6.0 +pip-chill==1.0.3 +pip-tools==7.5.2 +pluggy==1.6.0 +py-sr25519-bindings==0.2.3 +pyasn1==0.6.1 +pycparser==2.23 + +## Integrity Verification +Each platform directory contains SHA256SUMS for verification: +```bash +cd vendor/linux-x86_64 +shasum -a 256 -c SHA256SUMS +``` + +Last updated: 2026-01-07 +Built by: LC mac +Commit: 2807982 diff --git a/vendor/linux-x86_64/SHA256SUMS b/vendor/linux-x86_64/SHA256SUMS new file mode 100644 index 0000000..3bb35df --- /dev/null +++ b/vendor/linux-x86_64/SHA256SUMS @@ -0,0 +1,31 @@ +11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2 base58-2.1.1-py3-none-any.whl +33792674bda552a071a539b6590b2986aa8c08d0c9c30c2566d7cb323173310d bip_utils-2.10.0-py3-none-any.whl +7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4 build-1.3.0-py3-none-any.whl +518c118a5e00001854adb51f3164e647aa99b6a9877d2a733a28cb5c0a4d6857 cbor2-5.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl +b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062 cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl +981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6 click-8.3.1-py3-none-any.whl +5a366c314df7217e3357bb8c7d2cda540b0bce180705f7a0ce2d1d9e28f62ad4 coincurve-21.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl +82d740f4e923b88981b6ca8b854dceb270f319c81153211ccbec6ee523c7ccee crcmod-1.7-cp312-cp312-linux_aarch64.whl +549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91 cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl +30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3 ecdsa-0.19.1-py2.py3-none-any.whl +96427209d130354f399ef20acc444c2ac76dd05b13cd270dea79909c39cdf6ec ed25519_blake2b-1.4.1-cp312-cp312-linux_aarch64.whl +f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 iniconfig-2.3.0-py3-none-any.whl +29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 packaging-25.0-py3-none-any.whl +52a584525a9032726a8e28fc02ced9cec896f54aa5c6f5552035000821525f2d pgpy-0.6.0-py3-none-any.whl +452a38edbcdfc333301c438c26ba00a0762d2034fe26a235d8587134453ccdb1 pip_chill-1.0.3-py2.py3-none-any.whl +2fe16db727bbe5bf28765aeb581e792e61be51fc275545ef6725374ad720a1ce pip_tools-7.5.2-py3-none-any.whl +9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd pip-25.3-py3-none-any.whl +e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 pluggy-1.6.0-py3-none-any.whl +a3929c291408e67a1a11566f251b9f7d06c3fb3ae240caec44b9181de09e3fc9 py_sr25519_bindings-0.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl +0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 pyasn1-0.6.1-py3-none-any.whl +e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934 pycparser-2.23-py3-none-any.whl +67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490 pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl +43c446e2ba8df8889e0e16f02211c25b4934898384c1ec1ec04d7889c0333587 pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl +86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b pygments-2.19.2-py3-none-any.whl +26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130 pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl +9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 pyproject_hooks-1.2.0-py3-none-any.whl +711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b pytest-9.0.2-py3-none-any.whl +6d097f465bfa47796b1494e12ea65d1478107d38e13bc56f6e58eedc4f6c1a87 pyzipper-0.3.6-py2.py3-none-any.whl +062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 setuptools-80.9.0-py3-none-any.whl +4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 six-1.17.0-py2.py3-none-any.whl +708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248 wheel-0.45.1-py3-none-any.whl diff --git a/vendor/linux-x86_64/base58-2.1.1-py3-none-any.whl b/vendor/linux-x86_64/base58-2.1.1-py3-none-any.whl new file mode 100644 index 0000000..04086bf Binary files /dev/null and b/vendor/linux-x86_64/base58-2.1.1-py3-none-any.whl differ diff --git a/vendor/linux-x86_64/bip_utils-2.10.0-py3-none-any.whl b/vendor/linux-x86_64/bip_utils-2.10.0-py3-none-any.whl new file mode 100644 index 0000000..7208162 Binary files /dev/null and b/vendor/linux-x86_64/bip_utils-2.10.0-py3-none-any.whl differ diff --git a/vendor/linux-x86_64/build-1.3.0-py3-none-any.whl b/vendor/linux-x86_64/build-1.3.0-py3-none-any.whl new file mode 100644 index 0000000..75a525e Binary files /dev/null and b/vendor/linux-x86_64/build-1.3.0-py3-none-any.whl differ diff --git a/vendor/linux-x86_64/cbor2-5.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl b/vendor/linux-x86_64/cbor2-5.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl new file mode 100644 index 0000000..12da552 Binary files /dev/null and b/vendor/linux-x86_64/cbor2-5.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl differ diff --git a/vendor/linux-x86_64/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl b/vendor/linux-x86_64/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl new file mode 100644 index 0000000..5ffef1d Binary files /dev/null and b/vendor/linux-x86_64/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl differ diff --git a/vendor/linux-x86_64/click-8.3.1-py3-none-any.whl b/vendor/linux-x86_64/click-8.3.1-py3-none-any.whl new file mode 100644 index 0000000..f2513ad Binary files /dev/null and b/vendor/linux-x86_64/click-8.3.1-py3-none-any.whl differ diff --git a/vendor/linux-x86_64/coincurve-21.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl b/vendor/linux-x86_64/coincurve-21.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl new file mode 100644 index 0000000..1631fbf Binary files /dev/null and b/vendor/linux-x86_64/coincurve-21.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl differ diff --git a/vendor/linux-x86_64/crcmod-1.7-cp312-cp312-linux_aarch64.whl b/vendor/linux-x86_64/crcmod-1.7-cp312-cp312-linux_aarch64.whl new file mode 100644 index 0000000..b262b42 Binary files /dev/null and b/vendor/linux-x86_64/crcmod-1.7-cp312-cp312-linux_aarch64.whl differ diff --git a/vendor/linux-x86_64/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl b/vendor/linux-x86_64/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl new file mode 100644 index 0000000..734e9f9 Binary files /dev/null and b/vendor/linux-x86_64/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl differ diff --git a/vendor/linux-x86_64/ecdsa-0.19.1-py2.py3-none-any.whl b/vendor/linux-x86_64/ecdsa-0.19.1-py2.py3-none-any.whl new file mode 100644 index 0000000..a98c904 Binary files /dev/null and b/vendor/linux-x86_64/ecdsa-0.19.1-py2.py3-none-any.whl differ diff --git a/vendor/linux-x86_64/ed25519_blake2b-1.4.1-cp312-cp312-linux_aarch64.whl b/vendor/linux-x86_64/ed25519_blake2b-1.4.1-cp312-cp312-linux_aarch64.whl new file mode 100644 index 0000000..52a89d3 Binary files /dev/null and b/vendor/linux-x86_64/ed25519_blake2b-1.4.1-cp312-cp312-linux_aarch64.whl differ diff --git a/vendor/linux-x86_64/iniconfig-2.3.0-py3-none-any.whl b/vendor/linux-x86_64/iniconfig-2.3.0-py3-none-any.whl new file mode 100644 index 0000000..f8cd4b9 Binary files /dev/null and b/vendor/linux-x86_64/iniconfig-2.3.0-py3-none-any.whl differ diff --git a/vendor/linux-x86_64/packaging-25.0-py3-none-any.whl b/vendor/linux-x86_64/packaging-25.0-py3-none-any.whl new file mode 100644 index 0000000..1809cdb Binary files /dev/null and b/vendor/linux-x86_64/packaging-25.0-py3-none-any.whl differ diff --git a/vendor/linux-x86_64/pgpy-0.6.0-py3-none-any.whl b/vendor/linux-x86_64/pgpy-0.6.0-py3-none-any.whl new file mode 100644 index 0000000..f6d7463 Binary files /dev/null and b/vendor/linux-x86_64/pgpy-0.6.0-py3-none-any.whl differ diff --git a/vendor/linux-x86_64/pip-25.3-py3-none-any.whl b/vendor/linux-x86_64/pip-25.3-py3-none-any.whl new file mode 100644 index 0000000..755e1aa Binary files /dev/null and b/vendor/linux-x86_64/pip-25.3-py3-none-any.whl differ diff --git a/vendor/linux-x86_64/pip_chill-1.0.3-py2.py3-none-any.whl b/vendor/linux-x86_64/pip_chill-1.0.3-py2.py3-none-any.whl new file mode 100644 index 0000000..bc76b05 Binary files /dev/null and b/vendor/linux-x86_64/pip_chill-1.0.3-py2.py3-none-any.whl differ diff --git a/vendor/linux-x86_64/pip_tools-7.5.2-py3-none-any.whl b/vendor/linux-x86_64/pip_tools-7.5.2-py3-none-any.whl new file mode 100644 index 0000000..e17b868 Binary files /dev/null and b/vendor/linux-x86_64/pip_tools-7.5.2-py3-none-any.whl differ diff --git a/vendor/linux-x86_64/pluggy-1.6.0-py3-none-any.whl b/vendor/linux-x86_64/pluggy-1.6.0-py3-none-any.whl new file mode 100644 index 0000000..1f7e626 Binary files /dev/null and b/vendor/linux-x86_64/pluggy-1.6.0-py3-none-any.whl differ diff --git a/vendor/linux-x86_64/py_sr25519_bindings-0.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl b/vendor/linux-x86_64/py_sr25519_bindings-0.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl new file mode 100644 index 0000000..2266442 Binary files /dev/null and b/vendor/linux-x86_64/py_sr25519_bindings-0.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl differ diff --git a/vendor/linux-x86_64/pyasn1-0.6.1-py3-none-any.whl b/vendor/linux-x86_64/pyasn1-0.6.1-py3-none-any.whl new file mode 100644 index 0000000..eef3fa5 Binary files /dev/null and b/vendor/linux-x86_64/pyasn1-0.6.1-py3-none-any.whl differ diff --git a/vendor/linux-x86_64/pycparser-2.23-py3-none-any.whl b/vendor/linux-x86_64/pycparser-2.23-py3-none-any.whl new file mode 100644 index 0000000..0748c19 Binary files /dev/null and b/vendor/linux-x86_64/pycparser-2.23-py3-none-any.whl differ diff --git a/vendor/linux-x86_64/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl b/vendor/linux-x86_64/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl new file mode 100644 index 0000000..2cbf091 Binary files /dev/null and b/vendor/linux-x86_64/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl differ diff --git a/vendor/linux-x86_64/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl b/vendor/linux-x86_64/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl new file mode 100644 index 0000000..6c4e835 Binary files /dev/null and b/vendor/linux-x86_64/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl differ diff --git a/vendor/linux-x86_64/pygments-2.19.2-py3-none-any.whl b/vendor/linux-x86_64/pygments-2.19.2-py3-none-any.whl new file mode 100644 index 0000000..3ec3f10 Binary files /dev/null and b/vendor/linux-x86_64/pygments-2.19.2-py3-none-any.whl differ diff --git a/vendor/linux-x86_64/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl b/vendor/linux-x86_64/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl new file mode 100644 index 0000000..043bb00 Binary files /dev/null and b/vendor/linux-x86_64/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl differ diff --git a/vendor/linux-x86_64/pyproject_hooks-1.2.0-py3-none-any.whl b/vendor/linux-x86_64/pyproject_hooks-1.2.0-py3-none-any.whl new file mode 100644 index 0000000..a86cc9e Binary files /dev/null and b/vendor/linux-x86_64/pyproject_hooks-1.2.0-py3-none-any.whl differ diff --git a/vendor/linux-x86_64/pytest-9.0.2-py3-none-any.whl b/vendor/linux-x86_64/pytest-9.0.2-py3-none-any.whl new file mode 100644 index 0000000..f7bbe54 Binary files /dev/null and b/vendor/linux-x86_64/pytest-9.0.2-py3-none-any.whl differ diff --git a/vendor/linux-x86_64/pyzipper-0.3.6-py2.py3-none-any.whl b/vendor/linux-x86_64/pyzipper-0.3.6-py2.py3-none-any.whl new file mode 100644 index 0000000..98a5dac Binary files /dev/null and b/vendor/linux-x86_64/pyzipper-0.3.6-py2.py3-none-any.whl differ diff --git a/vendor/linux-x86_64/setuptools-80.9.0-py3-none-any.whl b/vendor/linux-x86_64/setuptools-80.9.0-py3-none-any.whl new file mode 100644 index 0000000..2412ad4 Binary files /dev/null and b/vendor/linux-x86_64/setuptools-80.9.0-py3-none-any.whl differ diff --git a/vendor/linux-x86_64/six-1.17.0-py2.py3-none-any.whl b/vendor/linux-x86_64/six-1.17.0-py2.py3-none-any.whl new file mode 100644 index 0000000..c506fd0 Binary files /dev/null and b/vendor/linux-x86_64/six-1.17.0-py2.py3-none-any.whl differ diff --git a/vendor/linux-x86_64/wheel-0.45.1-py3-none-any.whl b/vendor/linux-x86_64/wheel-0.45.1-py3-none-any.whl new file mode 100644 index 0000000..589308a Binary files /dev/null and b/vendor/linux-x86_64/wheel-0.45.1-py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/base58-2.1.1-py3-none-any.whl b/vendor/macos-arm64-docker/base58-2.1.1-py3-none-any.whl new file mode 100644 index 0000000..04086bf Binary files /dev/null and b/vendor/macos-arm64-docker/base58-2.1.1-py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/bip_utils-2.10.0-py3-none-any.whl b/vendor/macos-arm64-docker/bip_utils-2.10.0-py3-none-any.whl new file mode 100644 index 0000000..7208162 Binary files /dev/null and b/vendor/macos-arm64-docker/bip_utils-2.10.0-py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/build-1.3.0-py3-none-any.whl b/vendor/macos-arm64-docker/build-1.3.0-py3-none-any.whl new file mode 100644 index 0000000..75a525e Binary files /dev/null and b/vendor/macos-arm64-docker/build-1.3.0-py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/cbor2-5.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl b/vendor/macos-arm64-docker/cbor2-5.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl new file mode 100644 index 0000000..12da552 Binary files /dev/null and b/vendor/macos-arm64-docker/cbor2-5.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl differ diff --git a/vendor/macos-arm64-docker/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl b/vendor/macos-arm64-docker/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl new file mode 100644 index 0000000..5ffef1d Binary files /dev/null and b/vendor/macos-arm64-docker/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl differ diff --git a/vendor/macos-arm64-docker/click-8.3.1-py3-none-any.whl b/vendor/macos-arm64-docker/click-8.3.1-py3-none-any.whl new file mode 100644 index 0000000..f2513ad Binary files /dev/null and b/vendor/macos-arm64-docker/click-8.3.1-py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/coincurve-21.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl b/vendor/macos-arm64-docker/coincurve-21.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl new file mode 100644 index 0000000..1631fbf Binary files /dev/null and b/vendor/macos-arm64-docker/coincurve-21.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl differ diff --git a/vendor/macos-arm64-docker/crcmod-1.7-cp312-cp312-linux_aarch64.whl b/vendor/macos-arm64-docker/crcmod-1.7-cp312-cp312-linux_aarch64.whl new file mode 100644 index 0000000..252e58f Binary files /dev/null and b/vendor/macos-arm64-docker/crcmod-1.7-cp312-cp312-linux_aarch64.whl differ diff --git a/vendor/macos-arm64-docker/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl b/vendor/macos-arm64-docker/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl new file mode 100644 index 0000000..734e9f9 Binary files /dev/null and b/vendor/macos-arm64-docker/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl differ diff --git a/vendor/macos-arm64-docker/ecdsa-0.19.1-py2.py3-none-any.whl b/vendor/macos-arm64-docker/ecdsa-0.19.1-py2.py3-none-any.whl new file mode 100644 index 0000000..a98c904 Binary files /dev/null and b/vendor/macos-arm64-docker/ecdsa-0.19.1-py2.py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/ed25519_blake2b-1.4.1-cp312-cp312-linux_aarch64.whl b/vendor/macos-arm64-docker/ed25519_blake2b-1.4.1-cp312-cp312-linux_aarch64.whl new file mode 100644 index 0000000..6b67347 Binary files /dev/null and b/vendor/macos-arm64-docker/ed25519_blake2b-1.4.1-cp312-cp312-linux_aarch64.whl differ diff --git a/vendor/macos-arm64-docker/iniconfig-2.3.0-py3-none-any.whl b/vendor/macos-arm64-docker/iniconfig-2.3.0-py3-none-any.whl new file mode 100644 index 0000000..f8cd4b9 Binary files /dev/null and b/vendor/macos-arm64-docker/iniconfig-2.3.0-py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/packaging-25.0-py3-none-any.whl b/vendor/macos-arm64-docker/packaging-25.0-py3-none-any.whl new file mode 100644 index 0000000..1809cdb Binary files /dev/null and b/vendor/macos-arm64-docker/packaging-25.0-py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/pgpy-0.6.0-py3-none-any.whl b/vendor/macos-arm64-docker/pgpy-0.6.0-py3-none-any.whl new file mode 100644 index 0000000..f192d46 Binary files /dev/null and b/vendor/macos-arm64-docker/pgpy-0.6.0-py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/pip-25.3-py3-none-any.whl b/vendor/macos-arm64-docker/pip-25.3-py3-none-any.whl new file mode 100644 index 0000000..755e1aa Binary files /dev/null and b/vendor/macos-arm64-docker/pip-25.3-py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/pip_chill-1.0.3-py2.py3-none-any.whl b/vendor/macos-arm64-docker/pip_chill-1.0.3-py2.py3-none-any.whl new file mode 100644 index 0000000..bc76b05 Binary files /dev/null and b/vendor/macos-arm64-docker/pip_chill-1.0.3-py2.py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/pip_tools-7.5.2-py3-none-any.whl b/vendor/macos-arm64-docker/pip_tools-7.5.2-py3-none-any.whl new file mode 100644 index 0000000..e17b868 Binary files /dev/null and b/vendor/macos-arm64-docker/pip_tools-7.5.2-py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/pluggy-1.6.0-py3-none-any.whl b/vendor/macos-arm64-docker/pluggy-1.6.0-py3-none-any.whl new file mode 100644 index 0000000..1f7e626 Binary files /dev/null and b/vendor/macos-arm64-docker/pluggy-1.6.0-py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/py_sr25519_bindings-0.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl b/vendor/macos-arm64-docker/py_sr25519_bindings-0.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl new file mode 100644 index 0000000..2266442 Binary files /dev/null and b/vendor/macos-arm64-docker/py_sr25519_bindings-0.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl differ diff --git a/vendor/macos-arm64-docker/pyasn1-0.6.1-py3-none-any.whl b/vendor/macos-arm64-docker/pyasn1-0.6.1-py3-none-any.whl new file mode 100644 index 0000000..eef3fa5 Binary files /dev/null and b/vendor/macos-arm64-docker/pyasn1-0.6.1-py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/pycparser-2.23-py3-none-any.whl b/vendor/macos-arm64-docker/pycparser-2.23-py3-none-any.whl new file mode 100644 index 0000000..0748c19 Binary files /dev/null and b/vendor/macos-arm64-docker/pycparser-2.23-py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl b/vendor/macos-arm64-docker/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl new file mode 100644 index 0000000..2cbf091 Binary files /dev/null and b/vendor/macos-arm64-docker/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl differ diff --git a/vendor/macos-arm64-docker/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl b/vendor/macos-arm64-docker/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl new file mode 100644 index 0000000..6c4e835 Binary files /dev/null and b/vendor/macos-arm64-docker/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl differ diff --git a/vendor/macos-arm64-docker/pygments-2.19.2-py3-none-any.whl b/vendor/macos-arm64-docker/pygments-2.19.2-py3-none-any.whl new file mode 100644 index 0000000..3ec3f10 Binary files /dev/null and b/vendor/macos-arm64-docker/pygments-2.19.2-py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl b/vendor/macos-arm64-docker/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl new file mode 100644 index 0000000..043bb00 Binary files /dev/null and b/vendor/macos-arm64-docker/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl differ diff --git a/vendor/macos-arm64-docker/pyproject_hooks-1.2.0-py3-none-any.whl b/vendor/macos-arm64-docker/pyproject_hooks-1.2.0-py3-none-any.whl new file mode 100644 index 0000000..a86cc9e Binary files /dev/null and b/vendor/macos-arm64-docker/pyproject_hooks-1.2.0-py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/pytest-9.0.2-py3-none-any.whl b/vendor/macos-arm64-docker/pytest-9.0.2-py3-none-any.whl new file mode 100644 index 0000000..f7bbe54 Binary files /dev/null and b/vendor/macos-arm64-docker/pytest-9.0.2-py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/pyzipper-0.3.6-py2.py3-none-any.whl b/vendor/macos-arm64-docker/pyzipper-0.3.6-py2.py3-none-any.whl new file mode 100644 index 0000000..98a5dac Binary files /dev/null and b/vendor/macos-arm64-docker/pyzipper-0.3.6-py2.py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/setuptools-80.9.0-py3-none-any.whl b/vendor/macos-arm64-docker/setuptools-80.9.0-py3-none-any.whl new file mode 100644 index 0000000..2412ad4 Binary files /dev/null and b/vendor/macos-arm64-docker/setuptools-80.9.0-py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/six-1.17.0-py2.py3-none-any.whl b/vendor/macos-arm64-docker/six-1.17.0-py2.py3-none-any.whl new file mode 100644 index 0000000..c506fd0 Binary files /dev/null and b/vendor/macos-arm64-docker/six-1.17.0-py2.py3-none-any.whl differ diff --git a/vendor/macos-arm64-docker/wheel-0.45.1-py3-none-any.whl b/vendor/macos-arm64-docker/wheel-0.45.1-py3-none-any.whl new file mode 100644 index 0000000..589308a Binary files /dev/null and b/vendor/macos-arm64-docker/wheel-0.45.1-py3-none-any.whl differ diff --git a/vendor/macos-arm64/SHA256SUMS b/vendor/macos-arm64/SHA256SUMS new file mode 100644 index 0000000..12915e2 --- /dev/null +++ b/vendor/macos-arm64/SHA256SUMS @@ -0,0 +1,28 @@ +11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2 base58-2.1.1-py3-none-any.whl +33792674bda552a071a539b6590b2986aa8c08d0c9c30c2566d7cb323173310d bip_utils-2.10.0-py3-none-any.whl +7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4 build-1.3.0-py3-none-any.whl +4b3f91fa699a5ce22470e973601c62dd9d55dc3ca20ee446516ac075fcab27c9 cbor2-5.8.0-cp312-cp312-macosx_11_0_arm64.whl +8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl +981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6 click-8.3.1-py3-none-any.whl +1cb1cd19fb0be22e68ecb60ad950b41f18b9b02eebeffaac9391dc31f74f08f2 coincurve-21.0.0-cp312-cp312-macosx_11_0_arm64.whl +109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl +30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3 ecdsa-0.19.1-py2.py3-none-any.whl +f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 iniconfig-2.3.0-py3-none-any.whl +29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 packaging-25.0-py3-none-any.whl +452a38edbcdfc333301c438c26ba00a0762d2034fe26a235d8587134453ccdb1 pip_chill-1.0.3-py2.py3-none-any.whl +2fe16db727bbe5bf28765aeb581e792e61be51fc275545ef6725374ad720a1ce pip_tools-7.5.2-py3-none-any.whl +9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd pip-25.3-py3-none-any.whl +e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 pluggy-1.6.0-py3-none-any.whl +4443adf871e224493c4ee4c06be205a10ea649a781132af883f6638fd7acc9d7 py_sr25519_bindings-0.2.3-cp312-cp312-macosx_11_0_arm64.whl +0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 pyasn1-0.6.1-py3-none-any.whl +e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934 pycparser-2.23-py3-none-any.whl +187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27 pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl +06698f957fe1ab229a99ba2defeeae1c09af185baa909a31a5d1f9d42b1aaed6 pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_universal2.whl +86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b pygments-2.19.2-py3-none-any.whl +c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465 pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl +9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 pyproject_hooks-1.2.0-py3-none-any.whl +711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b pytest-9.0.2-py3-none-any.whl +6d097f465bfa47796b1494e12ea65d1478107d38e13bc56f6e58eedc4f6c1a87 pyzipper-0.3.6-py2.py3-none-any.whl +062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 setuptools-80.9.0-py3-none-any.whl +4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 six-1.17.0-py2.py3-none-any.whl +708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248 wheel-0.45.1-py3-none-any.whl