Merge remote-tracking branch 'origin/feat-std-key-formats' into dev

This commit is contained in:
Lloyd
2026-06-29 15:21:05 +01:00
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:
logger.warning(f"Failed to load identity key: {e}")
# Generate new random key
key = os.urandom(32)
# Generate new key via MeshCore-compatible keygen; store 32-byte private scalar.
from repeater.keygen import generate_meshcore_keypair
_, private_key = generate_meshcore_keypair()
key = private_key[:32]
# Save it
try:
+27 -14
View File
@@ -14,6 +14,11 @@ from typing import Optional, Tuple
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]:
"""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 is 32 bytes, private_key is 64 bytes.
"""
# 1. Random 32-byte seed
seed = secrets.token_bytes(32)
while True:
# 1. Random 32-byte seed
seed = secrets.token_bytes(32)
# 2. SHA-512 hash
digest = hashlib.sha512(seed).digest()
# 2. SHA-512 hash
digest = hashlib.sha512(seed).digest()
# 3. Ed25519 scalar clamping on first 32 bytes
clamped = bytearray(digest[:32])
clamped[0] &= 248 # Clear bottom 3 bits
clamped[31] &= 63 # Clear top 2 bits
clamped[31] |= 64 # Set bit 6
# 3. Ed25519 scalar clamping on first 32 bytes
clamped = bytearray(digest[:32])
clamped[0] &= 248 # Clear bottom 3 bits
clamped[31] &= 63 # Clear top 2 bits
clamped[31] |= 64 # Set bit 6
# 4. Derive public key
public_key = crypto_scalarmult_ed25519_base_noclamp(bytes(clamped))
# 4. Derive public key
public_key = crypto_scalarmult_ed25519_base_noclamp(bytes(clamped))
# 5. Private key = [clamped_scalar][sha512_upper_half]
private_key = bytes(clamped) + digest[32:64]
# Disallow keys with 0x00 / 0xFF leading byte.
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(
@@ -57,6 +67,9 @@ def generate_vanity_key(
"""
target = prefix.upper()
if target.startswith("00") or target.startswith("FF"):
return None
for attempt in range(1, max_iterations + 1):
pub, priv = generate_meshcore_keypair()
if pub.hex().upper().startswith(target):
+5 -3
View File
@@ -4256,9 +4256,11 @@ class APIEndpoints:
key_was_generated = False
if not identity_key:
try:
# Generate a new random 32-byte key (same method as config.py)
random_key = os.urandom(32)
identity_key = random_key.hex()
# Use MeshCore-compatible keygen and store 32-byte private scalar hex.
from repeater.keygen import generate_meshcore_keypair
_, private_key = generate_meshcore_keypair()
identity_key = private_key[:32].hex()
key_was_generated = True
logger.info(f"Auto-generated identity key for '{name}': {identity_key[:16]}...")
except Exception as gen_error: