src/pqc_audit_log_fs/anchor.py
1.2 KB · 41 lines · python Raw
1 """MerkleAnchor - periodically publish segment roots to an external transparency log."""
2
3 from __future__ import annotations
4
5 from abc import ABC, abstractmethod
6 from dataclasses import dataclass, field
7
8
9 class AnchorSink(ABC):
10 """Where anchored roots get published (blockchain, transparency log, etc.)."""
11
12 @abstractmethod
13 def publish(self, log_id: str, segment_number: int, merkle_root: str) -> str:
14 """Return an opaque receipt/transaction ID."""
15
16
17 class NullAnchorSink(AnchorSink):
18 """No-op sink - useful for tests."""
19
20 def __init__(self) -> None:
21 self.received: list[tuple[str, int, str]] = []
22
23 def publish(self, log_id: str, segment_number: int, merkle_root: str) -> str:
24 self.received.append((log_id, segment_number, merkle_root))
25 return f"null-receipt-{segment_number}"
26
27
28 @dataclass
29 class MerkleAnchor:
30 """Periodic anchoring of segment roots to an external store."""
31
32 sink: AnchorSink
33 published: dict[int, str] = field(default_factory=dict)
34
35 def anchor_segment(
36 self, log_id: str, segment_number: int, merkle_root: str
37 ) -> str:
38 receipt = self.sink.publish(log_id, segment_number, merkle_root)
39 self.published[segment_number] = receipt
40 return receipt
41