Compare commits

...

26 Commits

Author SHA1 Message Date
b47935de86 Update README.md 2026-01-10 01:01:15 +08:00
LC mac
46f748301e chore: cleanup and prepare v1.1.0 release (Makefile clean up) 2026-01-10 00:52:58 +08:00
LC mac
27ee16f21a chore: cleanup and prepare v1.1.0 release 2026-01-10 00:50:50 +08:00
LC mac
0a748dd8b0 vendor: update wheels 2026-01-10 00:45:54 +08:00
LC mac
c41928688c chore: cleanup build artifacts and add .gitignore 2026-01-10 00:39:06 +08:00
d4f6e3d207 rebuild the offline wheel using Linux environment; add a test asc file to git 2026-01-09 14:41:45 +00:00
LC mac
a6c84f81ee chore: bump version to v1.1.0 2026-01-09 19:30:10 +08:00
109829f1f5 Update playbook.md 2026-01-09 19:20:22 +08:00
LC mac
2f7433b704 add wheels and update docs 2026-01-09 19:16:28 +08:00
LC mac
6457ec2cee chore: recompile requirements.txt 2026-01-09 18:55:57 +08:00
LC mac
0949fe9792 feat(bip85): add gen-child command (v1.0.6)
Implements BIP85 child mnemonic derivation with full interoperability.

Features:
- Derives child BIP39 mnemonics (12/15/18/21/24 words) from master mnemonic
- BIP85 path: m/83696968'/39'/0'/{words}'/{index}'
- Supports optional master BIP39 passphrase
- Reuses existing input modes (--interactive, --mnemonic-stdin)
- Follows existing UX patterns (--off-screen, --file, PGP encryption)
- Offline-first with NetworkGuard protection

Testing:
- Adds deterministic regression tests for BIP85 spec compliance
- Verified against official BIP85 test vectors
- CLI smoke tests for end-to-end validation

Interoperability:
- Produces mnemonics compatible with Coldcard, Ian Coleman tool, etc.
- Test vector verified: 'girl mad pet galaxy egg matter matrix prison refuse sense ordinary nose'

Version bumped to v1.0.6
2026-01-09 18:46:19 +08:00
21b9389591 add vendor folder for linux-aarch64 2026-01-09 02:58:31 +08:00
LC mac
369c8595a1 Refs: offline install via vendored wheels and test verification workflow (see README/playbook updates) 2026-01-08 01:45:25 +08:00
LC mac
2807982209 vendor: add macOS ARM64 wheels for offline installation (Python 3.12) 2026-01-08 00:42:22 +08:00
LC mac
84953dbe5a add BestPractices.md 2026-01-08 00:12:37 +08:00
LC mac
6fd7cd4e79 modified playbook.md 2026-01-08 00:05:27 +08:00
LC mac
5a52eb0954 test: add offline integrity test suite with frozen vectors 2026-01-07 23:54:25 +08:00
LC mac
28f01a613a format the README.md 2026-01-07 23:14:05 +08:00
a9af9d33af add Makefile Dockerfile README.md 2026-01-07 21:03:00 +08:00
129b09fcd9 adding wheelhouse 2026-01-07 20:53:37 +08:00
LC mac
d02e1d872e with figerprint match option when encryption with asc file, extra assurance the file is the target asc file 2026-01-07 02:21:03 +08:00
LC mac
875fa17d6c patch the recover logic with interactive mode 2026-01-07 01:45:35 +08:00
LC mac
ccd070dc56 modified README 2026-01-07 00:53:39 +08:00
LC mac
ce26b3560a modified doc to v1.0.5 2026-01-07 00:40:47 +08:00
LC
4cf32f9ba0 Remove venv from previous commit 2026-01-06 15:48:15 +00:00
LC
94fcb993db Fix: ignore venv and commit project files 2026-01-06 15:25:11 +00:00
59 changed files with 4013 additions and 493 deletions

82
.gitignore vendored
View File

@@ -1,25 +1,79 @@
.venv/ # Python
__pycache__/ __pycache__/
*.py[cod]
*$py.class
*.pyc *.pyc
.vscode/ *.so
.env .Python
.DS_Store
.idea/
logs/
*.log
coverage/
_toDelete/
_toDelete/
dist/
build/
*.egg-info/ *.egg-info/
.ipynb_checkpoints/ .ipynb_checkpoints/
.envrc
# Virtual environments
.venv/
.venv312/
.venv-*/
venv/
ENV/
env/
# Build artifacts
dist/
build/
*.spec
wheelhouse/
# PyInstaller temporary files
src/*_frozen.py
# Testing
.pytest_cache/ .pytest_cache/
.cache/
coverage/
# Type checking
.mypy_cache/ .mypy_cache/
.pyre/ .pyre/
.pytype/ .pytype/
.cache/
# IDEs
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Environment
.env
.envrc
# Logs
logs/
*.log
# Database
*.sqlite3 *.sqlite3
*.db *.db
# Project specific
.wallet/
releases/*/
_toDelete/
.potentialfix.md
# PGP keys (except test data)
*.asc *.asc
!tests/data/recipient.asc
# Vendor cleanup (wrong architectures/test builds)
vendor/linux-aarch64/
vendor/macos-arm64-docker/
vendor/*-docker/
# Keep official vendor directories
# vendor/macos-arm64/
# vendor/linux-x86_64/
# vendor/PROVENANCE.md

18
Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
# Dockerfile
# 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 \
build-essential \
python3-dev \
libffi-dev \
python3-venv \
&& rm -rf /var/lib/apt/lists/*
# Set working directory for bind mounts
WORKDIR /app
# Default command (can be overridden)
CMD ["/bin/bash"]

334
Makefile Normal file
View File

@@ -0,0 +1,334 @@
# Makefile for hdwalletpy workflow
# - 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 or vendor/
# - Optionally run inside a warm container
# ---------- Platform Detection ----------
UNAME_S := $(shell uname -s)
UNAME_M := $(shell uname -m)
ifeq ($(UNAME_S),Darwin)
PLATFORM := macos
ifeq ($(UNAME_M),arm64)
ARCH := arm64
else
$(error Unsupported macOS architecture: $(UNAME_M))
endif
else ifeq ($(UNAME_S),Linux)
PLATFORM := linux
ARCH := x86_64
else
$(error Unsupported OS: $(UNAME_S))
endif
VENDOR_DIR := vendor/$(PLATFORM)-$(ARCH)
# ---------- Config ----------
IMAGE := hdwallet-build:3.12
CONTAINER := hdwallet-dev
WORKDIR := /app
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 "pyhdwallet build system (macOS + Linux compatible)"
@echo ""
@echo "📦 Vendoring (for offline/air-gapped use):"
@echo " make vendor-macos - Build macOS ARM64 wheels (requires macOS native)"
@echo " make vendor-linux - Build Linux x86_64 wheels (Docker - any OS)"
@echo " make vendor-all - Build wheels for both platforms (macOS native + Docker)"
@echo " make verify-vendor - Test offline installation from vendor/"
@echo ""
@echo "🔨 Binary Distribution:"
@echo " make binary - Build standalone binary for current platform"
@echo " make binary-linux - Build Linux binary via Docker (any OS)"
@echo " make binary-all - Build binaries for all platforms"
@echo ""
@echo "🚀 Release Management:"
@echo " make release - Build complete release (binaries + vendors + checksums)"
@echo " make release-binaries - Copy binaries to releases/vX.Y.Z/"
@echo " make release-checksums - Generate SHA256SUMS for binaries"
@echo " make release-test - Test release binaries"
@echo " make clean-release - Remove all release artifacts"
@echo ""
@echo "🛠️ Development workflow (works on macOS + Linux):"
@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"
@echo " make clean-release - Remove releases/ only"
@echo ""
@echo " Platform Info:"
@echo " make info - Show platform detection info"
@echo ""
@echo "Platform notes:"
@echo " • vendor-macos requires native macOS (uses .venv312 with ARM64 Python)"
@echo " • vendor-linux works on any platform with Docker"
@echo " • binary-linux works on any platform with Docker"
@echo " • All other targets work on macOS and Linux"
# ---------- Build reusable image ----------
.PHONY: build-image
build-image:
docker build -t $(IMAGE) .
# ---------- 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 \
'
# ---------- Binary Building ----------
.PHONY: binary
binary:
@echo "🔨 Building binary for current platform: $(PLATFORM)-$(ARCH)..."
ifeq ($(PLATFORM),macos)
@if [ ! -f "build_binary.sh" ]; then \
echo "❌ build_binary.sh not found!"; \
exit 1; \
fi
@bash build_binary.sh
else
@echo "⚠️ For Linux native builds, use build_binary.sh directly"
@echo " Or use: make binary-linux (builds via Docker)"
endif
.PHONY: binary-linux
binary-linux:
@./build_binary_linux.sh
.PHONY: binary-all
binary-all:
@echo "🏗️ Building binaries for all platforms..."
@echo ""
@echo "1⃣ Building native binary..."
@$(MAKE) binary
@echo ""
@echo "2⃣ Building Linux binary via Docker..."
@$(MAKE) binary-linux
@echo ""
@echo "✅ All binaries built:"
@ls -lh dist/pyhdwallet* 2>/dev/null | awk '{print " " $$9 " (" $$5 ")"}' || echo " No binaries found"
# ---------- Development Workflow ----------
.PHONY: wheels
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) \
"
.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: build-image
docker run -dit \
-v "$$PWD":$(WORKDIR) \
-w $(WORKDIR) \
--name $(CONTAINER) \
$(IMAGE) \
bash
.PHONY: shell
shell:
docker exec -it $(CONTAINER) bash
.PHONY: down
down:
- docker rm -f $(CONTAINER)
# ---------- Cleanup ----------
.PHONY: clean-vendor
clean-vendor:
rm -rf vendor/
.PHONY: clean
clean: down
rm -rf $(VENV_HOST) $(WHEELHOUSE) .venv-verify .venv-offline-test
rm -rf dist/ build/ *.spec src/pyhdwallet_frozen.py
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
# NOTE: vendor/ is kept (use 'make clean-vendor' to remove)
# ---------- Platform-Aware Operations ----------
.PHONY: info
info:
@echo "Platform Information:"
@echo " OS: $(PLATFORM)"
@echo " Architecture: $(ARCH)"
@echo " Vendor dir: $(VENDOR_DIR)"
@echo " Python: $(shell python3 --version 2>/dev/null || echo 'not found')"
@echo " Docker: $(shell docker --version 2>/dev/null || echo 'not found')"
# ---------- Release Management ----------
VERSION := $(shell grep 'VERSION = ' src/pyhdwallet.py | head -1 | sed 's/.*"\(.*\)".*/\1/' || echo "v1.1.0")
RELEASE_DIR := releases/$(VERSION)
.PHONY: release-prep
release-prep:
@echo "📦 Preparing release $(VERSION)..."
@mkdir -p $(RELEASE_DIR)
.PHONY: release-binaries
release-binaries: release-prep binary binary-linux
@echo "📦 Copying binaries to $(RELEASE_DIR)..."
@cp dist/pyhdwallet $(RELEASE_DIR)/pyhdwallet-macos-arm64
@cp dist/pyhdwallet-linux $(RELEASE_DIR)/pyhdwallet-linux-x86_64
@echo "✅ Binaries copied"
.PHONY: release-checksums
release-checksums: release-binaries
@echo "🔐 Generating checksums..."
@cd $(RELEASE_DIR) && shasum -a 256 pyhdwallet-* > SHA256SUMS
@echo "✅ Checksums generated:"
@cat $(RELEASE_DIR)/SHA256SUMS
.PHONY: release-vendors
release-vendors: release-prep vendor-all
@echo "📦 Copying vendor wheels to $(RELEASE_DIR)..."
@mkdir -p $(RELEASE_DIR)/vendor
@cp -r vendor/macos-arm64 $(RELEASE_DIR)/vendor/
@cp -r vendor/linux-x86_64 $(RELEASE_DIR)/vendor/
@echo "✅ Vendor wheels copied"
.PHONY: release
release: release-checksums
@echo ""
@echo "🎉 Release $(VERSION) binaries ready!"
@echo ""
@if [ -d ".venv312" ]; then \
echo "📦 Building vendor wheels..."; \
$(MAKE) release-vendors; \
else \
echo "⚠️ Skipping vendor wheels (.venv312 not found)"; \
echo " To include vendors: python3.12 -m venv .venv312 && make release-vendors"; \
fi
@echo ""
@echo "Contents:"
@ls -lh $(RELEASE_DIR)/ | tail -n +2
@echo ""
@cat $(RELEASE_DIR)/SHA256SUMS
@echo ""
@echo "Next steps:"
@echo " 1. Test: make release-test"
@echo " 2. Tag: git tag -a $(VERSION) -m 'Release $(VERSION)'"
@echo " 3. Push: git push origin $(VERSION)"
.PHONY: release-test
release-test:
@echo "🧪 Testing release binaries..."
@echo ""
@echo "Testing macOS binary:"
@$(RELEASE_DIR)/pyhdwallet-macos-arm64 test || echo "⚠️ macOS binary test skipped (wrong platform)"
@echo ""
@echo "Testing Linux binary (via Docker):"
@docker run --rm --platform linux/amd64 \
-v "$$PWD/$(RELEASE_DIR)":/binaries \
ubuntu:22.04 \
/binaries/pyhdwallet-linux-x86_64 test
.PHONY: clean-release
clean-release:
@echo "🧹 Cleaning release artifacts..."
@rm -rf releases/
@echo "✅ Release artifacts cleaned"

283
README.md
View File

@@ -1,31 +1,274 @@
# pyhdwallet v1.0.4 # pyhdwallet Secure HD Wallet Tool
A secure, offline command-line tool for generating and recovering BIP39 HD wallets with support for Ethereum, Solana, and Bitcoin. A Python command-line tool for generating and recovering BIP39 HD wallets with support for Ethereum, Solana, and Bitcoin. Includes **BIP85 deterministic child mnemonic derivation** for creating multiple isolated wallets from a single master seed. Designed for offline operation with optional PGP encryption and AES-encrypted ZIP artifacts.
## Purpose ---
pyhdwallet helps you create new wallets or recover from existing mnemonics/seeds, with built-in PGP encryption for secure storage. It's designed for privacy-conscious users who want offline, auditable wallet management. ## 📦 Installation
## Installation ### **Quick Start (macOS/Linux with Internet)**
1. Ensure Python 3.11+ is installed. ```bash
2. Clone/download the repo. # Clone repository
3. Create a virtual environment: `python -m venv .venv && source .venv/bin/activate` git clone https://github.com/<your-username>/hdwalletpy.git
4. Install dependencies: `pip install -r requirements.txt` cd hdwalletpy
## Basic Usage # Install using automated script
./install_offline.sh
```
Run the tool with a subcommand. For help, use `-h`. The script automatically:
- Generate a wallet: `python ./src/pyhdwallet.py gen --chains ethereum --addresses 3` - Creates Python 3.12 virtual environment
- Recover from mnemonic: `python ./src/pyhdwallet.py recover --mnemonic "your words" --chains bitcoin` - Installs from vendored wheels (offline-capable)
- Fetch PGP key: `python ./src/pyhdwallet.py fetchkey "https://example.com/key.asc"` - Verifies installation with test suite
- Run tests: `python ./src/pyhdwallet.py test` - Leaves you in activated venv
For detailed examples and security tips, see `playbook.md`. ---
## Security ### **Air-Gapped Installation (No Internet)**
- Operates offline by default. This repository includes pre-built Python wheels for offline use.
- Use `--secure-mode` for high-security operations.
- Always verify PGP keys and run on trusted systems. **Supported platforms:**
- macOS ARM64 (M1/M2/M3/M4) - 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/<your-username>/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. Transfer entire repo to USB drive
3. On air-gapped machine:
```bash
cd hdwalletpy
./install_offline.sh
```
4. Generate wallet:
```bash
python src/pyhdwallet.py gen --off-screen --file
```
---
### **Developer Installation (with Docker)**
For development or building wheels for other platforms:
```bash
# Build Docker image
make build-image
# Build wheels for all platforms
make vendor-all
# Install development environment
make install
# Run tests
make test
```
---
## ✅ Basic Usage
```bash
# Generate wallet (prints mnemonic - debug mode)
python src/pyhdwallet.py gen
# Generate with off-screen mode + encrypted ZIP
python src/pyhdwallet.py gen --off-screen --file
# Generate with PGP encryption + ZIP
python src/pyhdwallet.py gen \
--pgp-pubkey-file pubkeys/mykey.asc \
--expected-fingerprint A27B96F2B169B5491013D2DA892B822C14A9AA18 \
--off-screen \
--file
# Derive BIP85 child mnemonic (12/15/18/21/24 words)
python src/pyhdwallet.py gen-child \
--interactive \
--words 12 \
--index 0
# Derive BIP85 child with passphrase + encrypted output
python src/pyhdwallet.py gen-child \
--mnemonic-stdin \
--passphrase \
--words 24 \
--index 0 \
--pgp-pubkey-file pubkeys/mykey.asc \
--off-screen \
--file
# Recover wallet from mnemonic
python src/pyhdwallet.py recover --interactive --file
# 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
pytest -v tests/test_vectors.py
```
---
## 🔐 Security Features
- **Offline-first**: Network access blocked during key generation/recovery
- **BIP85 deterministic entropy**: Derive unlimited child mnemonics from master seed
- **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
- **Interoperable**: BIP85 children compatible with Coldcard, Ian Coleman tool, etc.
---
## 🛠️ Makefile Targets
**Vendoring (for air-gapped deployment):**
```bash
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
```
**Development:**
```bash
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.
## 🖥️ Platform Compatibility
### Makefile Targets
| Target | macOS | Linux | Notes |
| -------- | ------- | ------- | ------- |
| `make install` | ✅ | ✅ | Create venv + install deps |
| `make test` | ✅ | ✅ | Run test suite |
| `make vendor-macos` | ✅ | ❌ | Requires native macOS ARM64 |
| `make vendor-linux` | ✅ | ✅ | Uses Docker (any OS) |
| `make vendor-all` | ✅ | ⚠️ | Needs macOS for both platforms |
| `make binary` | ✅ | ❌ | Requires `build_binary.sh` |
| `make binary-linux` | ✅ | ✅ | Uses Docker (any OS) |
| `make build-image` | ✅ | ✅ | Docker required |
### Quick Commands by Platform
**macOS:**
```bash
make info # Show platform info
make vendor-current # Build for current platform only
make binary-current # Build binary for current platform
```
**Linux:***
```bash
make info # Show platform info
make vendor-linux # Build Linux wheels
make binary-linux # Build Linux binary
```
***Cross-platform (requires Docker):***
```bash
make vendor-linux # Works on any OS with Docker
make binary-linux # Works on any OS with Docker
```
## Quick Test
```bash
# Check platform detection
make info
# Build for your current platform
make vendor-current
# Or just the targets you need
make install # Works everywhere
make test # Works everywhere
```
## 📦 Binary Distribution
Pre-built standalone binaries are available for convenience (no Python required):
### Download Binary
```bash
# macOS ARM64
curl -LO https://github.com/user/hdwalletpy/releases/download/v1.1.0/pyhdwallet-macos-arm64
chmod +x pyhdwallet-macos-arm64
./pyhdwallet-macos-arm64 test
# Linux x86_64
curl -LO https://github.com/user/hdwalletpy/releases/download/v1.1.0/pyhdwallet-linux
chmod +x pyhdwallet-linux
./pyhdwallet-linux test
```
---
## 🆕 What's New in v1.1.0
- **BIP85 child mnemonic derivation** via new `gen-child` command
- Derive 12/15/18/21/24-word child mnemonics from master seed
- Full interoperability with BIP85-compatible wallets (Coldcard, etc.)
- Optional master BIP39 passphrase support
- Verified against official BIP85 test vectors
- No new dependencies required (uses existing bip-utils + stdlib)
---

115
build-all-platforms.sh Executable file
View File

@@ -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'"

76
build_binary.sh Executable file
View File

@@ -0,0 +1,76 @@
#!/bin/bash
set -e
echo "🔨 Building pyhdwallet standalone binary..."
# Check venv more strictly
if [ -z "$VIRTUAL_ENV" ] || [[ "$VIRTUAL_ENV" != *"hdwalletpy"* ]]; then
echo "❌ Not in virtual environment!"
echo ""
echo "Activate it first:"
echo " source .venv/bin/activate"
exit 1
fi
# Install PyInstaller if needed
if ! pip show pyinstaller &>/dev/null; then
echo "📦 Installing PyInstaller..."
pip install pyinstaller
fi
# Create patched version for frozen builds
echo "📝 Creating frozen-compatible version..."
python3 << 'PATCH'
with open('src/pyhdwallet.py', 'r') as f:
content = f.read()
# Patch _require() for PyInstaller
content = content.replace(
"""def _require(mod: str, pkg: str) -> None:
try:
__import__(mod)""",
"""def _require(mod: str, pkg: str) -> None:
# Skip check in PyInstaller frozen executable
if getattr(sys, 'frozen', False):
return
try:
__import__(mod)"""
)
with open('src/pyhdwallet_frozen.py', 'w') as f:
f.write(content)
PATCH
# Clean previous builds
rm -rf build/ dist/ *.spec
# Build
echo "🔧 Building with PyInstaller..."
pyinstaller --onefile \
--name pyhdwallet \
--clean \
--collect-all bip_utils \
--collect-all pgpy \
--collect-all nacl \
--collect-all pyzipper \
--collect-all coincurve \
--copy-metadata coincurve \
src/pyhdwallet_frozen.py
# Clean up temp file
rm src/pyhdwallet_frozen.py
# Test
echo ""
echo "✅ Testing binary..."
./dist/pyhdwallet test
# Show results
echo ""
echo "🎉 Binary created successfully!"
echo " Location: dist/pyhdwallet"
echo " Size: $(du -h dist/pyhdwallet | cut -f1)"
echo ""
echo "Quick test:"
echo " ./dist/pyhdwallet gen --help"
echo " ./dist/pyhdwallet gen-child --help"

70
build_binary_linux.sh Executable file
View File

@@ -0,0 +1,70 @@
#!/bin/bash
set -e
echo "🐧 Building Linux x86_64 binary via Docker (Ubuntu 22.04 base)..."
mkdir -p dist
docker run --rm \
--platform linux/amd64 \
-v "$PWD":/work \
-w /work \
ubuntu:22.04 \
bash -c '
set -e
export DEBIAN_FRONTEND=noninteractive
# Install Python 3.12 and build tools
apt-get update -qq
apt-get install -y -qq software-properties-common
add-apt-repository -y ppa:deadsnakes/ppa
apt-get update -qq
apt-get install -y -qq \
python3.12 \
python3.12-venv \
python3.12-dev \
binutils \
gcc \
g++ \
make \
libffi-dev
# Install pip for Python 3.12
python3.12 -m ensurepip
python3.12 -m pip install -q --upgrade pip
python3.12 -m pip install -q pyinstaller
python3.12 -m pip install -q -r requirements.txt
echo "📝 Patching for frozen build..."
python3.12 -c "
import sys
with open(\"src/pyhdwallet.py\", \"r\") as f:
content = f.read()
content = content.replace(
\"def _require(mod: str, pkg: str) -> None:\\n try:\",
\"def _require(mod: str, pkg: str) -> None:\\n import sys\\n if getattr(sys, \\\"frozen\\\", False): return\\n try:\"
)
with open(\"/tmp/pyhdwallet_frozen.py\", \"w\") as f:
f.write(content)
"
echo "🔧 Building with PyInstaller..."
python3.12 -m PyInstaller --onefile --name pyhdwallet-linux --clean \
--collect-all bip_utils \
--collect-all pgpy \
--collect-all nacl \
--collect-all pyzipper \
--collect-all coincurve \
--copy-metadata coincurve \
/tmp/pyhdwallet_frozen.py
echo "✅ Linux binary created (compatible with Ubuntu 22.04+)"
'
echo ""
echo "Binary location: dist/pyhdwallet-linux"
echo "Size: $(du -h dist/pyhdwallet-linux 2>/dev/null | cut -f1 || echo 'N/A')"
echo "GLIBC: $(docker run --rm --platform linux/amd64 -v "$PWD/dist":/b ubuntu:22.04 ldd /b/pyhdwallet-linux | grep libc.so || echo 'static')"
echo ""
echo "Test it:"
echo " docker run --rm --platform linux/amd64 -v \"\$PWD/dist\":/bin ubuntu:22.04 /bin/pyhdwallet-linux test"

117
install_offline.sh Executable file
View File

@@ -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 ""

File diff suppressed because it is too large Load Diff

7
pytest.ini Normal file
View File

@@ -0,0 +1,7 @@
[pytest]
filterwarnings =
ignore::DeprecationWarning:pgpy.constants
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*

2
requirements-dev.in Normal file
View File

@@ -0,0 +1,2 @@
-r requirements.txt # Include production deps
pytest>=9.0.0

81
requirements-dev.txt Normal file
View File

@@ -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

View File

@@ -1,2 +1,8 @@
PGPy # Core crypto libraries
bip-utils bip-utils>=2.9.0
PGPy>=0.6.0
pynacl>=1.5.0
base58>=2.1.1
# ZIP encryption
pyzipper>=0.3.6

View File

@@ -1,9 +1,11 @@
# #
# This file is autogenerated by pip-compile with Python 3.11 # This file is autogenerated by pip-compile with Python 3.12
# by the following command: # by the following command:
# #
# pip-compile # pip-compile requirements.in
# #
base58==2.1.1
# via -r requirements.in
bip-utils==2.10.0 bip-utils==2.10.0
# via -r requirements.in # via -r requirements.in
cbor2==5.8.0 cbor2==5.8.0
@@ -32,7 +34,13 @@ pycparser==2.23
# via cffi # via cffi
pycryptodome==3.23.0 pycryptodome==3.23.0
# via bip-utils # via bip-utils
pycryptodomex==3.23.0
# via pyzipper
pynacl==1.6.2 pynacl==1.6.2
# via bip-utils # via
# -r requirements.in
# bip-utils
pyzipper==0.3.6
# via -r requirements.in
six==1.17.0 six==1.17.0
# via ecdsa # via ecdsa

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
{
"master_fingerprint": "DD1449B7",
"passphrase_used": false,
"passphrase_hint": "",
"dice_rolls_used": false,
"solana_profile": "phantom_bip44change",
"addresses": {
"ethereum": [
{
"index": 0,
"path": "m/44'/60'/0'/0/0",
"address": "0x9d3e3540f4C507ca992035607326798130051e03"
}
]
}
}

104
tests/bootstrap_vectors.py Normal file
View File

@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""
Bootstrap script to generate tests/vectors.json.
Run this ONCE (online allowed if needed, though this logic is offline)
to freeze the expected outputs of the current pyhdwallet implementation.
"""
import sys
import json
import os
from pathlib import Path
# Add src to path so we can import pyhdwallet
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
import pyhdwallet
from bip_utils import Bip39SeedGenerator
def main():
print("Bootstrapping test vectors...")
vectors = {}
# 1. PGP Fingerprint Vector
recipient_path = Path(__file__).parent / "data" / "recipient.asc"
if not recipient_path.exists():
print(f"Error: {recipient_path} not found. Please create it first.")
sys.exit(1)
with open(recipient_path, "r", encoding="utf-8") as f:
armored = f.read()
# Calculate expected fingerprint using current code logic
expected_fpr = pyhdwallet.pgp_fingerprint(armored)
vectors["pgp"] = {
"recipient_file": "recipient.asc",
"expected_fingerprint": expected_fpr
}
print(f"PGP: Pinned fingerprint {expected_fpr}")
# 2. BIP39 & Derivation Vectors
mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
vectors["bip39"] = []
# Case A: No Passphrase
seed_bytes = Bip39SeedGenerator(mnemonic).Generate("")
seed_hex = seed_bytes.hex()
# Generate addresses for all supported chains/profiles
chains = ["ethereum", "bitcoin", "solana"]
# Fixed profile names to use underscores instead of hyphens
sol_profiles = ["phantom_bip44change", "phantom_bip44", "solana_bip39_first32"]
derived_data = {}
# Run derivation for each sol profile logic
for profile in sol_profiles:
res = pyhdwallet.derive_all(
seed_bytes,
chains,
count=5,
sol_profile=profile,
export_private=False
)
derived_data[profile] = res["addresses"]
vectors["bip39"].append({
"mnemonic": mnemonic,
"passphrase": "",
"expected_seed_hex": seed_hex,
"derived_addresses": derived_data
})
# Case B: With Passphrase (Regression test)
passphrase = "TREZOR"
seed_bytes_p = Bip39SeedGenerator(mnemonic).Generate(passphrase)
seed_hex_p = seed_bytes_p.hex()
res_p = pyhdwallet.derive_all(
seed_bytes_p,
chains,
count=1,
sol_profile="phantom_bip44change",
export_private=False
)
vectors["bip39"].append({
"mnemonic": mnemonic,
"passphrase": passphrase,
"expected_seed_hex": seed_hex_p,
"derived_addresses": {"phantom_bip44change": res_p["addresses"]}
})
print("Derivation: Generated vectors for empty and non-empty passphrases.")
# 3. Save
out_path = Path(__file__).parent / "vectors.json"
with open(out_path, "w", encoding="utf-8") as f:
json.dump(vectors, f, indent=2)
print(f"Success! Vectors written to {out_path}")
if __name__ == "__main__":
main()

52
tests/data/recipient.asc Normal file
View File

@@ -0,0 +1,52 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
gohx1u+yjx1L
=vol8
-----END PGP PUBLIC KEY BLOCK-----

91
tests/generate_bip85_vectors.py Executable file
View File

@@ -0,0 +1,91 @@
#!/usr/bin/env python3
"""
Generate BIP85 test vectors for vectors.json
Run this from the repo root:
python3 tests/generate_bip85_vectors.py
"""
import sys
from pathlib import Path
# Add src to path
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
import pyhdwallet
from bip_utils import Bip39SeedGenerator
import json
# Test cases to generate
test_cases = [
{
"description": "BIP85 test case 1: 12-word child, no passphrase",
"master_mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"master_passphrase": "",
"child_words": 12,
"index": 0,
},
{
"description": "BIP85 test case 2: 18-word child, no passphrase",
"master_mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"master_passphrase": "",
"child_words": 18,
"index": 0,
},
{
"description": "BIP85 test case 3: 24-word child, no passphrase",
"master_mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"master_passphrase": "",
"child_words": 24,
"index": 0,
},
{
"description": "BIP85 test case 4: 12-word child, WITH passphrase",
"master_mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"master_passphrase": "TREZOR",
"child_words": 12,
"index": 0,
},
]
print("Generating BIP85 test vectors...\n")
print("=" * 80)
vectors = []
for case in test_cases:
print(f"\n{case['description']}")
print("-" * 80)
# Generate master seed
master_seed = Bip39SeedGenerator(case["master_mnemonic"]).Generate(case["master_passphrase"])
# Derive child using BIP85
path, child_mnemonic, entropy64, truncated_entropy = pyhdwallet.bip85_derive_child_mnemonic(
master_seed,
case["child_words"],
case["index"]
)
vector = {
"description": case["description"],
"master_mnemonic": case["master_mnemonic"],
"master_passphrase": case["master_passphrase"],
"child_words": case["child_words"],
"index": case["index"],
"bip85_path": path,
"expected_entropy64_hex": entropy64.hex(),
"expected_entropy_truncated_hex": truncated_entropy.hex(),
"expected_child_mnemonic": child_mnemonic,
}
vectors.append(vector)
print(f"Path: {path}")
print(f"Entropy64: {entropy64.hex()}")
print(f"Truncated: {truncated_entropy.hex()}")
print(f"Child mnemonic: {child_mnemonic}")
print("\n" + "=" * 80)
print("\nJSON output for vectors.json:\n")
print(json.dumps(vectors, indent=2))

221
tests/test_vectors.py Normal file
View File

@@ -0,0 +1,221 @@
import sys
import os
import json
import pytest
import subprocess
from pathlib import Path
# Add src to path at the very top
TEST_DIR = Path(__file__).parent
SRC_DIR = TEST_DIR.parent / "src"
sys.path.insert(0, str(SRC_DIR))
import pyhdwallet
from bip_utils import Bip39SeedGenerator
DATA_DIR = TEST_DIR / "data"
VECTORS_FILE = TEST_DIR / "vectors.json"
@pytest.fixture
def vectors():
if not VECTORS_FILE.exists():
pytest.fail("tests/vectors.json missing. Run tests/bootstrap_vectors.py first.")
with open(VECTORS_FILE, "r") as f:
return json.load(f)
@pytest.fixture
def recipient_key_content():
path = DATA_DIR / "recipient.asc"
if not path.exists():
pytest.fail("tests/data/recipient.asc missing.")
with open(path, "r", encoding="utf-8") as f:
return f.read()
def test_pgp_fingerprint_calculation(vectors, recipient_key_content):
"""
Verifies that pgp_fingerprint computes the expected fingerprint
for the stored recipient key.
"""
expected = vectors["pgp"]["expected_fingerprint"]
actual = pyhdwallet.pgp_fingerprint(recipient_key_content)
assert actual == expected, "Fingerprint calculation logic has changed!"
def test_pgp_require_fingerprint_match(vectors):
"""
Verifies the safety check require_fingerprint_match enforces exact matches.
"""
expected = vectors["pgp"]["expected_fingerprint"]
# Should not raise
pyhdwallet.require_fingerprint_match(expected, expected, "test")
# Should raise on mismatch
wrong = expected.replace("A", "B") if "A" in expected else expected.replace("0", "1")
with pytest.raises(ValueError, match="test: PGP fingerprint mismatch"):
pyhdwallet.require_fingerprint_match(wrong, expected, "test")
def test_bip39_seed_derivation(vectors):
"""
Verifies that mnemonics convert to seeds exactly as they did at bootstrap time.
"""
for case in vectors["bip39"]:
mnemonic = case["mnemonic"]
passphrase = case["passphrase"]
expected_hex = case["expected_seed_hex"]
# Verify internal Bip39SeedGenerator usage matches expected hex
actual_bytes = Bip39SeedGenerator(mnemonic).Generate(passphrase)
assert actual_bytes.hex() == expected_hex
def test_address_derivation_integrity(vectors):
"""
Verifies derive_all produces the exact same addresses for supported chains.
"""
for case in vectors["bip39"]:
seed_hex = case["expected_seed_hex"]
seed_bytes = bytes.fromhex(seed_hex)
for profile, expected_addresses in case["derived_addresses"].items():
# Infer count from ethereum addresses in the expected data
# (Ethereum always generates `count` addresses, unlike Solana which depends on profile)
count = len(expected_addresses.get("ethereum", []))
result = pyhdwallet.derive_all(
seed_bytes,
chains=["ethereum", "bitcoin", "solana"],
count=count,
sol_profile=profile,
export_private=False
)
assert result["addresses"] == expected_addresses, f"Mismatch for profile {profile}"
def test_bip85_derivation(vectors):
"""
Verifies BIP85 child mnemonic derivation matches expected test vectors.
Tests:
- Path construction
- Entropy64 derivation (HMAC-SHA512)
- Entropy truncation
- Child mnemonic generation
"""
from bip_utils import Bip39SeedGenerator
for case in vectors["bip85"]:
master_mnemonic = case["master_mnemonic"]
master_passphrase = case["master_passphrase"]
child_words = case["child_words"]
index = case["index"]
# Generate master seed
master_seed = Bip39SeedGenerator(master_mnemonic).Generate(master_passphrase)
# Derive child using BIP85
path, child_mnemonic, entropy64, truncated_entropy = pyhdwallet.bip85_derive_child_mnemonic(
master_seed,
child_words,
index
)
# Assert path
assert path == case["bip85_path"], f"Path mismatch: {path} != {case['bip85_path']}"
# Assert entropy64 (full 64-byte HMAC output)
assert entropy64.hex() == case["expected_entropy64_hex"], \
f"Entropy64 mismatch for {case['description']}"
# Assert truncated entropy
assert truncated_entropy.hex() == case["expected_entropy_truncated_hex"], \
f"Truncated entropy mismatch for {case['description']}"
# Assert child mnemonic
assert child_mnemonic == case["expected_child_mnemonic"], \
f"Child mnemonic mismatch for {case['description']}"
def test_cli_gen_child_smoke(tmp_path, vectors):
"""
CLI smoke test for gen-child command.
Verifies:
- Command runs without error
- ZIP file is created
- Off-screen mode works
- File output works
"""
vector = vectors["bip85"][0] # Use first BIP85 test case
master_mnemonic = vector["master_mnemonic"]
cmd = [
sys.executable, "src/pyhdwallet.py", "gen-child",
"--mnemonic-stdin",
"--index", "0",
"--words", "12",
"--off-screen",
"--file",
"--zip-password-mode", "auto",
"--wallet-location", str(tmp_path)
]
result = subprocess.run(
cmd,
input=master_mnemonic.encode("utf-8"),
capture_output=True
)
assert result.returncode == 0, f"CLI gen-child failed: {result.stderr.decode()}"
# Check ZIP file created
zips = list(tmp_path.glob("*.zip"))
assert len(zips) == 1, f"Expected exactly one zip file, found {len(zips)}"
# Check filename pattern
zip_name = zips[0].name
assert zip_name.startswith("bip85_child_"), f"Unexpected zip name: {zip_name}"
def test_cli_recover_smoke(tmp_path, vectors):
"""
Runs the CLI in a subprocess to verify end-to-end wiring
without network (recover mode).
"""
vector = vectors["bip39"][0]
mnemonic = vector["mnemonic"]
expected_fp = vectors["pgp"]["expected_fingerprint"]
recipient_file = DATA_DIR / "recipient.asc"
# 1. Successful Recovery
cmd = [
sys.executable, "src/pyhdwallet.py", "recover",
"--mnemonic-stdin",
"--pgp-pubkey-file", str(recipient_file),
"--expected-fingerprint", expected_fp,
"--file",
"--wallet-location", str(tmp_path),
"--off-screen",
"--zip-password-mode", "auto"
]
result = subprocess.run(
cmd,
input=mnemonic.encode("utf-8"),
capture_output=True
)
assert result.returncode == 0, f"CLI failed: {result.stderr.decode()}"
zips = list(tmp_path.glob("*.zip"))
assert len(zips) == 1, "Expected exactly one zip file output"
# 2. Failed Recovery (Wrong Fingerprint)
wrong_fp = expected_fp.replace("A", "B")
cmd_fail = list(cmd)
cmd_fail[cmd_fail.index(expected_fp)] = wrong_fp
result_fail = subprocess.run(
cmd_fail,
input=mnemonic.encode("utf-8"),
capture_output=True
)
assert result_fail.returncode != 0
assert b"fingerprint mismatch" in result_fail.stderr or b"fingerprint mismatch" in result_fail.stdout

528
tests/vectors.json Normal file
View File

@@ -0,0 +1,528 @@
{
"pgp": {
"recipient_file": "recipient.asc",
"expected_fingerprint": "A27B96F2B169B5491013D2DA892B822C14A9AA18"
},
"bip39": [
{
"mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"passphrase": "",
"expected_seed_hex": "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4",
"derived_addresses": {
"phantom_bip44change": {
"ethereum": [
{
"index": 0,
"path": "m/44'/60'/0'/0/0",
"address": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94"
},
{
"index": 1,
"path": "m/44'/60'/0'/0/1",
"address": "0x6Fac4D18c912343BF86fa7049364Dd4E424Ab9C0"
},
{
"index": 2,
"path": "m/44'/60'/0'/0/2",
"address": "0xb6716976A3ebe8D39aCEB04372f22Ff8e6802D7A"
},
{
"index": 3,
"path": "m/44'/60'/0'/0/3",
"address": "0xF3f50213C1d2e255e4B2bAD430F8A38EEF8D718E"
},
{
"index": 4,
"path": "m/44'/60'/0'/0/4",
"address": "0x51cA8ff9f1C0a99f88E86B8112eA3237F55374cA"
}
],
"solana": [
{
"index": 0,
"path": "m/44'/501'/0'/0'",
"address": "HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk"
},
{
"index": 1,
"path": "m/44'/501'/1'/0'",
"address": "Hh8QwFUA6MtVu1qAoq12ucvFHNwCcVTV7hpWjeY1Hztb"
},
{
"index": 2,
"path": "m/44'/501'/2'/0'",
"address": "7WktogJEd2wQ9eH2oWusmcoFTgeYi6rS632UviTBJ2jm"
},
{
"index": 3,
"path": "m/44'/501'/3'/0'",
"address": "3YqEpfo3c818GhvbQ1UmVY1nJxw16vtu4JB9peJXT94k"
},
{
"index": 4,
"path": "m/44'/501'/4'/0'",
"address": "6nod592sTfEWD3VSVPdQndLMVBCNmMc6ngt7MyGBK21j"
}
],
"bitcoin": [
{
"index": 0,
"path": "m/84'/0'/0'/0/0",
"address_type": "native_segwit",
"address": "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"
},
{
"index": 0,
"path": "m/49'/0'/0'/0/0",
"address_type": "segwit",
"address": "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf"
},
{
"index": 0,
"path": "m/44'/0'/0'/0/0",
"address_type": "legacy",
"address": "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA"
},
{
"index": 1,
"path": "m/84'/0'/0'/0/1",
"address_type": "native_segwit",
"address": "bc1qnjg0jd8228aq7egyzacy8cys3knf9xvrerkf9g"
},
{
"index": 1,
"path": "m/49'/0'/0'/0/1",
"address_type": "segwit",
"address": "3LtMnn87fqUeHBUG414p9CWwnoV6E2pNKS"
},
{
"index": 1,
"path": "m/44'/0'/0'/0/1",
"address_type": "legacy",
"address": "1Ak8PffB2meyfYnbXZR9EGfLfFZVpzJvQP"
},
{
"index": 2,
"path": "m/84'/0'/0'/0/2",
"address_type": "native_segwit",
"address": "bc1qp59yckz4ae5c4efgw2s5wfyvrz0ala7rgvuz8z"
},
{
"index": 2,
"path": "m/49'/0'/0'/0/2",
"address_type": "segwit",
"address": "3B4cvWGR8X6Xs8nvTxVUoMJV77E4f7oaia"
},
{
"index": 2,
"path": "m/44'/0'/0'/0/2",
"address_type": "legacy",
"address": "1MNF5RSaabFwcbtJirJwKnDytsXXEsVsNb"
},
{
"index": 3,
"path": "m/84'/0'/0'/0/3",
"address_type": "native_segwit",
"address": "bc1qgl5vlg0zdl7yvprgxj9fevsc6q6x5dmcyk3cn3"
},
{
"index": 3,
"path": "m/49'/0'/0'/0/3",
"address_type": "segwit",
"address": "38CahkVftQneLonbWtfWxiiaT2fdnzsEAN"
},
{
"index": 3,
"path": "m/44'/0'/0'/0/3",
"address_type": "legacy",
"address": "1MVGa13XFvvpKGZdX389iU8b3qwtmAyrsJ"
},
{
"index": 4,
"path": "m/84'/0'/0'/0/4",
"address_type": "native_segwit",
"address": "bc1qm97vqzgj934vnaq9s53ynkyf9dgr05rargr04n"
},
{
"index": 4,
"path": "m/49'/0'/0'/0/4",
"address_type": "segwit",
"address": "37mbeJptxfQC6SNNLJ9a8efCY4BwBh5Kak"
},
{
"index": 4,
"path": "m/44'/0'/0'/0/4",
"address_type": "legacy",
"address": "1Gka4JdwhLxRwXaC6oLNH4YuEogeeSwqW7"
}
]
},
"phantom_bip44": {
"ethereum": [
{
"index": 0,
"path": "m/44'/60'/0'/0/0",
"address": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94"
},
{
"index": 1,
"path": "m/44'/60'/0'/0/1",
"address": "0x6Fac4D18c912343BF86fa7049364Dd4E424Ab9C0"
},
{
"index": 2,
"path": "m/44'/60'/0'/0/2",
"address": "0xb6716976A3ebe8D39aCEB04372f22Ff8e6802D7A"
},
{
"index": 3,
"path": "m/44'/60'/0'/0/3",
"address": "0xF3f50213C1d2e255e4B2bAD430F8A38EEF8D718E"
},
{
"index": 4,
"path": "m/44'/60'/0'/0/4",
"address": "0x51cA8ff9f1C0a99f88E86B8112eA3237F55374cA"
}
],
"solana": [
{
"index": 0,
"path": "m/44'/501'/0'",
"address": "GjJyeC1r2RgkuoCWMyPYkCWSGSGLcz266EaAkLA27AhL"
},
{
"index": 1,
"path": "m/44'/501'/1'",
"address": "ANf3TEKFL6jPWjzkndo4CbnNdUNkBk4KHPggJs2nu8Xi"
},
{
"index": 2,
"path": "m/44'/501'/2'",
"address": "Ag74i82rUZBTgMGLacCA1ZLnotvAca8CLscXcrG6Nwem"
},
{
"index": 3,
"path": "m/44'/501'/3'",
"address": "weCFpgyyyrzum6nA8XdmJXjDGDTXmG5P2DdgHv59hgQ"
},
{
"index": 4,
"path": "m/44'/501'/4'",
"address": "4w6V162fV7HJQNma7vZvxjunqmkie8hM2x1DqaFQqxqL"
}
],
"bitcoin": [
{
"index": 0,
"path": "m/84'/0'/0'/0/0",
"address_type": "native_segwit",
"address": "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"
},
{
"index": 0,
"path": "m/49'/0'/0'/0/0",
"address_type": "segwit",
"address": "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf"
},
{
"index": 0,
"path": "m/44'/0'/0'/0/0",
"address_type": "legacy",
"address": "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA"
},
{
"index": 1,
"path": "m/84'/0'/0'/0/1",
"address_type": "native_segwit",
"address": "bc1qnjg0jd8228aq7egyzacy8cys3knf9xvrerkf9g"
},
{
"index": 1,
"path": "m/49'/0'/0'/0/1",
"address_type": "segwit",
"address": "3LtMnn87fqUeHBUG414p9CWwnoV6E2pNKS"
},
{
"index": 1,
"path": "m/44'/0'/0'/0/1",
"address_type": "legacy",
"address": "1Ak8PffB2meyfYnbXZR9EGfLfFZVpzJvQP"
},
{
"index": 2,
"path": "m/84'/0'/0'/0/2",
"address_type": "native_segwit",
"address": "bc1qp59yckz4ae5c4efgw2s5wfyvrz0ala7rgvuz8z"
},
{
"index": 2,
"path": "m/49'/0'/0'/0/2",
"address_type": "segwit",
"address": "3B4cvWGR8X6Xs8nvTxVUoMJV77E4f7oaia"
},
{
"index": 2,
"path": "m/44'/0'/0'/0/2",
"address_type": "legacy",
"address": "1MNF5RSaabFwcbtJirJwKnDytsXXEsVsNb"
},
{
"index": 3,
"path": "m/84'/0'/0'/0/3",
"address_type": "native_segwit",
"address": "bc1qgl5vlg0zdl7yvprgxj9fevsc6q6x5dmcyk3cn3"
},
{
"index": 3,
"path": "m/49'/0'/0'/0/3",
"address_type": "segwit",
"address": "38CahkVftQneLonbWtfWxiiaT2fdnzsEAN"
},
{
"index": 3,
"path": "m/44'/0'/0'/0/3",
"address_type": "legacy",
"address": "1MVGa13XFvvpKGZdX389iU8b3qwtmAyrsJ"
},
{
"index": 4,
"path": "m/84'/0'/0'/0/4",
"address_type": "native_segwit",
"address": "bc1qm97vqzgj934vnaq9s53ynkyf9dgr05rargr04n"
},
{
"index": 4,
"path": "m/49'/0'/0'/0/4",
"address_type": "segwit",
"address": "37mbeJptxfQC6SNNLJ9a8efCY4BwBh5Kak"
},
{
"index": 4,
"path": "m/44'/0'/0'/0/4",
"address_type": "legacy",
"address": "1Gka4JdwhLxRwXaC6oLNH4YuEogeeSwqW7"
}
]
},
"solana_bip39_first32": {
"ethereum": [
{
"index": 0,
"path": "m/44'/60'/0'/0/0",
"address": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94"
},
{
"index": 1,
"path": "m/44'/60'/0'/0/1",
"address": "0x6Fac4D18c912343BF86fa7049364Dd4E424Ab9C0"
},
{
"index": 2,
"path": "m/44'/60'/0'/0/2",
"address": "0xb6716976A3ebe8D39aCEB04372f22Ff8e6802D7A"
},
{
"index": 3,
"path": "m/44'/60'/0'/0/3",
"address": "0xF3f50213C1d2e255e4B2bAD430F8A38EEF8D718E"
},
{
"index": 4,
"path": "m/44'/60'/0'/0/4",
"address": "0x51cA8ff9f1C0a99f88E86B8112eA3237F55374cA"
}
],
"solana": [
{
"index": 0,
"path": "BIP39 seed[0:32]",
"address": "EHqmfkN89RJ7Y33CXM6uCzhVeuywHoJXZZLszBHHZy7o"
}
],
"bitcoin": [
{
"index": 0,
"path": "m/84'/0'/0'/0/0",
"address_type": "native_segwit",
"address": "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"
},
{
"index": 0,
"path": "m/49'/0'/0'/0/0",
"address_type": "segwit",
"address": "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf"
},
{
"index": 0,
"path": "m/44'/0'/0'/0/0",
"address_type": "legacy",
"address": "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA"
},
{
"index": 1,
"path": "m/84'/0'/0'/0/1",
"address_type": "native_segwit",
"address": "bc1qnjg0jd8228aq7egyzacy8cys3knf9xvrerkf9g"
},
{
"index": 1,
"path": "m/49'/0'/0'/0/1",
"address_type": "segwit",
"address": "3LtMnn87fqUeHBUG414p9CWwnoV6E2pNKS"
},
{
"index": 1,
"path": "m/44'/0'/0'/0/1",
"address_type": "legacy",
"address": "1Ak8PffB2meyfYnbXZR9EGfLfFZVpzJvQP"
},
{
"index": 2,
"path": "m/84'/0'/0'/0/2",
"address_type": "native_segwit",
"address": "bc1qp59yckz4ae5c4efgw2s5wfyvrz0ala7rgvuz8z"
},
{
"index": 2,
"path": "m/49'/0'/0'/0/2",
"address_type": "segwit",
"address": "3B4cvWGR8X6Xs8nvTxVUoMJV77E4f7oaia"
},
{
"index": 2,
"path": "m/44'/0'/0'/0/2",
"address_type": "legacy",
"address": "1MNF5RSaabFwcbtJirJwKnDytsXXEsVsNb"
},
{
"index": 3,
"path": "m/84'/0'/0'/0/3",
"address_type": "native_segwit",
"address": "bc1qgl5vlg0zdl7yvprgxj9fevsc6q6x5dmcyk3cn3"
},
{
"index": 3,
"path": "m/49'/0'/0'/0/3",
"address_type": "segwit",
"address": "38CahkVftQneLonbWtfWxiiaT2fdnzsEAN"
},
{
"index": 3,
"path": "m/44'/0'/0'/0/3",
"address_type": "legacy",
"address": "1MVGa13XFvvpKGZdX389iU8b3qwtmAyrsJ"
},
{
"index": 4,
"path": "m/84'/0'/0'/0/4",
"address_type": "native_segwit",
"address": "bc1qm97vqzgj934vnaq9s53ynkyf9dgr05rargr04n"
},
{
"index": 4,
"path": "m/49'/0'/0'/0/4",
"address_type": "segwit",
"address": "37mbeJptxfQC6SNNLJ9a8efCY4BwBh5Kak"
},
{
"index": 4,
"path": "m/44'/0'/0'/0/4",
"address_type": "legacy",
"address": "1Gka4JdwhLxRwXaC6oLNH4YuEogeeSwqW7"
}
]
}
}
},
{
"mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"passphrase": "TREZOR",
"expected_seed_hex": "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04",
"derived_addresses": {
"phantom_bip44change": {
"ethereum": [
{
"index": 0,
"path": "m/44'/60'/0'/0/0",
"address": "0x9c32F71D4DB8Fb9e1A58B0a80dF79935e7256FA6"
}
],
"solana": [
{
"index": 0,
"path": "m/44'/501'/0'/0'",
"address": "7zSmbu6gKkb6HB7UDPtHYjwCWuBHU1D4TpNZFm4sndQe"
}
],
"bitcoin": [
{
"index": 0,
"path": "m/84'/0'/0'/0/0",
"address_type": "native_segwit",
"address": "bc1qv5rmq0kt9yz3pm36wvzct7p3x6mtgehjul0feu"
},
{
"index": 0,
"path": "m/49'/0'/0'/0/0",
"address_type": "segwit",
"address": "3Aho3kS7vgVWKTpRHjcqBoPXiCujiSuTaZ"
},
{
"index": 0,
"path": "m/44'/0'/0'/0/0",
"address_type": "legacy",
"address": "1PEha8dk5Me5J1rZWpgqSt5F4BroTBLS5y"
}
]
}
}
}
],
"bip85": [
{
"description": "BIP85 test case 1: 12-word child, no passphrase",
"master_mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"master_passphrase": "",
"child_words": 12,
"index": 0,
"bip85_path": "m/83696968'/39'/0'/12'/0'",
"expected_entropy64_hex": "ac98dac5d4f4ebad6056682ac95eb9ad9ba94fb68e96848264dad0b4357d002e41b3dd7a4c6f4ebc234be6938495840a73f59e9ba0e8e5c5208c94e6df2d7709",
"expected_entropy_truncated_hex": "ac98dac5d4f4ebad6056682ac95eb9ad",
"expected_child_mnemonic": "prosper short ramp prepare exchange stove life snack client enough purpose fold"
},
{
"description": "BIP85 test case 2: 18-word child, no passphrase",
"master_mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"master_passphrase": "",
"child_words": 18,
"index": 0,
"bip85_path": "m/83696968'/39'/0'/18'/0'",
"expected_entropy64_hex": "fc039f51d67ed7dfd01552f27de28887cf3e58655153e44b023d37578321f7083241970730e522d3f20b38a5296c5e51e57e0429546629704a09c6d1e2d10829",
"expected_entropy_truncated_hex": "fc039f51d67ed7dfd01552f27de28887cf3e58655153e44b",
"expected_child_mnemonic": "winter brother stamp provide uniform useful doctor prevent venue upper peasant auto view club next clerk tone fox"
},
{
"description": "BIP85 test case 3: 24-word child, no passphrase",
"master_mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"master_passphrase": "",
"child_words": 24,
"index": 0,
"bip85_path": "m/83696968'/39'/0'/24'/0'",
"expected_entropy64_hex": "d5a9cb46670566c4246b6e7af22e1dfc3668744ed831afea7ce2beea44e34e23e348e86091f24394f4be6253a7d5d24b91b1c4e0863b296e9e541e8018288897",
"expected_entropy_truncated_hex": "d5a9cb46670566c4246b6e7af22e1dfc3668744ed831afea7ce2beea44e34e23",
"expected_child_mnemonic": "stick exact spice sock filter ginger museum horse kit multiply manual wear grief demand derive alert quiz fault december lava picture immune decade jaguar"
},
{
"description": "BIP85 test case 4: 12-word child, WITH passphrase",
"master_mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"master_passphrase": "TREZOR",
"child_words": 12,
"index": 0,
"bip85_path": "m/83696968'/39'/0'/12'/0'",
"expected_entropy64_hex": "2b1d7c4f311137fa95f6302e64cdb88584d52b51b57d0430ee68e148b82baa3f8c40316397eb404f4573bc0c8e5c4bc14e4aa5f0f472a9d3587f494f1f7b3684",
"expected_entropy_truncated_hex": "2b1d7c4f311137fa95f6302e64cdb885",
"expected_child_mnemonic": "climb typical because giraffe beach wool fit ship common chapter hotel arm"
}
]
}

18
vendor/linux-x86_64/SHA256SUMS vendored Normal file
View File

@@ -0,0 +1,18 @@
11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2 base58-2.1.1-py3-none-any.whl
33792674bda552a071a539b6590b2986aa8c08d0c9c30c2566d7cb323173310d bip_utils-2.10.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
5a366c314df7217e3357bb8c7d2cda540b0bce180705f7a0ce2d1d9e28f62ad4 coincurve-21.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
1ef255bb24cb5116c7e6c83cf284db2146f0158b1fbe8c63ad7b1df214d141ba 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
c2695b1eac2f6bda962450def9f92639532630e2a8ba694bc56db3f9aa9454da ed25519_blake2b-1.4.1-cp312-cp312-linux_aarch64.whl
eb3aa8bcaf699be242e9c5e152cc5da37bc1dceb697454cd1b596cdb10f54f3c pgpy-0.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
26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130 pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl
6d097f465bfa47796b1494e12ea65d1478107d38e13bc56f6e58eedc4f6c1a87 pyzipper-0.3.6-py2.py3-none-any.whl
4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 six-1.17.0-py2.py3-none-any.whl

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
vendor/macos-arm64/PGPy-0.6.0.tar.gz vendored Normal file

Binary file not shown.

15
vendor/macos-arm64/SHA256SUMS vendored Normal file
View File

@@ -0,0 +1,15 @@
11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2 base58-2.1.1-py3-none-any.whl
33792674bda552a071a539b6590b2986aa8c08d0c9c30c2566d7cb323173310d bip_utils-2.10.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
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
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
c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465 pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl
6d097f465bfa47796b1494e12ea65d1478107d38e13bc56f6e58eedc4f6c1a87 pyzipper-0.3.6-py2.py3-none-any.whl
4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 six-1.17.0-py2.py3-none-any.whl

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
vendor/macos-arm64/crcmod-1.7.tar.gz vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.