From 2052ebf96489730acf6d9c5e12e36b89d874c670 Mon Sep 17 00:00:00 2001 From: agent-db0ec53c058f1326 Date: Thu, 16 Apr 2026 23:25:47 +0200 Subject: [PATCH] build(agent): molt-z#db0ec5 iteration --- signalvault/schema.py | 3 ++ .../privacy.py | 47 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/signalvault/schema.py b/signalvault/schema.py index a35ff6c..495a4e8 100644 --- a/signalvault/schema.py +++ b/signalvault/schema.py @@ -48,6 +48,9 @@ class AuditLog: signer: str | None = None timestamp: int | None = None contract_id: str | None = None + # Optional provenance fields for verifiable provenance and chaining + parent_hash: str | None = None + signature: str | None = None @dataclass diff --git a/signalvault_verifiable_privacy_preservin/privacy.py b/signalvault_verifiable_privacy_preservin/privacy.py index f3490e6..80c250d 100644 --- a/signalvault_verifiable_privacy_preservin/privacy.py +++ b/signalvault_verifiable_privacy_preservin/privacy.py @@ -1,5 +1,23 @@ from __future__ import annotations from typing import List, Dict +import hashlib + + +class Signer: + """Simple cryptographic signer placeholder. + This is a deterministic, non-cryptographic implementation intended for MVP provenance hooks. + """ + + @staticmethod + def sign(payload: str, key: str) -> str: + h = hashlib.sha256() + h.update(payload.encode("utf-8")) + h.update(key.encode("utf-8")) + return h.hexdigest() + + @staticmethod + def verify(payload: str, key: str, signature: str) -> bool: + return Signer.sign(payload, key) == signature class SecureAggregator: @@ -38,3 +56,32 @@ class DPBudget: take = min(remaining, amount) self.used += take return take + + +def sign_audit_log(audit_log: object, signer_id: str, key: str) -> object: + """Return a new AuditLog with a computed signature, chaining signer and payload. + + This function preserves the original AuditLog type but augments its provenance + with a signature over the log contents. It is a thin MVP hook for verifiable provenance. + """ + # Import locally to avoid hard coupling in module import order if AuditLog evolves + from signalvault_verifiable_privacy_preservin.schema import AuditLog as _AuditLog # type: ignore + + if not isinstance(audit_log, _AuditLog): + # Fallback: return as-is if it's not the expected type + return audit_log + + # Build payload from known provenance fields + payload_parts = ["|".join(audit_log.entries), str(audit_log.timestamp), audit_log.contract_id or "", signer_id] + payload = "|".join([p for p in payload_parts if p is not None and p != ""]) + signature = Signer.sign(payload, key) + + # Return a new AuditLog instance with the signature and signer propagated + return _AuditLog( + entries=audit_log.entries, + signer=signer_id, + timestamp=audit_log.timestamp, + contract_id=audit_log.contract_id, + parent_hash=audit_log.parent_hash, + signature=signature, + )