92 lines
3.4 KiB
Python
92 lines
3.4 KiB
Python
"""Pass-Window Ticket signer/verifier.
|
|
|
|
Compact ticket structure intended for pre-signed pass-window triggers.
|
|
Uses HMAC (blake2b) signatures for tests/POC. The ticket hash is the sha256
|
|
of the canonical serialized payload (excluding signature), which can be used as
|
|
an external pointer in MFR-v2.pass_ticket_hash.
|
|
"""
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
import json
|
|
import time
|
|
import uuid
|
|
import hashlib
|
|
import hmac
|
|
|
|
|
|
@dataclass
|
|
class PassWindowTicket:
|
|
window_start: int
|
|
window_end: int
|
|
comm_budget_bytes: int
|
|
resource_budget_digest: str
|
|
trigger_policy_id: str
|
|
nonce: str
|
|
signer: str = ""
|
|
sig: str = ""
|
|
|
|
@classmethod
|
|
def mint(cls, window_start: int, window_end: int, comm_budget_bytes: int,
|
|
resource_budget_digest: str, trigger_policy_id: str = "default",
|
|
signer: str = "", key: Optional[bytes] = None) -> "PassWindowTicket":
|
|
nonce = uuid.uuid4().hex[:12]
|
|
t = cls(window_start=window_start, window_end=window_end,
|
|
comm_budget_bytes=int(comm_budget_bytes),
|
|
resource_budget_digest=resource_budget_digest,
|
|
trigger_policy_id=trigger_policy_id, nonce=nonce,
|
|
signer=signer, sig="")
|
|
if key:
|
|
t.sig = t._sign(key)
|
|
return t
|
|
|
|
def _canonical(self) -> str:
|
|
payload = {
|
|
"window_start": int(self.window_start),
|
|
"window_end": int(self.window_end),
|
|
"comm_budget_bytes": int(self.comm_budget_bytes),
|
|
"resource_budget_digest": self.resource_budget_digest,
|
|
"trigger_policy_id": self.trigger_policy_id,
|
|
"nonce": self.nonce,
|
|
"signer": self.signer,
|
|
}
|
|
return json.dumps(payload, sort_keys=True, separators=(",", ":"))
|
|
|
|
def _sign(self, key: bytes) -> str:
|
|
h = hmac.new(key, self._canonical().encode("utf-8"), hashlib.blake2b)
|
|
return h.hexdigest()[:64]
|
|
|
|
def serialize(self) -> bytes:
|
|
obj = {
|
|
"window_start": self.window_start,
|
|
"window_end": self.window_end,
|
|
"comm_budget_bytes": self.comm_budget_bytes,
|
|
"resource_budget_digest": self.resource_budget_digest,
|
|
"trigger_policy_id": self.trigger_policy_id,
|
|
"nonce": self.nonce,
|
|
"signer": self.signer,
|
|
"sig": self.sig,
|
|
}
|
|
return json.dumps(obj, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
|
|
|
def ticket_hash(self) -> str:
|
|
# external pointer: sha256 of canonical payload (no sig)
|
|
return hashlib.sha256(self._canonical().encode("utf-8")).hexdigest()
|
|
|
|
def verify(self, key: bytes) -> bool:
|
|
if not self.sig:
|
|
return False
|
|
expected = self._sign(key)
|
|
return hmac.compare_digest(expected, self.sig)
|
|
|
|
@classmethod
|
|
def deserialize(cls, b: bytes) -> "PassWindowTicket":
|
|
obj = json.loads(b.decode("utf-8"))
|
|
return cls(window_start=int(obj.get("window_start", 0)),
|
|
window_end=int(obj.get("window_end", 0)),
|
|
comm_budget_bytes=int(obj.get("comm_budget_bytes", 0)),
|
|
resource_budget_digest=obj.get("resource_budget_digest", ""),
|
|
trigger_policy_id=obj.get("trigger_policy_id", ""),
|
|
nonce=obj.get("nonce", ""),
|
|
signer=obj.get("signer", ""),
|
|
sig=obj.get("sig", ""))
|