1 Commits

3 changed files with 37 additions and 19 deletions
+5 -2
View File
@@ -422,8 +422,11 @@ def _load_or_create_identity_key(path: Optional[str] = None) -> bytes:
except Exception as e: except Exception as e:
logger.warning(f"Failed to load identity key: {e}") logger.warning(f"Failed to load identity key: {e}")
# Generate new random key # Generate new key via MeshCore-compatible keygen; store 32-byte private scalar.
key = os.urandom(32) from repeater.keygen import generate_meshcore_keypair
_, private_key = generate_meshcore_keypair()
key = private_key[:32]
# Save it # Save it
try: try:
+27 -14
View File
@@ -14,6 +14,11 @@ from typing import Optional, Tuple
from nacl.bindings import crypto_scalarmult_ed25519_base_noclamp from nacl.bindings import crypto_scalarmult_ed25519_base_noclamp
def _has_disallowed_public_prefix(public_key: bytes) -> bool:
"""Return True when public key starts with a forbidden prefix byte."""
return bool(public_key) and public_key[0] in (0x00, 0xFF)
def generate_meshcore_keypair() -> Tuple[bytes, bytes]: def generate_meshcore_keypair() -> Tuple[bytes, bytes]:
"""Generate a MeshCore-compatible Ed25519 keypair. """Generate a MeshCore-compatible Ed25519 keypair.
@@ -21,25 +26,30 @@ def generate_meshcore_keypair() -> Tuple[bytes, bytes]:
(public_key, private_key) as raw bytes. (public_key, private_key) as raw bytes.
public_key is 32 bytes, private_key is 64 bytes. public_key is 32 bytes, private_key is 64 bytes.
""" """
# 1. Random 32-byte seed while True:
seed = secrets.token_bytes(32) # 1. Random 32-byte seed
seed = secrets.token_bytes(32)
# 2. SHA-512 hash # 2. SHA-512 hash
digest = hashlib.sha512(seed).digest() digest = hashlib.sha512(seed).digest()
# 3. Ed25519 scalar clamping on first 32 bytes # 3. Ed25519 scalar clamping on first 32 bytes
clamped = bytearray(digest[:32]) clamped = bytearray(digest[:32])
clamped[0] &= 248 # Clear bottom 3 bits clamped[0] &= 248 # Clear bottom 3 bits
clamped[31] &= 63 # Clear top 2 bits clamped[31] &= 63 # Clear top 2 bits
clamped[31] |= 64 # Set bit 6 clamped[31] |= 64 # Set bit 6
# 4. Derive public key # 4. Derive public key
public_key = crypto_scalarmult_ed25519_base_noclamp(bytes(clamped)) public_key = crypto_scalarmult_ed25519_base_noclamp(bytes(clamped))
# 5. Private key = [clamped_scalar][sha512_upper_half] # Disallow keys with 0x00 / 0xFF leading byte.
private_key = bytes(clamped) + digest[32:64] if _has_disallowed_public_prefix(public_key):
continue
return public_key, private_key # 5. Private key = [clamped_scalar][sha512_upper_half]
private_key = bytes(clamped) + digest[32:64]
return public_key, private_key
def generate_vanity_key( def generate_vanity_key(
@@ -57,6 +67,9 @@ def generate_vanity_key(
""" """
target = prefix.upper() target = prefix.upper()
if target.startswith("00") or target.startswith("FF"):
return None
for attempt in range(1, max_iterations + 1): for attempt in range(1, max_iterations + 1):
pub, priv = generate_meshcore_keypair() pub, priv = generate_meshcore_keypair()
if pub.hex().upper().startswith(target): if pub.hex().upper().startswith(target):
+5 -3
View File
@@ -4256,9 +4256,11 @@ class APIEndpoints:
key_was_generated = False key_was_generated = False
if not identity_key: if not identity_key:
try: try:
# Generate a new random 32-byte key (same method as config.py) # Use MeshCore-compatible keygen and store 32-byte private scalar hex.
random_key = os.urandom(32) from repeater.keygen import generate_meshcore_keypair
identity_key = random_key.hex()
_, private_key = generate_meshcore_keypair()
identity_key = private_key[:32].hex()
key_was_generated = True key_was_generated = True
logger.info(f"Auto-generated identity key for '{name}': {identity_key[:16]}...") logger.info(f"Auto-generated identity key for '{name}': {identity_key[:16]}...")
except Exception as gen_error: except Exception as gen_error: