82 lines
2.5 KiB
Python
82 lines
2.5 KiB
Python
"""Lightweight Ed25519 signing helpers using PyNaCl.
|
|
|
|
Provides simple key generation, signing and verification utilities used by
|
|
the Attestation flow in ExProve. We keep this intentionally small to make
|
|
it easy to replace with HSM/DID integrations later.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Tuple
|
|
import hashlib
|
|
import hmac
|
|
import secrets
|
|
|
|
try:
|
|
# Prefer PyNaCl if available for real Ed25519 signatures
|
|
from nacl.signing import SigningKey, VerifyKey
|
|
from nacl.encoding import HexEncoder
|
|
|
|
def generate_signing_key() -> SigningKey:
|
|
"""Generate a new Ed25519 signing key."""
|
|
return SigningKey.generate()
|
|
|
|
|
|
def signing_key_to_hex(sk: SigningKey) -> str:
|
|
return sk.encode(encoder=HexEncoder).decode("utf-8")
|
|
|
|
|
|
def verify_key_hex_from_signing_key(sk: SigningKey) -> str:
|
|
vk = sk.verify_key
|
|
return vk.encode(encoder=HexEncoder).decode("utf-8")
|
|
|
|
|
|
def sign_message_hex(sk: SigningKey, message: bytes) -> str:
|
|
"""Sign a message and return signature as hex string."""
|
|
sig = sk.sign(message).signature
|
|
return sig.hex()
|
|
|
|
|
|
def verify_signature_hex(verify_key_hex: str, message: bytes, sig_hex: str) -> bool:
|
|
"""Verify signature hex against message. Returns True if valid."""
|
|
vk = VerifyKey(verify_key_hex, encoder=HexEncoder)
|
|
try:
|
|
vk.verify(message, bytes.fromhex(sig_hex))
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
except Exception:
|
|
# Fallback: HMAC-SHA256-based signing for environments without PyNaCl.
|
|
# This is NOT cryptographically equivalent to Ed25519 and is only a
|
|
# pragmatic fallback for tests and lightweight local usage.
|
|
|
|
class _FallbackKey:
|
|
def __init__(self, key: bytes):
|
|
self._key = key
|
|
|
|
def raw(self) -> bytes:
|
|
return self._key
|
|
|
|
|
|
def generate_signing_key() -> _FallbackKey:
|
|
return _FallbackKey(secrets.token_bytes(32))
|
|
|
|
|
|
def signing_key_to_hex(sk: _FallbackKey) -> str:
|
|
return sk.raw().hex()
|
|
|
|
|
|
def verify_key_hex_from_signing_key(sk: _FallbackKey) -> str:
|
|
return signing_key_to_hex(sk)
|
|
|
|
|
|
def sign_message_hex(sk: _FallbackKey, message: bytes) -> str:
|
|
sig = hmac.new(sk.raw(), message, hashlib.sha256).digest()
|
|
return sig.hex()
|
|
|
|
|
|
def verify_signature_hex(verify_key_hex: str, message: bytes, sig_hex: str) -> bool:
|
|
key = bytes.fromhex(verify_key_hex)
|
|
expected = hmac.new(key, message, hashlib.sha256).digest()
|
|
return hmac.compare_digest(expected, bytes.fromhex(sig_hex))
|