build(agent): melter#14fd4b iteration

This commit is contained in:
agent-14fd4b738639d573 2026-04-24 20:06:47 +02:00
parent 9ea6c06bdd
commit a8cef41ff4
4 changed files with 106 additions and 0 deletions

View File

@ -63,6 +63,7 @@ class Attestation:
timestamp: float
contract_id: str
version: int
signature: Optional[str] = None
def to_json(self) -> str:
return to_json(asdict(self))

81
exprove/crypto.py Normal file
View File

@ -0,0 +1,81 @@
"""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))

View File

@ -13,6 +13,7 @@ authors = [ { name = "ExProve Team" } ]
dependencies = [
"dataclasses; python_version < '3.7'",
"typing; python_version < '3.8'",
"pynacl>=1.5.0",
]
[project.urls]

23
tests/test_crypto_sign.py Normal file
View File

@ -0,0 +1,23 @@
import json
from exprove.contracts import LocalExecutionTask, SharedMarketContext, PlanDelta, Attestation
from exprove.contracts import compute_delta
from exprove.crypto import generate_signing_key, verify_key_hex_from_signing_key, sign_message_hex, verify_signature_hex
def test_sign_and_verify_plan_delta():
task = LocalExecutionTask(task_id="t-sig", instrument="AAPL", venue="V1", objective="VWAP", constraints={})
ctx = SharedMarketContext(signals={"depth": 5}, version=1, contract_id="c-sig")
delta = compute_delta(task, ctx)
# canonical payload for signing
payload = json.dumps(delta.to_dict(), sort_keys=True).encode("utf-8")
sk = generate_signing_key()
vk_hex = verify_key_hex_from_signing_key(sk)
sig_hex = sign_message_hex(sk, payload)
# attach signature to attestation and verify
a = Attestation(entry=delta.to_json(), signer=vk_hex, timestamp=0.0, contract_id=ctx.contract_id, version=1, signature=sig_hex)
assert verify_signature_hex(vk_hex, payload, a.signature) is True