55 lines
1.9 KiB
Python
55 lines
1.9 KiB
Python
"""Governance ledger: append-only tamper-evident log with HMAC signatures.
|
|
|
|
This ledger implements a minimal tamper-evident append-only log. Each entry
|
|
carries the previous hash and an HMAC using a local key to simulate per-node
|
|
signatures. The ledger supports append and verification.
|
|
"""
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import time
|
|
from typing import List, Dict, Any, Optional
|
|
|
|
|
|
class GovernanceLedger:
|
|
def __init__(self, node_key: bytes):
|
|
self.node_key = node_key
|
|
self._chain: List[Dict[str, Any]] = []
|
|
|
|
def _entry_hash(self, entry: Dict[str, Any]) -> str:
|
|
raw = json.dumps(entry, sort_keys=True, separators=(",", ":"))
|
|
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
|
|
|
|
def _sign(self, payload: str) -> str:
|
|
return hmac.new(self.node_key, payload.encode("utf-8"), hashlib.sha256).hexdigest()
|
|
|
|
def append(self, author: str, payload: Dict[str, Any], ts: Optional[float] = None) -> Dict[str, Any]:
|
|
prev_hash = self._chain[-1]["hash"] if self._chain else ""
|
|
entry = {
|
|
"author": author,
|
|
"payload": payload,
|
|
"prev_hash": prev_hash,
|
|
"ts": time.time() if ts is None else ts,
|
|
}
|
|
entry_hash = self._entry_hash(entry)
|
|
signature = self._sign(entry_hash)
|
|
entry["hash"] = entry_hash
|
|
entry["sig"] = signature
|
|
self._chain.append(entry)
|
|
return entry
|
|
|
|
def verify_chain(self) -> bool:
|
|
prev = ""
|
|
for entry in self._chain:
|
|
if entry.get("prev_hash", "") != prev:
|
|
return False
|
|
# verify signature
|
|
expected_sig = self._sign(entry["hash"])
|
|
if not hmac.compare_digest(expected_sig, entry.get("sig", "")):
|
|
return False
|
|
prev = entry["hash"]
|
|
return True
|
|
|
|
def entries(self) -> List[Dict[str, Any]]:
|
|
return list(self._chain)
|