#!/usr/bin/env python3
# verify.sh — third-party verifier for the SSAI beacon. Needs ONLY the public key.
#
# Checks, in order:
#   1. every log line is valid JSON with the required fields
#   2. hash chain: sha256(line N) == line N+1's "prev"; genesis prev = 64 zeros
#   3. no corruption at the tail (truncated/corrupt final line is reported)
#   4. HEAD.json.sig verifies against the pubkey AND matches the recomputed chain head
#   5. addresses.json.sig verifies against the pubkey (if present)
#
# Usage: verify.sh <log.jsonl> <pubkey.pem> [dir-with-HEAD-and-addresses]
# Exit 0 = valid, 1 = tampered/corrupt, 2 = structural problem.
import hashlib
import json
import subprocess
import sys

ZERO = "0" * 64


def fail(msg, code=1):
    print(f"INVALID: {msg}")
    sys.exit(code)


def main():
    args = sys.argv[1:]
    if len(args) < 2:
        print("usage: verify.sh <log.jsonl> <pubkey.pem> [dir-with-HEAD-and-addresses]")
        sys.exit(2)
    log_path, pub = args[0], args[1]
    extra = args[2] if len(args) > 2 else None

    try:
        lines = open(log_path, "rb").read().decode().splitlines()
    except OSError as e:
        fail(f"cannot read log: {e}", 2)
    if not lines:
        fail("empty log", 2)

    entries = []
    for i, raw in enumerate(lines):
        try:
            e = json.loads(raw)
            for f in ("seq", "ts", "type", "data", "prev"):
                if f not in e:
                    fail(f"line {i}: missing field '{f}'")
        except (json.JSONDecodeError, UnicodeDecodeError):
            if i == len(lines) - 1:
                fail(f"corrupt TAIL at line {i} — likely truncated write; chain before it intact", 1)
            fail(f"corrupt line {i} — log tampered or damaged mid-file")
        entries.append(e)

    if entries[0]["seq"] != 0 or entries[0]["prev"] != ZERO:
        fail("genesis entry missing (seq must start at 0, prev must be 64 zeros)")

    head_hash = None
    for i, e in enumerate(entries):
        if e["seq"] != i:
            fail(f"line {i}: seq out of order (got {e['seq']})")
        h = hashlib.sha256(lines[i].encode()).hexdigest()
        if head_hash is not None and e["prev"] != head_hash:
            fail(f"line {i}: hash-chain BROKEN (prev {e['prev'][:16]}… != computed {head_hash[:16]}…)")
        head_hash = h
    print(f"chain OK: {len(entries)} entries, head {head_hash[:16]}…")

    if extra:
        head_file = f"{extra}/HEAD.json"
        try:
            head = json.load(open(head_file))
        except (OSError, json.JSONDecodeError):
            head = None
        if head:
            if head.get("head_sha256") != head_hash:
                fail("HEAD.json head_sha256 does NOT match recomputed chain head")
            sig_ok = subprocess.run(
                ["openssl", "dgst", "-sha256", "-verify", pub,
                 "-signature", f"{head_file}.sig", head_file],
                capture_output=True).returncode == 0
            if not sig_ok:
                fail("HEAD.json signature INVALID")
            print(f"anchor OK: seq {head['seq']}, signed head matches chain")
            log_sha = hashlib.sha256(open(log_path, "rb").read()).hexdigest()
            if head.get("log_sha256") == log_sha:
                print("log file is byte-identical to the anchored state")
            else:
                print("log has entries newer than the anchor (expected if agent kept working)")

        addr_file = f"{extra}/addresses.json"
        try:
            json.load(open(addr_file))
            sig_ok = subprocess.run(
                ["openssl", "dgst", "-sha256", "-verify", pub,
                 "-signature", f"{addr_file}.sig", addr_file],
                capture_output=True).returncode == 0
            if not sig_ok:
                fail("addresses.json signature INVALID")
            print("address list OK: signature valid")
        except (OSError, json.JSONDecodeError):
            print("address list: not present, skipped")

    print("VALID")
    sys.exit(0)


if __name__ == "__main__":
    main()
