"""Mutual Feasibility Receipt v2 (MFR-v2) Compact, byte-stable receipt suitable for DTN-era custody handoffs. This implementation focuses on a small JSON representation with deterministic serialization and an HMAC-based signature for tests/POC. Fields are chosen to keep serialized size <= 512 bytes in typical use. Fields (compact): - receipt_id: short random id - proposal_hash: short hex digest of proposal - invariant_id_set: list[int] (small ints) - decision: "ok"/"partial"/"deny" - uncertainty_pct: coarse uncertainty percent (0..100) - pass_ticket_hash: optional hex pointer to pre-signed ticket - execution_hash_skim: optional short hex prefix (e.g., blake2b prefix) - round_nonce, ts, signer, sig """ from dataclasses import dataclass from typing import List, Optional import time import uuid import json import hmac import hashlib @dataclass class MutualFeasibilityReceiptV2: receipt_id: str proposal_hash: str invariant_id_set: List[int] decision: str uncertainty_pct: int round_nonce: int ts: float pass_ticket_hash: str = "" execution_hash_skim: str = "" signer: str = "" sig: str = "" @classmethod def create(cls, proposal_hash: str, invariant_id_set: List[int], decision: str, uncertainty_pct: int = 0, round_nonce: int = 0, pass_ticket_hash: Optional[str] = None, execution_hash_skim: Optional[str] = None, signer: str = "", key: Optional[bytes] = None) -> "MutualFeasibilityReceiptV2": rid = uuid.uuid4().hex[:12] ts = time.time() r = cls(receipt_id=rid, proposal_hash=proposal_hash, invariant_id_set=list(invariant_id_set), decision=decision, uncertainty_pct=int(uncertainty_pct), round_nonce=int(round_nonce), ts=ts, pass_ticket_hash=pass_ticket_hash or "", execution_hash_skim=execution_hash_skim or "", signer=signer, sig="") if key: r.sig = r._sign(key) return r def _canonical_payload(self) -> str: # deterministic and compact JSON over authoritative fields (exclude sig) payload = { "receipt_id": self.receipt_id, "proposal_hash": self.proposal_hash, "invariant_id_set": self.invariant_id_set, "decision": self.decision, "uncertainty_pct": int(self.uncertainty_pct), "round_nonce": int(self.round_nonce), "ts": int(self.ts), "pass_ticket_hash": self.pass_ticket_hash, "execution_hash_skim": self.execution_hash_skim, "signer": self.signer, } return json.dumps(payload, sort_keys=True, separators=(",", ":")) def _sign(self, key: bytes) -> str: h = hmac.new(key, self._canonical_payload().encode("utf-8"), hashlib.blake2b) # use hex digest truncated to 64 chars to keep sizes small return h.hexdigest()[:64] def verify(self, key: bytes) -> bool: if not self.sig: return False expected = self._sign(key) return hmac.compare_digest(expected, self.sig) def serialize(self) -> bytes: obj = { "receipt_id": self.receipt_id, "proposal_hash": self.proposal_hash, "invariant_id_set": self.invariant_id_set, "decision": self.decision, "uncertainty_pct": self.uncertainty_pct, "round_nonce": self.round_nonce, "ts": self.ts, "pass_ticket_hash": self.pass_ticket_hash, "execution_hash_skim": self.execution_hash_skim, "signer": self.signer, "sig": self.sig, } return json.dumps(obj, sort_keys=True, separators=(",", ":")).encode("utf-8") @classmethod def deserialize(cls, b: bytes) -> "MutualFeasibilityReceiptV2": obj = json.loads(b.decode("utf-8")) return cls(receipt_id=obj.get("receipt_id", ""), proposal_hash=obj.get("proposal_hash", ""), invariant_id_set=[int(x) for x in obj.get("invariant_id_set", [])], decision=obj.get("decision", "deny"), uncertainty_pct=int(obj.get("uncertainty_pct", 0)), round_nonce=int(obj.get("round_nonce", 0)), ts=float(obj.get("ts", 0.0)), pass_ticket_hash=obj.get("pass_ticket_hash", ""), execution_hash_skim=obj.get("execution_hash_skim", ""), signer=obj.get("signer", ""), sig=obj.get("sig", "")) @property def size_bytes(self) -> int: return len(self.serialize())