#!/usr/bin/env python3 """Verify a JSONL hash-chain memory store against an immutable-prefix checkpoint. Exit codes: 0 verified, 1 tamper/inconsistency, 2 usage or unreadable input. Stdlib only, deterministic, no network, no subprocess. Reads files only. """ import hashlib import json import sys GENESIS = "0" * 64 SCHEMA = "bilateral-reset-checkpoint/1" REQUIRED = ("id", "text", "source", "ts", "prev_hash", "hash") def canonical(entry): body = {k: v for k, v in entry.items() if k not in ("hash", "prev_hash")} return json.dumps(body, sort_keys=True, separators=(",", ":"), ensure_ascii=False) def entry_hash(prev_hash, entry): return hashlib.sha256((prev_hash + "\n" + canonical(entry)).encode("utf-8")).hexdigest() def split_lines(data): """Split bytes into lines, each keeping its trailing newline.""" return data.splitlines(keepends=True) def prefix_sha256(lines, n): return hashlib.sha256(b"".join(lines[:n])).hexdigest() def make_checkpoint(data, n): """Build a checkpoint dict covering the first n entries of store bytes.""" lines = split_lines(data) head = json.loads(lines[n - 1])["hash"] return {"schema": SCHEMA, "entry_count": n, "head_hash": head, "prefix_sha256": prefix_sha256(lines, n)} def verify(store_bytes, checkpoint): """Return a list of problem strings; empty list means verified.""" problems = [] lines = split_lines(store_bytes) entries = [] for i, line in enumerate(lines): if not line.endswith(b"\n"): problems.append(f"line {i + 1}: missing trailing newline") try: e = json.loads(line) except ValueError: problems.append(f"line {i + 1}: not valid JSON") continue if not isinstance(e, dict) or any(k not in e for k in REQUIRED): problems.append(f"line {i + 1}: missing required fields") continue entries.append((i, e)) expected_prev = GENESIS seen = set() for i, e in entries: if e["id"] in seen: problems.append(f"line {i + 1}: duplicate id {e['id']}") seen.add(e["id"]) if e["prev_hash"] != expected_prev: problems.append(f"line {i + 1} ({e['id']}): prev_hash does not match previous entry (chain break)") if entry_hash(e["prev_hash"], e) != e["hash"]: problems.append(f"line {i + 1} ({e['id']}): hash does not match content") expected_prev = e["hash"] n = checkpoint["entry_count"] if len(lines) < n: problems.append(f"store has {len(lines)} entries, checkpoint covers {n} (truncated or deleted)") else: if prefix_sha256(lines, n) != checkpoint["prefix_sha256"]: problems.append("checkpoint prefix sha256 mismatch (history rewritten or reordered)") try: head = json.loads(lines[n - 1]).get("hash") except ValueError: head = None if head != checkpoint["head_hash"]: problems.append("checkpoint head_hash mismatch at entry %d" % n) return problems def load_checkpoint(path): with open(path, "r", encoding="utf-8") as f: c = json.load(f) if (not isinstance(c, dict) or c.get("schema") != SCHEMA or not isinstance(c.get("entry_count"), int) or c["entry_count"] < 1 or not isinstance(c.get("head_hash"), str) or not isinstance(c.get("prefix_sha256"), str)): raise ValueError("not a valid %s file" % SCHEMA) return c def main(argv): if len(argv) != 2 or argv[0] in ("-h", "--help"): sys.stderr.write("usage: verify_checkpoint.py STORE.jsonl CHECKPOINT.json\n") return 2 try: with open(argv[0], "rb") as f: data = f.read() cp = load_checkpoint(argv[1]) except (OSError, ValueError) as e: sys.stderr.write("error: %s\n" % e) return 2 problems = verify(data, cp) if problems: print("TAMPER") for p in problems: print(" - " + p) return 1 print("VERIFIED entries>=%d head=%s" % (cp["entry_count"], cp["head_hash"])) return 0 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))