Files
seedPass/src/nostr/key_manager.py
2025-08-08 15:47:52 -04:00

157 lines
5.0 KiB
Python

# nostr/key_manager.py
import hashlib
import logging
from bech32 import bech32_encode, convertbits
from local_bip85.bip85 import BIP85
from bip_utils import Bip39SeedGenerator
from .coincurve_keys import Keys
# BIP-85 application numbers for Nostr key derivation
NOSTR_KEY_APP_ID = 1237
LEGACY_NOSTR_KEY_APP_ID = 0
logger = logging.getLogger(__name__)
class KeyManager:
"""
Manages key generation, encoding, and derivation for NostrClient.
"""
def __init__(self, parent_seed: str, fingerprint: str):
"""
Initializes the KeyManager with the provided parent_seed and fingerprint.
Parameters:
parent_seed (str): The parent seed used for key derivation.
fingerprint (str): The fingerprint to differentiate key derivations.
"""
try:
if not isinstance(parent_seed, str):
raise TypeError(
f"Parent seed must be a string, got {type(parent_seed)}"
)
if not isinstance(fingerprint, str):
raise TypeError(
f"Fingerprint must be a string, got {type(fingerprint)}"
)
self.parent_seed = parent_seed
self.fingerprint = fingerprint
logger.debug(f"KeyManager initialized with parent_seed and fingerprint.")
# Initialize BIP85
self.bip85 = self.initialize_bip85()
# Generate Nostr keys using the fingerprint
self.keys = self.generate_nostr_keys()
logger.debug("Nostr Keys initialized successfully.")
except Exception as e:
logger.error(f"Key initialization failed: {e}", exc_info=True)
raise
def initialize_bip85(self):
"""
Initializes BIP85 with the parent seed.
Returns:
BIP85: An instance of the BIP85 class.
"""
try:
seed_bytes = Bip39SeedGenerator(self.parent_seed).Generate()
bip85 = BIP85(seed_bytes)
logger.debug("BIP85 initialized successfully.")
return bip85
except Exception as e:
logger.error(f"Failed to initialize BIP85: {e}", exc_info=True)
raise
def generate_nostr_keys(self) -> Keys:
"""
Derives a unique Nostr key pair for the given fingerprint using BIP-85.
Returns:
Keys: An instance of Keys containing the Nostr key pair.
"""
try:
# Convert fingerprint to an integer index (using a hash function)
index = int(hashlib.sha256(self.fingerprint.encode()).hexdigest(), 16) % (
2**31
)
# Derive entropy for Nostr key (32 bytes)
entropy_bytes = self.bip85.derive_entropy(
index=index,
bytes_len=32,
app_no=NOSTR_KEY_APP_ID,
)
# Generate Nostr key pair from entropy
private_key_hex = entropy_bytes.hex()
keys = Keys(priv_k=private_key_hex)
logger.debug(f"Nostr keys generated for fingerprint {self.fingerprint}.")
return keys
except Exception as e:
logger.error(f"Failed to generate Nostr keys: {e}", exc_info=True)
raise
def generate_legacy_nostr_keys(self) -> Keys:
"""Derive Nostr keys using the legacy application ID."""
try:
entropy = self.bip85.derive_entropy(
index=0, bytes_len=32, app_no=LEGACY_NOSTR_KEY_APP_ID
)
return Keys(priv_k=entropy.hex())
except Exception as e:
logger.error(f"Failed to generate legacy Nostr keys: {e}", exc_info=True)
raise
def get_public_key_hex(self) -> str:
"""
Returns the public key in hexadecimal format.
Returns:
str: The public key in hex.
"""
return self.keys.public_key_hex()
def get_private_key_hex(self) -> str:
"""
Returns the private key in hexadecimal format.
Returns:
str: The private key in hex.
"""
return self.keys.private_key_hex()
def get_npub(self) -> str:
"""
Returns the npub (Bech32 encoded public key).
Returns:
str: The npub string.
"""
try:
pub_key_hex = self.get_public_key_hex()
pub_key_bytes = bytes.fromhex(pub_key_hex)
data = convertbits(pub_key_bytes, 8, 5, True)
npub = bech32_encode("npub", data)
return npub
except Exception as e:
logger.error(f"Failed to generate npub: {e}", exc_info=True)
raise
def get_nsec(self) -> str:
"""Return the nsec (Bech32 encoded private key)."""
try:
priv_hex = self.get_private_key_hex()
priv_bytes = bytes.fromhex(priv_hex)
data = convertbits(priv_bytes, 8, 5, True)
return bech32_encode("nsec", data)
except Exception as e:
logger.error(f"Failed to generate nsec: {e}", exc_info=True)
raise