proof-aware-crypto-tooling-.../src/pacta/witness.py
mrwulf fbe40c3dfe 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 16:05:20 +02:00

90 lines
3.6 KiB
Python

"""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,
)