From c6def43d69d83a88c5541b89b5a2a5ff86450ccb Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 15:03:07 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20key=20bridge=20prototype=20=E2=80=94=20?= =?UTF-8?q?derive=20sidechain=20+=20document=20keys=20from=20Lethean=20spe?= =?UTF-8?q?nd=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One Lethean seed phrase derives keys for: - Main chain (ed25519, native) - HNS sidechain (secp256k1, derived via HKDF) - Document signing (ed25519, for hash timestamping) - Audit trail (ed25519, for revision verification) Domain-separated HKDF ensures keys can't be reversed or collide. Foundation for Web3 identity bridge and corporate document control. Co-Authored-By: Charon --- docker/tools/key-bridge-prototype.py | 153 +++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 docker/tools/key-bridge-prototype.py diff --git a/docker/tools/key-bridge-prototype.py b/docker/tools/key-bridge-prototype.py new file mode 100644 index 00000000..fdb89922 --- /dev/null +++ b/docker/tools/key-bridge-prototype.py @@ -0,0 +1,153 @@ +#!/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()