#!/usr/bin/env python3 """Verify Christian Rated's public vote log at https://christianrated.com. Recompute the hash chain and optionally compare it with an anchor on Solana. No dependencies beyond the standard library. Usage: python3 verify.py --no-chain # recompute the log only python3 verify.py --state # compare the latest anchor python3 verify.py --state --all-anchors # check every recorded anchor python3 verify.py --site http://localhost:18190 --no-chain # local preview Exit code 0 means every check passed. """ import argparse import base64 import hashlib import json import sys import urllib.request ZERO = "0" * 64 def fetch(url): with urllib.request.urlopen(url, timeout=120) as r: return r.read() def rpc(url, method, params): req = urllib.request.Request(url, json.dumps({"jsonrpc": "2.0", "id": 1, "method": method, "params": params}).encode(), {"Content-Type": "application/json"}) with urllib.request.urlopen(req, timeout=60) as r: out = json.loads(r.read()) if "error" in out: raise SystemExit(f"RPC error: {out['error']}") return out["result"] def recompute(lines): """Return the list of chain hashes. Raises on the first broken link.""" hashes, h = [], ZERO for raw in lines: if not raw.strip(): continue e = json.loads(raw) assert e["prev"] == h, f"chain broken at seq {e['seq']}" canon = json.dumps({k: v for k, v in e.items() if k != "prev"}, sort_keys=True, separators=(",", ":")) h = hashlib.sha256((h + canon).encode()).hexdigest() assert e["seq"] == len(hashes), f"gap at seq {e['seq']}" hashes.append(h) return hashes def read_state(rpc_url, state): data = base64.b64decode(rpc(rpc_url, "getAccountInfo", [state, {"encoding": "base64"}])["value"]["data"][0]) # layout: 8 discriminator, 32 owner, 8 batch_id, 32 state_hash, 8 vote_count, 8 timestamp return { "batch_id": int.from_bytes(data[40:48], "little"), "state_hash": data[48:80].hex(), "vote_count": int.from_bytes(data[80:88], "little"), "timestamp": int.from_bytes(data[88:96], "little", signed=True), } def main(): ap = argparse.ArgumentParser(description="Verify Christian Rated's public vote log and optional Solana anchors.") ap.add_argument("--site", default="https://christianrated.com", help="site URL (default: https://christianrated.com)") ap.add_argument("--rpc", default="https://api.mainnet-beta.solana.com") ap.add_argument("--state", help="the program's state account (printed on the Verify page)") ap.add_argument("--no-chain", action="store_true", help="only recompute the hash chain; skip Solana") ap.add_argument("--all-anchors", action="store_true", help="compare every anchor in /api/anchors with its transaction") ap.add_argument("--export", help="read the export from a local file instead of the site") a = ap.parse_args() if a.export: lines = open(a.export, encoding="utf-8").read().splitlines() else: lines = fetch(f"{a.site.rstrip('/')}/api/votes/export").decode("utf-8").splitlines() hashes = recompute(lines) print(f"chain OK: {len(hashes)} events, head {hashes[-1] if hashes else ZERO}") if a.no_chain: return 0 if not a.state: print("MISSING: pass --state (or --no-chain)", file=sys.stderr) return 2 st = read_state(a.rpc, a.state) n = st["vote_count"] if n == 0: print("no anchor posted yet") return 0 if n > len(hashes): print(f"MISMATCH: the chain anchors {n} events but the export has only {len(hashes)}") return 1 ok = hashes[n - 1] == st["state_hash"] print(("OK" if ok else "MISMATCH") + f": anchor batch {st['batch_id']} covers {n} events; on-chain hash {st['state_hash']}") if not ok: print("the server's history does not match the anchor on Solana") return 1 if a.all_anchors: anchors = json.loads(fetch(f"{a.site.rstrip('/')}/api/anchors")) bad = 0 for an in anchors: tx = rpc(a.rpc, "getTransaction", [an["signature"], {"encoding": "json", "maxSupportedTransactionVersion": 0}]) if tx is None: print(f"batch {an['batch_id']}: transaction {an['signature']} not found") bad += 1 continue logs = tx["meta"]["logMessages"] found = None for line in logs: if line.startswith("Program data: "): raw = base64.b64decode(line[len("Program data: "):]) # event: 8 discriminator, 8 batch_id, 32 state_hash, 8 vote_count, 8 timestamp found = {"batch_id": int.from_bytes(raw[8:16], "little"), "state_hash": raw[16:48].hex(), "vote_count": int.from_bytes(raw[48:56], "little")} if not found: print(f"batch {an['batch_id']}: no Anchored event in transaction logs") bad += 1 continue c = found["vote_count"] if c > len(hashes) or hashes[c - 1] != found["state_hash"] or found["batch_id"] != an["batch_id"]: print(f"batch {an['batch_id']}: MISMATCH (on-chain {found})") bad += 1 print(f"{len(anchors) - bad} of {len(anchors)} anchors match") return 1 if bad else 0 return 0 if __name__ == "__main__": sys.exit(main())