#!/usr/bin/env python3
# Check a file against the Orbyt Labs provenance record.
# https://www.orbytlabs.ai/provenance
# Python 3, standard library only. The weekly seal is checked with ssh-keygen.
# It only reads from the site and from GitHub, and writes what it downloads to ./orbyt-provenance.
# Usage: python3 verify.py [file]   (with no file it asks; press Return to check the site's copy)
import datetime
import hashlib
import json
import os
import re
import subprocess
import sys
import urllib.error
import urllib.request

SITE = "https://www.orbytlabs.ai"
GITHUB = "https://raw.githubusercontent.com/orbytlabs-ai/ai-agent-research/main/provenance"
FILE = "/research/agent-leadership-governance/agent-leadership-governance.pdf"
OUT = "orbyt-provenance"
OWNER_KEYS = [
    'orbyt-owner namespaces="orbyt-provenance" ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJMl7pELNGVsAUl/c1J2pe9uQ/VKmoHFZ8RzRTSRLumlI2jssJPoL4cPTbxXHKxQgYgGuG3HJqQVMscsCFD4lZA=',
    'orbyt-owner namespaces="orbyt-provenance" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDel/a+TT40HU7lP6NUIbSysF/u+sOpQWRbGM7Z5SC5G',
]
DAY = re.compile(r"^\d{4}-\d{2}-\d{2}$")
sys.stdout.reconfigure(line_buffering=True)


def stop(message):
    print(message)
    sys.exit(1)


def fetch(url):
    try:
        with urllib.request.urlopen(url, timeout=60) as response:
            return response.status, response.read()
    except urllib.error.HTTPError as error:
        return error.code, b""
    except Exception:
        return None, b""


def get(url):
    status, body = fetch(url)
    if status != 200:
        stop(f"CANNOT FETCH: {url}")
    return body


def save(name, data):
    with open(os.path.join(OUT, name), "wb") as f:
        f.write(data)


def sha256(data):
    return hashlib.sha256(data).hexdigest()


def given_path(text):
    text = text.strip()
    if len(text) >= 2 and text[0] == text[-1] == "'":
        return text[1:-1].replace("'\\''", "'")
    if len(text) >= 2 and text[0] == text[-1] == '"':
        return text[1:-1]
    return text if os.name == "nt" else re.sub(r"\\(.)", r"\1", text)


if len(sys.argv) > 1:
    given = sys.argv[1]
else:
    try:
        given = given_path(input("Drag the file here and press Return: "))
    except EOFError:
        given = ""
data = None
if given:
    if not os.path.isfile(given):
        stop(f"NOT FOUND: there is no file at {given}")
    with open(given, "rb") as f:
        data = f.read()

os.makedirs(OUT, exist_ok=True)
raw = get(SITE + "/provenance/index.json")
save("index.json", raw)
try:
    index = json.loads(raw)
except ValueError:
    stop("CANNOT FETCH: the site's index is not valid JSON")
latest = index.get("latest")
if not isinstance(latest, str) or not DAY.match(latest):
    stop("NOT RECORDED YET: no manifest has been recorded yet, so there is nothing to check against.")
if data is None:
    print(f"SITE COPY: no file given, so this checks the site's copy of {FILE}")
    data = get(SITE + FILE)
digest = sha256(data)

manifests = {}


def check(day):
    raw = get(f"{SITE}/provenance/manifests/{day}.json")
    save(f"manifest-{day}.json", raw)
    try:
        manifest = json.loads(raw)
    except ValueError:
        stop(f"BAD MANIFEST: the {day} manifest is not valid JSON")
    body = json.dumps({k: v for k, v in manifest.items() if k != "manifest_sha256"}, sort_keys=True, separators=(",", ":")).encode()
    mh = sha256(body)
    if mh != manifest.get("manifest_sha256"):
        stop(f"BAD MANIFEST: the {day} manifest does not hash to its own manifest_sha256")
    manifests[day] = (manifest, mh, raw, body)
    return manifest


latest_manifest = check(latest)
if given:
    rows = [a for a in latest_manifest.get("artifacts", []) if a.get("sha256") == digest]
else:
    rows = [a for a in latest_manifest.get("artifacts", []) if a.get("path") == "public" + FILE and a.get("sha256") == digest]
if not rows:
    stop(f"NO MATCH: this file is not one we recorded in the {latest} manifest")
path = rows[0]["path"]
first = next((a.get("first_recorded") for a in index.get("latest_artifacts", []) if a.get("path") == path), None)
if not isinstance(first, str) or not DAY.match(first) or first > latest:
    first = latest
days = []
day = datetime.date.fromisoformat(first)
while day <= datetime.date.fromisoformat(latest):
    days.append(day.isoformat())
    day += datetime.timedelta(days=1)
for d in days:
    manifest = manifests[d][0] if d in manifests else check(d)
    if not any(a.get("path") == path and a.get("sha256") == digest for a in manifest.get("artifacts", [])):
        stop(f"CHANGED: the {d} manifest does not record these bytes for {path[len('public'):]}")
d0 = days[0]
h0 = manifests[d0][1]
save("manifest.body", manifests[d0][3])
print(f"MANIFEST: the {d0} manifest hashes to {h0}")
status, stamp = fetch(f"{SITE}/provenance/stamps/{d0}.ots")
if status == 200:
    save("stamp.ots", stamp)
    print(f"STAMP: the {d0} manifest has a timestamp proof, saved as {OUT}/stamp.ots")
else:
    print(f"NO STAMP: the {d0} manifest has no timestamp proof yet")

save("owner.allowed_signers", ("\n".join(OWNER_KEYS) + "\n").encode())
seals = index.get("seals") or []
weeks = [s.get("week_ending") for s in seals if isinstance(s.get("week_ending"), str) and DAY.match(s["week_ending"]) and s["week_ending"] >= d0]
if not seals:
    print("NO SEAL YET: the owner has not signed a weekly seal yet")
else:
    sealed = None
    for w in weeks:
        save(f"seal-{w}.json", get(f"{SITE}/provenance/seals/{w}.json"))
        save(f"seal-{w}.json.sig", get(f"{SITE}/provenance/seals/{w}.json.sig"))
        try:
            with open(os.path.join(OUT, f"seal-{w}.json"), "rb") as seal_file:
                said = subprocess.run(
                    ["ssh-keygen", "-Y", "verify", "-f", os.path.join(OUT, "owner.allowed_signers"), "-I", "orbyt-owner",
                     "-n", "orbyt-provenance", "-s", os.path.join(OUT, f"seal-{w}.json.sig")],
                    stdin=seal_file, capture_output=True, text=True,
                )
        except FileNotFoundError:
            stop("CANNOT FETCH: ssh-keygen is needed to check the seal, and it was not found")
        if said.returncode != 0:
            stop(f"BAD SEAL: the seal for the week ending {w} does not verify against the owner key in this command")
        with open(os.path.join(OUT, f"seal-{w}.json"), "rb") as seal_file:
            seal = json.loads(seal_file.read())
        for entry in seal.get("manifests", []):
            if entry.get("date") in manifests and entry.get("manifest_sha256") != manifests[entry["date"]][1]:
                stop(f"REWRITTEN: the seal for the week ending {w} names a different {entry['date']} manifest")
            if entry.get("date") == d0 and entry.get("manifest_sha256") == h0:
                sealed = (w, (re.search(r" key (SHA256:\S+)", said.stdout + said.stderr) or [None, ""])[1])
    if sealed:
        print(f"SEALED: the owner's seal for the week ending {sealed[0]} names the {d0} manifest, signed by key {sealed[1]}")
    else:
        print(f"NOT YET SEALED: no seal names the {d0} manifest yet")

copies = [("owner.allowed_signers", "owner.allowed_signers")]
copies += [(f"manifests/{d}.json", f"manifest-{d}.json") for d in days]
copies += [(f"seals/{w}.{x}", f"seal-{w}.{x}") for w in (weeks if seals else []) for x in ("json", "json.sig")]
missing = None
for remote, local in copies:
    status, body = fetch(f"{GITHUB}/{remote}")
    if status != 200:
        missing = missing or remote
        continue
    with open(os.path.join(OUT, local), "rb") as f:
        if f.read() != body:
            stop(f"GITHUB DIFFERS: the GitHub copy of {remote} is not what this command or this site holds")
if missing:
    print(f"NOT ON GITHUB YET: the GitHub copy of {missing} could not be read, so not everything was compared")
else:
    print("GITHUB: the GitHub copies of the owner key, the manifests and the seals are the same files")
print(f"MATCH: {path[len('public'):]} has the bytes first recorded on {d0}, unchanged through {latest}, sha256 {digest}")
