#!/usr/bin/env python3 """ Lethean Key Bridge Prototype Derives an HNS sidechain secp256k1 keypair from a Lethean ed25519 spend key. One seed phrase → both chains. Lethean seed phrase ↓ ed25519 spend key (32 bytes) ├── Main chain wallet (native CryptoNote) └── HKDF("lethean-hns-bridge", spend_key) ↓ secp256k1 private key (32 bytes) └── Sidechain wallet (derived) Usage: python3 key-bridge-prototype.py python3 key-bridge-prototype.py --from-wallet http://127.0.0.1:46944/json_rpc Dependencies: pip install cryptography secp256k1 (or use hashlib for prototype) """ import hashlib import hmac import sys import json import urllib.request def derive_sidechain_key(spend_key_hex: str) -> dict: """ Derive a secp256k1 private key from a Lethean ed25519 spend key. Uses HKDF-like construction: prk = HMAC-SHA256(key="lethean-hns-bridge", msg=spend_key) secp256k1_key = HMAC-SHA256(key=prk, msg="hns-sidechain-v1" || 0x01) The domain separation ensures: - Different derivation paths produce different keys - The ed25519 key cannot be recovered from the secp256k1 key - Future derivation paths (v2, v3) won't collide """ spend_key = bytes.fromhex(spend_key_hex) assert len(spend_key) == 32, f"Spend key must be 32 bytes, got {len(spend_key)}" # Step 1: Extract — domain-separated PRK salt = b"lethean-hns-bridge" prk = hmac.new(salt, spend_key, hashlib.sha256).digest() # Step 2: Expand — derive the secp256k1 key info = b"hns-sidechain-v1\x01" secp256k1_privkey = hmac.new(prk, info, hashlib.sha256).digest() # secp256k1 requires the key to be in range [1, n-1] # n = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 key_int = int.from_bytes(secp256k1_privkey, 'big') if key_int == 0 or key_int >= n: # Extremely unlikely but handle it — rehash secp256k1_privkey = hmac.new(prk, b"hns-sidechain-v1\x02", hashlib.sha256).digest() key_int = int.from_bytes(secp256k1_privkey, 'big') % (n - 1) + 1 secp256k1_privkey = key_int.to_bytes(32, 'big') # Derive additional keys for future use document_signing_key = hmac.new(prk, b"document-signing-v1\x01", hashlib.sha256).digest() audit_key = hmac.new(prk, b"audit-trail-v1\x01", hashlib.sha256).digest() return { "source": { "type": "ed25519", "chain": "lethean-mainchain", "spend_key": spend_key_hex, }, "derived": { "hns_sidechain": { "type": "secp256k1", "chain": "lethean-hns-sidechain", "private_key": secp256k1_privkey.hex(), "derivation": "HKDF(salt=lethean-hns-bridge, ikm=spend_key, info=hns-sidechain-v1)", }, "document_signing": { "type": "ed25519", "purpose": "document hash timestamping", "private_key": document_signing_key.hex(), "derivation": "HKDF(salt=lethean-hns-bridge, ikm=spend_key, info=document-signing-v1)", }, "audit": { "type": "ed25519", "purpose": "audit trail verification", "private_key": audit_key.hex(), "derivation": "HKDF(salt=lethean-hns-bridge, ikm=spend_key, info=audit-trail-v1)", }, }, "derivation_version": "1.0", "note": "All keys derived deterministically from the Lethean spend key. One seed, all chains and purposes.", } def get_spend_key_from_wallet(rpc_url: str) -> str: """Fetch the spend key from a running wallet RPC.""" payload = json.dumps({ "jsonrpc": "2.0", "id": "0", "method": "get_wallet_info", "params": {} }).encode() req = urllib.request.Request( rpc_url, data=payload, headers={"Content-Type": "application/json"} ) try: with urllib.request.urlopen(req, timeout=5) as resp: data = json.loads(resp.read()) # The spend key isn't directly in get_wallet_info # We'd need a custom RPC or the seed to derive it # For prototype, use the address as a stand-in print("Note: Full implementation needs 'get_spend_key' RPC or seed phrase input") print(f"Wallet address: {data.get('result', {}).get('wi', {}).get('address', '?')}") return None except Exception as e: print(f"Error connecting to wallet: {e}") return None def main(): if len(sys.argv) < 2: print(__doc__) print("\nExample with test key:") test_key = "a" * 64 # 32 bytes of 0xAA result = derive_sidechain_key(test_key) print(json.dumps(result, indent=2)) return if sys.argv[1] == "--from-wallet": rpc_url = sys.argv[2] if len(sys.argv) > 2 else "http://127.0.0.1:46944/json_rpc" spend_key = get_spend_key_from_wallet(rpc_url) if not spend_key: print("\nUsing test key for demonstration:") spend_key = "7b9f1e2a3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7" else: spend_key = sys.argv[1] result = derive_sidechain_key(spend_key) print(json.dumps(result, indent=2)) if __name__ == "__main__": main()