build(agent): jabba#56a767 iteration
This commit is contained in:
parent
19b74dc2ca
commit
82b5731339
|
|
@ -4,6 +4,18 @@ from dataclasses import dataclass, asdict
|
|||
from typing import Any, Dict, List
|
||||
import hashlib
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
try:
|
||||
from nacl.signing import SigningKey, VerifyKey
|
||||
from nacl.exceptions import BadSignatureError
|
||||
_HAS_PYNACL = True
|
||||
except Exception:
|
||||
# Keep module import-safe for environments without PyNaCl installed.
|
||||
SigningKey = None
|
||||
VerifyKey = None
|
||||
BadSignatureError = Exception
|
||||
_HAS_PYNACL = False
|
||||
|
||||
from .dsl import PlanDelta
|
||||
|
||||
|
|
@ -97,3 +109,36 @@ def generate_csfd(plan_delta: PlanDelta, signer: str = "", n_frames: int = 3) ->
|
|||
csfd = CSFDDigest(frames=frames, signer=signer, digest_hex=digest_hex)
|
||||
|
||||
return csfd
|
||||
|
||||
|
||||
def sign_csfd(csfd: CSFDDigest, signing_key_bytes: bytes) -> str:
|
||||
"""Sign a CSFD digest with an ed25519 private key (SigningKey bytes).
|
||||
|
||||
Returns the signature as a hex string. Requires PyNaCl.
|
||||
"""
|
||||
if not _HAS_PYNACL:
|
||||
raise RuntimeError("PyNaCl is required for signing CSFDs")
|
||||
|
||||
if not signing_key_bytes:
|
||||
raise ValueError("signing_key_bytes must be provided")
|
||||
|
||||
sk = SigningKey(signing_key_bytes)
|
||||
# Sign the compact digest_hex to produce a small signature
|
||||
sig = sk.sign(bytes.fromhex(csfd.digest_hex)).signature
|
||||
return sig.hex()
|
||||
|
||||
|
||||
def verify_csfd_signature(csfd: CSFDDigest, signature_hex: str, verify_key_bytes: bytes) -> bool:
|
||||
"""Verify a previously-created signature on the CSFD digest.
|
||||
|
||||
Returns True if valid, False otherwise. Requires PyNaCl.
|
||||
"""
|
||||
if not _HAS_PYNACL:
|
||||
raise RuntimeError("PyNaCl is required for signature verification")
|
||||
|
||||
vk = VerifyKey(verify_key_bytes)
|
||||
try:
|
||||
vk.verify(bytes.fromhex(csfd.digest_hex), bytes.fromhex(signature_hex))
|
||||
return True
|
||||
except BadSignatureError:
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ description = "MVP core for verifiable contract-driven mission planning in inter
|
|||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
license = {text = "MIT"}
|
||||
dependencies = ["pynacl>=1.4.0"]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
|
|
|
|||
|
|
@ -31,3 +31,28 @@ def test_csfd_size_bound():
|
|||
|
||||
serialized = json.dumps({"frames": [f.__dict__ for f in csfd.frames], "signer": csfd.signer, "digest_hex": csfd.digest_hex}, sort_keys=True, separators=(",", ":"))
|
||||
assert len(serialized.encode()) <= 512
|
||||
|
||||
|
||||
def test_csfd_signing_and_verification():
|
||||
import pytest
|
||||
pytest.importorskip("nacl")
|
||||
|
||||
# now import local helpers
|
||||
from missionledger.dsl import PlanDelta
|
||||
from missionledger.csfd import generate_csfd, sign_csfd, verify_csfd_signature
|
||||
|
||||
# create a small plandelta
|
||||
pd = PlanDelta(delta={"x": 1}, version=1, metadata={"invariants": {"a": True}})
|
||||
csfd = generate_csfd(pd, signer="s", n_frames=1)
|
||||
|
||||
# Generate an ephemeral Ed25519 keypair for signing
|
||||
from nacl.signing import SigningKey
|
||||
|
||||
sk = SigningKey.generate()
|
||||
sig_hex = sign_csfd(csfd, sk.encode())
|
||||
assert isinstance(sig_hex, str)
|
||||
assert len(sig_hex) > 0
|
||||
|
||||
vk = sk.verify_key
|
||||
ok = verify_csfd_signature(csfd, sig_hex, vk.encode())
|
||||
assert ok is True
|
||||
|
|
|
|||
Loading…
Reference in New Issue