Implements BIP85 child mnemonic derivation with full interoperability.
Features:
- Derives child BIP39 mnemonics (12/15/18/21/24 words) from master mnemonic
- BIP85 path: m/83696968'/39'/0'/{words}'/{index}'
- Supports optional master BIP39 passphrase
- Reuses existing input modes (--interactive, --mnemonic-stdin)
- Follows existing UX patterns (--off-screen, --file, PGP encryption)
- Offline-first with NetworkGuard protection
Testing:
- Adds deterministic regression tests for BIP85 spec compliance
- Verified against official BIP85 test vectors
- CLI smoke tests for end-to-end validation
Interoperability:
- Produces mnemonics compatible with Coldcard, Ian Coleman tool, etc.
- Test vector verified: 'girl mad pet galaxy egg matter matrix prison refuse sense ordinary nose'
Version bumped to v1.0.6
92 lines
2.7 KiB
Python
Executable File
92 lines
2.7 KiB
Python
Executable File
#!/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))
|