Compare commits
26 Commits
5c343c7944
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b47935de86 | |||
|
|
46f748301e | ||
|
|
27ee16f21a | ||
|
|
0a748dd8b0 | ||
|
|
c41928688c | ||
| d4f6e3d207 | |||
|
|
a6c84f81ee | ||
| 109829f1f5 | |||
|
|
2f7433b704 | ||
|
|
6457ec2cee | ||
|
|
0949fe9792 | ||
| 21b9389591 | |||
|
|
369c8595a1 | ||
|
|
2807982209 | ||
|
|
84953dbe5a | ||
|
|
6fd7cd4e79 | ||
|
|
5a52eb0954 | ||
|
|
28f01a613a | ||
| a9af9d33af | |||
| 129b09fcd9 | |||
|
|
d02e1d872e | ||
|
|
875fa17d6c | ||
|
|
ccd070dc56 | ||
|
|
ce26b3560a | ||
| 4cf32f9ba0 | |||
| 94fcb993db |
82
.gitignore
vendored
82
.gitignore
vendored
@@ -1,25 +1,79 @@
|
||||
.venv/
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.pyc
|
||||
.vscode/
|
||||
.env
|
||||
.DS_Store
|
||||
.idea/
|
||||
logs/
|
||||
*.log
|
||||
coverage/
|
||||
_toDelete/
|
||||
_toDelete/
|
||||
dist/
|
||||
build/
|
||||
*.so
|
||||
.Python
|
||||
*.egg-info/
|
||||
.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/
|
||||
.cache/
|
||||
coverage/
|
||||
|
||||
# Type checking
|
||||
.mypy_cache/
|
||||
.pyre/
|
||||
.pytype/
|
||||
.cache/
|
||||
|
||||
# IDEs
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.envrc
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# Database
|
||||
*.sqlite3
|
||||
*.db
|
||||
|
||||
# Project specific
|
||||
.wallet/
|
||||
releases/*/
|
||||
_toDelete/
|
||||
.potentialfix.md
|
||||
|
||||
# PGP keys (except test data)
|
||||
*.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
18
Dockerfile
Normal 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
334
Makefile
Normal 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
283
README.md
@@ -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.
|
||||
2. Clone/download the repo.
|
||||
3. Create a virtual environment: `python -m venv .venv && source .venv/bin/activate`
|
||||
4. Install dependencies: `pip install -r requirements.txt`
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://github.com/<your-username>/hdwalletpy.git
|
||||
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`
|
||||
- Recover from mnemonic: `python ./src/pyhdwallet.py recover --mnemonic "your words" --chains bitcoin`
|
||||
- Fetch PGP key: `python ./src/pyhdwallet.py fetchkey "https://example.com/key.asc"`
|
||||
- Run tests: `python ./src/pyhdwallet.py test`
|
||||
- Creates Python 3.12 virtual environment
|
||||
- Installs from vendored wheels (offline-capable)
|
||||
- Verifies installation with test suite
|
||||
- Leaves you in activated venv
|
||||
|
||||
For detailed examples and security tips, see `playbook.md`.
|
||||
---
|
||||
|
||||
## Security
|
||||
### **Air-Gapped Installation (No Internet)**
|
||||
|
||||
- Operates offline by default.
|
||||
- Use `--secure-mode` for high-security operations.
|
||||
- Always verify PGP keys and run on trusted systems.
|
||||
This repository includes pre-built Python wheels for offline use.
|
||||
|
||||
**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
115
build-all-platforms.sh
Executable 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
76
build_binary.sh
Executable 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
70
build_binary_linux.sh
Executable 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
117
install_offline.sh
Executable 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 ""
|
||||
968
playbook.md
968
playbook.md
File diff suppressed because it is too large
Load Diff
7
pytest.ini
Normal file
7
pytest.ini
Normal 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
2
requirements-dev.in
Normal file
@@ -0,0 +1,2 @@
|
||||
-r requirements.txt # Include production deps
|
||||
pytest>=9.0.0
|
||||
81
requirements-dev.txt
Normal file
81
requirements-dev.txt
Normal 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
|
||||
@@ -1,2 +1,8 @@
|
||||
PGPy
|
||||
bip-utils
|
||||
# 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
|
||||
|
||||
@@ -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:
|
||||
#
|
||||
# pip-compile
|
||||
# pip-compile requirements.in
|
||||
#
|
||||
base58==2.1.1
|
||||
# via -r requirements.in
|
||||
bip-utils==2.10.0
|
||||
# via -r requirements.in
|
||||
cbor2==5.8.0
|
||||
@@ -32,7 +34,13 @@ pycparser==2.23
|
||||
# via cffi
|
||||
pycryptodome==3.23.0
|
||||
# via bip-utils
|
||||
pycryptodomex==3.23.0
|
||||
# via pyzipper
|
||||
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
|
||||
# via ecdsa
|
||||
|
||||
1232
src/pyhdwallet.py
1232
src/pyhdwallet.py
File diff suppressed because it is too large
Load Diff
@@ -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
104
tests/bootstrap_vectors.py
Normal 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
52
tests/data/recipient.asc
Normal file
@@ -0,0 +1,52 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBGE3cD4BEADRf2MGzsF6lV/vtEZLSz+kMs7LWHfxycrwhK1rlqAEblMJdDrs
|
||||
odQ/Ep8y03TQ6iQfOaZHfthofvvEfYpNMEPRorvGDoW400haAooAxS4ieASy+YtB
|
||||
lUDQIF3HIk3cbTFDjsXsJhKDNhHxPmCnngwXz6l8xdz6JJr7QjUuprfoWztPwzQ2
|
||||
5M77jYi3M5K7e35b5dbvWq7hCsVoeYyj9BO+OVC48F/qfb91Ht9EgbBVpJK6Y9tN
|
||||
OIceRPoYw08WUCKM6hNRb8Nf0N+PY9edboAAEUhjkaCj64cGAzGhFBC4VCq3Dtc0
|
||||
GGQwlIGrooRhsghKlNKkCd2en3R+icMsbPdEdd3LpNF+iHAM/btZ30Wdw+woQQQR
|
||||
aQFf446Dshmu4d4pKTsHypw2aTmoqKzZ0ow/fKTq1MaASTezU3eGAQa7fr3n1xrY
|
||||
plWJISOcguJx/NO+XlW8B4AMW11xeeWoCvpxeGWuGk1SFa8RI7ExD4WKN+MCGkSE
|
||||
UZT0fQfSkQjr3fIw2q2WVjgkFN4Vu20e9ea4ZcSaeZvF6auj26Ypfa6JG+usOxDR
|
||||
fjNQ5II/FKrlZl/Ry9gDui6uvWbLQjEinA3hfDCzk33uIWjg3PU73bdh87vDhqFa
|
||||
NkF49Go6+J7AaeOAp6FgMwxLAh5HrvxNSsDhcApv82XhEo3EGX+M5vpnqQARAQAB
|
||||
tB1LY2MgTGVvIEMgPGtjY2xlb2NAZ21haWwuY29tPokCWAQTAQgAQhYhBKJ7lvKx
|
||||
abVJEBPS2okrgiwUqaoYBQJhN3A+AhsDBQkSzAMABQsJCAcCAyICAQYVCgkICwIE
|
||||
FgIDAQIeBwIXgAAKCRCJK4IsFKmqGK4ND/9ID2dnOBrobB8Y3hzwwVT1yvztODjE
|
||||
n3jA3F78cZhDaBJvcNFcmDHFyZSLsR4gtkTREuRtjF4bb/bN0lQsiwgTSSJ04bZm
|
||||
0Its04EQuZQw9hl/XxQEka+7GOi1jcUq/9n7A2DS1RkQ9kElJlR4MyvVUwohMmNA
|
||||
y1vkKMQrME2PGeqQ0mp3tlIpBBohEQzwB0d+dxjVyjpfiDVAMjD5OBf/tI1sND/R
|
||||
7fIDYTxOh8pDRWH4kJDr8LnDehGXPKab9IjK15DXzTS9FQ27QI654pZDlhZmj0GG
|
||||
fZvKb8EhjB0FeKiP+RnaTFI+fLd8iIi7aoTSiPqst1F7kDNi9xaqq08NA0tZwvRt
|
||||
GHKcdmZ+GF9Nui7j6X30oGPpUAccH033px4iVQB78qS5ZGAyIerW54gumGLxqQh7
|
||||
5ozIRxjjfMdD+R9ANa5hwQnv1nXlS0wOgzok0b86LV7Lid7NZ/1RO3lIt2CDFbZS
|
||||
OsmMy5XCeup4KT+lJyGqOCVznIzUDUA2ZQoX2cBxwPt60PFIxnCGnf8DAZAjY/WI
|
||||
6w+c4r+tnkWezDZ6T8P6+Vg+EgLg3ZJ1ZY4S7wFGCIQYxdZfBeIy1RmsHdA/jzOd
|
||||
zMsj0u+BJwbrvj8It+10n0mh/VaIhsK0/Mowf78/NvRziMZ3C9Y5g4YZxwK1f2x8
|
||||
B2tKo0QmtpTz9bkCDQRhN3A+ARAAtQRA2DXVIQBodQveLdng83EjsWEKBRZLeqAm
|
||||
KXjSe8LxsgjL36cttXvlA0UuD/dTa83KYrROhNzuSlGGQ1eDKl+VAgBI3Qbw3fTs
|
||||
ahK4vHX6cc3V4esPaUZU7P/xBzmhro/hHrF5vLMv7Sa4iNTJgdgez6uB9hccL0BD
|
||||
Ro8nO8qzZQJJnLFWy9Lk5jsiLxij7vvRFCTsUpHCeNIJ+/EHvn5oXXb775WYzTs0
|
||||
zRT2cZEJ1z8Ji+TGQUoukg2KBBwPMUI7ilscf5IbIJmXuWkyHBJa/PqrKuYuM8DC
|
||||
Iur+CdlInw4yet/i3y0t8wwrhaigz1kHURSExO1wasEmqlkCDcQsYfjF0Edyf22H
|
||||
CY/9vdX51Hf3p9KOi8lWclKYzWHMbX3PNEH5L3WRV6MxEFt8zCaue/3htAkn5Bts
|
||||
+AJ4a6bdBrO7zj8ZR9Xx/PsDRcH+vJL/mbiYsvyO2UJAoeS4Vf6/pG040sEfSNUB
|
||||
+FtbXgqqA9tjORKLekxKA4ZG1FIhLjL4JRJ8FrqMDmh2Cj7f0t4dI3Ubm8UymhHH
|
||||
t2771PE1vov0gKdtpyqWx/5MErdggiglI2lebBeefMB+NQSJJM7h3RrGpNTezmnx
|
||||
QBcGIgTrHVrXEaXUSV2/QdJwJicuTxROAf4wLhEf1OxPEzpgqvPjh7aAL+9+cxom
|
||||
scY+S4MAEQEAAYkCPAQYAQgAJhYhBKJ7lvKxabVJEBPS2okrgiwUqaoYBQJhN3A+
|
||||
AhsMBQkSzAMAAAoJEIkrgiwUqaoYaVcP/i0PF32Ri5WSy0CMN4nLgsEt1kOwpfQj
|
||||
8VaJl6YIxyHluCa2yXpc3GP4XuvP/GKO6ODRhlBWOQn532jDpFdkJnef/z0L9hct
|
||||
55/qxXc+7X65cw6T6ZfEJVCx4jt9mhm5g+1XOnVG+E/7DL97+AnKUYVepi2z6jfv
|
||||
Ql6HosEjXMRWwF8wF+5fwieqMA8yldkdzHcSb3fiWYycQUGPyXdJGht0mXrmfMh+
|
||||
4EUpHpu75ott+vj/yVFvDKWu1bhznA7Ma9HASV1QWmIvywNYUGcDcXEPVoYjTmBK
|
||||
Dz6S5sWWeTjJo+GPOfgF8BLK3xkZy3vULsAUq5xu9ryyvY2MdZeMN3RrJ7aRmFhh
|
||||
HZU+MK17LNWF3IqqJPJdJvV94mKouqHTyV7BGMs2y1eTC7mZBvmcZpm2ECqmg8RQ
|
||||
WXFospGZmcXYQVsgNIGy3nonzgF2EaUEqYKfl1P4zQvMOio79svp/0TuJVi9I/AW
|
||||
p+HbSJ6r75vxpBnk18N00wy5h3dhuc3MfiEnlXKbbr8Zstb9tJLy8Bm0KihDUyQX
|
||||
qFGnHPzRO36fh4eUkTlShzMrx9+bzkVTsjzK3dVviPnnNykfR63DJSuEOxT5kVPG
|
||||
geKpo+1BeSAXduK2SpEVvNBYlZFNki6sbuCvcGd49FTq0ZKFvUWtNTTvGSugeOSb
|
||||
gohx1u+yjx1L
|
||||
=vol8
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
91
tests/generate_bip85_vectors.py
Executable file
91
tests/generate_bip85_vectors.py
Executable 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
221
tests/test_vectors.py
Normal 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
528
tests/vectors.json
Normal 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
18
vendor/linux-x86_64/SHA256SUMS
vendored
Normal 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
|
||||
BIN
vendor/linux-x86_64/base58-2.1.1-py3-none-any.whl
vendored
Normal file
BIN
vendor/linux-x86_64/base58-2.1.1-py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/linux-x86_64/bip_utils-2.10.0-py3-none-any.whl
vendored
Normal file
BIN
vendor/linux-x86_64/bip_utils-2.10.0-py3-none-any.whl
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
vendor/linux-x86_64/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
vendored
Normal file
BIN
vendor/linux-x86_64/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/linux-x86_64/coincurve-21.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
vendored
Normal file
BIN
vendor/linux-x86_64/coincurve-21.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/linux-x86_64/crcmod-1.7-cp312-cp312-linux_aarch64.whl
vendored
Normal file
BIN
vendor/linux-x86_64/crcmod-1.7-cp312-cp312-linux_aarch64.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/linux-x86_64/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl
vendored
Normal file
BIN
vendor/linux-x86_64/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/linux-x86_64/ecdsa-0.19.1-py2.py3-none-any.whl
vendored
Normal file
BIN
vendor/linux-x86_64/ecdsa-0.19.1-py2.py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/linux-x86_64/ed25519_blake2b-1.4.1-cp312-cp312-linux_aarch64.whl
vendored
Normal file
BIN
vendor/linux-x86_64/ed25519_blake2b-1.4.1-cp312-cp312-linux_aarch64.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/linux-x86_64/pgpy-0.6.0-py3-none-any.whl
vendored
Normal file
BIN
vendor/linux-x86_64/pgpy-0.6.0-py3-none-any.whl
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
vendor/linux-x86_64/pyasn1-0.6.1-py3-none-any.whl
vendored
Normal file
BIN
vendor/linux-x86_64/pyasn1-0.6.1-py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/linux-x86_64/pycparser-2.23-py3-none-any.whl
vendored
Normal file
BIN
vendor/linux-x86_64/pycparser-2.23-py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/linux-x86_64/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
vendored
Normal file
BIN
vendor/linux-x86_64/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/linux-x86_64/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
vendored
Normal file
BIN
vendor/linux-x86_64/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/linux-x86_64/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl
vendored
Normal file
BIN
vendor/linux-x86_64/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/linux-x86_64/pyzipper-0.3.6-py2.py3-none-any.whl
vendored
Normal file
BIN
vendor/linux-x86_64/pyzipper-0.3.6-py2.py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/linux-x86_64/six-1.17.0-py2.py3-none-any.whl
vendored
Normal file
BIN
vendor/linux-x86_64/six-1.17.0-py2.py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/macos-arm64/PGPy-0.6.0.tar.gz
vendored
Normal file
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
15
vendor/macos-arm64/SHA256SUMS
vendored
Normal 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
|
||||
BIN
vendor/macos-arm64/base58-2.1.1-py3-none-any.whl
vendored
Normal file
BIN
vendor/macos-arm64/base58-2.1.1-py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/macos-arm64/bip_utils-2.10.0-py3-none-any.whl
vendored
Normal file
BIN
vendor/macos-arm64/bip_utils-2.10.0-py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/macos-arm64/cbor2-5.8.0-cp312-cp312-macosx_11_0_arm64.whl
vendored
Normal file
BIN
vendor/macos-arm64/cbor2-5.8.0-cp312-cp312-macosx_11_0_arm64.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/macos-arm64/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl
vendored
Normal file
BIN
vendor/macos-arm64/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/macos-arm64/coincurve-21.0.0-cp312-cp312-macosx_11_0_arm64.whl
vendored
Normal file
BIN
vendor/macos-arm64/coincurve-21.0.0-cp312-cp312-macosx_11_0_arm64.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/macos-arm64/crcmod-1.7.tar.gz
vendored
Normal file
BIN
vendor/macos-arm64/crcmod-1.7.tar.gz
vendored
Normal file
Binary file not shown.
BIN
vendor/macos-arm64/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl
vendored
Normal file
BIN
vendor/macos-arm64/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/macos-arm64/ecdsa-0.19.1-py2.py3-none-any.whl
vendored
Normal file
BIN
vendor/macos-arm64/ecdsa-0.19.1-py2.py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/macos-arm64/ed25519-blake2b-1.4.1.tar.gz
vendored
Normal file
BIN
vendor/macos-arm64/ed25519-blake2b-1.4.1.tar.gz
vendored
Normal file
Binary file not shown.
BIN
vendor/macos-arm64/py_sr25519_bindings-0.2.3-cp312-cp312-macosx_11_0_arm64.whl
vendored
Normal file
BIN
vendor/macos-arm64/py_sr25519_bindings-0.2.3-cp312-cp312-macosx_11_0_arm64.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/macos-arm64/pyasn1-0.6.1-py3-none-any.whl
vendored
Normal file
BIN
vendor/macos-arm64/pyasn1-0.6.1-py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/macos-arm64/pycparser-2.23-py3-none-any.whl
vendored
Normal file
BIN
vendor/macos-arm64/pycparser-2.23-py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/macos-arm64/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl
vendored
Normal file
BIN
vendor/macos-arm64/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/macos-arm64/pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_universal2.whl
vendored
Normal file
BIN
vendor/macos-arm64/pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_universal2.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/macos-arm64/pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl
vendored
Normal file
BIN
vendor/macos-arm64/pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/macos-arm64/pyzipper-0.3.6-py2.py3-none-any.whl
vendored
Normal file
BIN
vendor/macos-arm64/pyzipper-0.3.6-py2.py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
vendor/macos-arm64/six-1.17.0-py2.py3-none-any.whl
vendored
Normal file
BIN
vendor/macos-arm64/six-1.17.0-py2.py3-none-any.whl
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user