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