#!/usr/bin/env python3 """Check the Sky Truth public records yourself. Needs only Python 3, nothing to install. python3 verify_records.py https://sky.faithnfreedom.org/records/ python3 verify_records.py path/to/downloaded/records It checks three things: 1. Every prediction in the record is chained to the one before it (changing, removing or reordering any past prediction breaks the chain). 2. Every hourly stamp file names a fingerprint that really is in that chain. 3. Every hourly observations file (satellite results and scores) has exactly the fingerprint written in its stamp file. What it cannot do on its own is ask Bitcoin when each stamp file was made. For that, take any *.ledger-head.txt file and its *.ots file to https://opentimestamps.org (or run `ots verify` from the OpenTimestamps client). Together the two checks show the predictions and observations existed, unchanged, at the time Bitcoin recorded. """ import hashlib import json import re import sys import urllib.request from pathlib import Path GENESIS = "0" * 64 def canon(obj): return json.dumps(obj, sort_keys=True, separators=(",", ":"), default=str) def entry_hash(entry): return hashlib.sha256(canon({k: v for k, v in entry.items() if k != "hash"}).encode()).hexdigest() class Source: def __init__(self, where): self.url = where if re.match(r"https?://", where) else None self.base = where.rstrip("/") + "/" if self.url else Path(where) def get(self, rel): if self.url: with urllib.request.urlopen(self.base + rel, timeout=60) as r: return r.read() return (self.base / rel).read_bytes() def main(where): src = Source(where) manifest = json.loads(src.get("manifest.json")) files = {f["path"]: f["sha256"] for f in manifest["files"]} problems = 0 def fetch(rel): nonlocal problems data = src.get(rel) if hashlib.sha256(data).hexdigest() != files[rel]: print(f" ! {rel} does not match the list of published files") problems += 1 return data # 1. the prediction chain hashes, prev, n, first = set(), None, 0, None for rel in sorted(p for p in files if p.startswith("ledger/") and p.endswith(".jsonl")): for line in fetch(rel).decode().splitlines(): e = json.loads(line) if first is None: first, prev = e["seq"], e["prev_hash"] if e["prev_hash"] != prev: print(f" ! chain broken at prediction {e['seq']}") problems += 1 if entry_hash(e) != e["hash"]: print(f" ! prediction {e['seq']} was altered") problems += 1 prev = e["hash"] hashes.add(e["hash"]) n += 1 start = "from the very first prediction" if first == 0 else f"starting at prediction {first}" print(f"1. Prediction record: {n} predictions checked {start}; chain " f"{'intact' if not problems else 'NOT intact'}.") # 2 and 3. the stamp files stamps = sorted(p for p in files if p.endswith(".ledger-head.txt")) ok_heads = ok_obs = 0 for rel in stamps: text = fetch(rel).decode() m = re.search(r"prediction ledger head at \S+ UTC: ([0-9a-f]{64})", text) if m and m.group(1) in hashes: ok_heads += 1 elif m: print(f" ! {rel} names a fingerprint that is not in the published record") problems += 1 o = re.search(r"observations file (\S+) sha256: ([0-9a-f]{64})", text) if o: obs_rel = str(Path(rel).parent / o.group(1)) if obs_rel not in files: print(f" ! {obs_rel} is missing") problems += 1 elif hashlib.sha256(fetch(obs_rel)).hexdigest() == o.group(2): ok_obs += 1 else: print(f" ! {obs_rel} differs from what was stamped") problems += 1 print(f"2. Stamp files: {ok_heads} of {len(stamps)} name a fingerprint found in the record.") print(f"3. Observations files: {ok_obs} match their stamps exactly.") unconfirmed = [p for p in stamps if p + ".ots" not in files] if unconfirmed: print(f" ({len(unconfirmed)} stamp file(s) have no .ots proof published yet)") print() print("Everything checks out." if not problems else f"{problems} problem(s) found (listed above).") print("Next: take any .ledger-head.txt file and its .ots file to https://opentimestamps.org " "to see the Bitcoin block that dates it.") return 0 if not problems else 1 if __name__ == "__main__": if len(sys.argv) != 2: print(__doc__) sys.exit(2) sys.exit(main(sys.argv[1]))