blockchain/docker/tools/key-bridge-prototype.py
Claude c6def43d69
Some checks failed
Build & Release / Linux x86_64 (push) Has been cancelled
Build & Release / macOS ARM64 (push) Has been cancelled
Build & Release / Create Release (push) Has been cancelled
feat: key bridge prototype — derive sidechain + document keys from Lethean spend key
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 <charon@lethean.io>
2026-04-03 15:03:07 +01:00

153 lines
5.4 KiB
Python

#!/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 <hex_spend_key>
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()