proof-aware-crypto-tooling-.../src/pacta/witness.py

91 lines
3.6 KiB
Python
Raw Normal View History

The log goes public: git-published mirror, online service, witnesses Three synchronized faces of one log - transport orthogonal to trust: - PUBLISHED GIT MIRROR: log-publish exports the public face (one file per leaf so git history mirrors log history; the FULL STH history as the witness channel; per-component attestations + receipts; the provider public key; a standalone stdlib-only verify.py and customer README). Live at github.com/saymrwulf/lean-transparency-log (genesis: 8 leaves incl. the honest failed-run entries, dogfood-signed head). - ONLINE SERVICE (pacta_provider serve): read-only, zero-dependency HTTP with CT-style endpoints under a base path for zkdefi.org/lean-transparency-log - /v1/sth, /v1/sth-history, /v1/sth-consistency?first=N, /v1/proof, /v1/attestation, /v1/entries, /v1/metadata, /healthz - plus self-contained customer documentation at /docs (current state, attested components, API, the verify- without-trusting-this-site path, and the means/does-NOT-mean boundary). The process never loads private keys: heads are signed offline; a compromised server can withhold or replay (pinning + freshness detect both) but never forge. STH history now recorded append-only by the provider (with a backfill head signed for the existing log). - AGENT ONLINE CLIENT: pacta log-fetch (download evidence; explicitly UNVERIFIED until receipt-verify runs - transport is not trust) and pacta sth-refresh (fetch head, verify signature, advance the pin via an online consistency proof from the pinned size; fail closed). - WITNESSES: pacta witness-audit over a clone of the published mirror recomputes every prefix root from the public leaves and checks every historical head + signature - no consistency proofs needed when the leaves are public. Tampering one published entry trips both the leaf-hash check and the prefix-root check (tested). verify.py gives customers the same audit with zero installation. - DEPLOY.md: the complete server-session checklist for zkdefi.org - reconstruct the servable log FROM the published mirror (the server stays in witness trust-position), hardened systemd unit, nginx/Caddy path routing, Forgejo mirror setup, the provider->world update cycle, and remote smoke tests. Validated end-to-end on the REAL log: all 10 endpoints, online-fetched proof re-verified locally through the dogfood verifier with pinning, online pin refresh, publish + witness audit green, tamper caught, standalone verify.py green in the published clone. 54/54 tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:05:20 +00:00
"""Witness audit over the PUBLISHED log repository.
The git-published log (entries/ + sth-history.jsonl + provider key) turns
every cloner into a witness: since all leaves are public, a witness does
not need consistency proofs at all - it recomputes the root of every
prefix directly and checks every historical Signed Tree Head against its
prefix root and its signature. Two different clones agreeing on the git
history and both passing this audit have PROOF the provider never
equivocated within it.
"""
from __future__ import annotations
import json
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
from .signing import canonical_json
from .transparency import leaf_hash, merkle_root, verify_signed_tree_head
@dataclass(slots=True)
class WitnessReport:
ok: bool
tree_size: int
heads_checked: int
problems: list[str] = field(default_factory=list)
notes: list[str] = field(default_factory=list)
def audit_published_log(
published_dir: str | Path,
log_public_key_path: str | Path | None = None,
) -> WitnessReport:
root_dir = Path(published_dir)
problems: list[str] = []
notes: list[str] = []
entry_files = sorted((root_dir / "entries").glob("[0-9]*.json"))
leaves: list[bytes] = []
for position, path in enumerate(entry_files):
record = json.loads(path.read_text(encoding="utf-8"))
if int(record.get("index", -1)) != position:
problems.append(f"{path.name}: index {record.get('index')} at position {position} (gap or reorder).")
leaf_bytes = canonical_json(record["leaf"])
if leaf_hash(leaf_bytes).hex() != record.get("leaf_hash"):
problems.append(f"{path.name}: leaf_hash does not match the leaf content.")
leaves.append(leaf_bytes)
history_path = root_dir / "sth-history.jsonl"
heads = [
json.loads(line)
for line in history_path.read_text(encoding="utf-8").splitlines()
if line.strip()
] if history_path.exists() else []
if not heads:
notes.append("No sth-history.jsonl; only the latest head can be checked.")
latest_path = root_dir / "latest-sth.json"
if latest_path.exists():
heads = [json.loads(latest_path.read_text(encoding="utf-8"))]
previous_size = -1
for position, head in enumerate(heads):
size = int(head.get("tree_size", -1))
if size < previous_size:
problems.append(f"STH #{position}: tree_size {size} SHRANK from {previous_size} (rollback in history).")
previous_size = max(previous_size, size)
if size < 0 or size > len(leaves):
problems.append(f"STH #{position}: tree_size {size} outside published entries ({len(leaves)}).")
continue
expected_root = merkle_root(leaves[:size]).hex()
if head.get("root_hash") != expected_root:
problems.append(
f"STH #{position} (size {size}): signed root {str(head.get('root_hash'))[:16]}… does not match "
f"the recomputed prefix root {expected_root[:16]}… - EQUIVOCATION or tampered entries."
)
if log_public_key_path is not None:
ok, diagnostics, _statuses = verify_signed_tree_head(head, log_public_key_path)
if not ok:
problems.append(f"STH #{position} (size {size}): signature check failed: {'; '.join(diagnostics)}")
if log_public_key_path is None:
notes.append("No public key supplied; structural audit only (signatures unchecked).")
return WitnessReport(
ok=not problems,
tree_size=len(leaves),
heads_checked=len(heads),
problems=problems,
notes=notes,
)