#!/usr/bin/env python3
"""
Completeness verifier — cross-recorder, trusting no one.

A tamper-evident log proves the integrity of what a party *logged*; it never proves the completeness
of what they *did*. The only thing that bounds that for a two-party interaction is the OTHER party's
record. This walks that bound against LIVE, Bitcoin-anchored recorders (no bundle disclosure needed):

Given A's entry that names B as counterparty and carries B's co-signature, and B's recorder feed, it:
  1. confirms A committed the interaction (folds A's entry inclusion proof to A's checkpoint → Bitcoin);
  2. verifies B's CO-SIGNATURE over the same canonical content (Ed25519) — B provably acknowledged it;
  3. proves B's own log is COMPLETE up to its latest checkpoint, by recomputing each checkpoint's
     Merkle root from B's enumerated entry_hashes and checking it equals the Bitcoin-anchored root
     (B cannot omit an entry without breaking a root on Bitcoin); then
  4. checks whether the interaction's payload_hash is present in B's committed log:
       BILATERAL             — present: both parties committed the same content to Bitcoin.
       CO-SIGNED-BUT-ABSENT  — absent, but B co-signed it: B acknowledged it, yet B's complete anchored
                               log omits it as of block N. A provable, timestamped, non-repudiable fact
                               a counterparty can present. (Absence alone is NOT suppression — this is
                               absence PLUS an acknowledgement, bounded by Bitcoin.)
       ONE-SIDED             — absent and not co-signed: A's word alone. Uncorroborated, not suppression.

Dependency-free (Python standard library); vendors Ed25519 verify (RFC 8032, validated vs libsodium)
and reimplements every hash — read it, then run it.

Usage:
    python3 completeness-verify.py --a-feed=<A recorder feed> --a-seq=<seq> --b-feed=<B recorder feed>
"""
import sys
import json
import base64
import hashlib
import urllib.request
import urllib.parse


# ===================== vendored Ed25519 verify (RFC 8032 reference) =====================
_p = 2**255 - 19
_L = 2**252 + 27742317777372353535851937790883648493
_d = (-121665 * pow(121666, _p - 2, _p)) % _p
_I = pow(2, (_p - 1) // 4, _p)


def _inv(x):
    return pow(x, _p - 2, _p)


def _xrec(y):
    xx = (y * y - 1) * _inv(_d * y * y + 1)
    x = pow(xx, (_p + 3) // 8, _p)
    if (x * x - xx) % _p != 0:
        x = (x * _I) % _p
    if x % 2 != 0:
        x = _p - x
    return x


_By = (4 * _inv(5)) % _p
_B = (_xrec(_By) % _p, _By % _p, 1, (_xrec(_By) * _By) % _p)


def _add(P, Q):
    x1, y1, z1, t1 = P
    x2, y2, z2, t2 = Q
    a = ((y1 - x1) * (y2 - x2)) % _p
    b = ((y1 + x1) * (y2 + x2)) % _p
    c = (t1 * 2 * _d * t2) % _p
    dd = (z1 * 2 * z2) % _p
    e, f, g, h = b - a, dd - c, dd + c, b + a
    return ((e * f) % _p, (g * h) % _p, (f * g) % _p, (e * h) % _p)


def _mul(P, e):
    if e == 0:
        return (0, 1, 1, 0)
    Q = _mul(P, e // 2)
    Q = _add(Q, Q)
    return _add(Q, P) if e & 1 else Q


def _enc(P):
    zi = _inv(P[2])
    x = (P[0] * zi) % _p
    y = (P[1] * zi) % _p
    return (y | ((x & 1) << 255)).to_bytes(32, "little")


def _oncurve(P):
    zi = _inv(P[2])
    x = (P[0] * zi) % _p
    y = (P[1] * zi) % _p
    return (-x * x + y * y - 1 - _d * x * x * y * y) % _p == 0


def _dec(s):
    y = int.from_bytes(s, "little") & ((1 << 255) - 1)
    x = _xrec(y)
    if x & 1 != (int.from_bytes(s, "little") >> 255) & 1:
        x = _p - x
    P = (x % _p, y % _p, 1, (x * y) % _p)
    if not _oncurve(P):
        raise ValueError("off curve")
    return P


def _ed_verify(pub, msg, sig):
    if len(pub) != 32 or len(sig) != 64:
        return False
    try:
        A = _dec(pub)
        R = sig[:32]
        s = int.from_bytes(sig[32:], "little")
        if s >= _L:
            return False
        h = int.from_bytes(hashlib.sha512(R + pub + msg).digest(), "little") % _L
        return _enc(_mul(_B, s)) == _enc(_add(_dec(R), _mul(A, h)))
    except Exception:
        return False


def ed_verify(pub_b64, msg_bytes, sig_b64):
    try:
        return _ed_verify(_b64(pub_b64), msg_bytes, _b64(sig_b64))
    except Exception:
        return False


def _b64(s):
    return base64.b64decode(s.replace("-", "+").replace("_", "/") + "===")
# ======================================================================================


def _sort(v):
    if isinstance(v, dict):
        return {k: _sort(v[k]) for k in sorted(v.keys())}
    if isinstance(v, list):
        return [_sort(x) for x in v]
    return v


def jcs(value):
    return json.dumps(_sort(value), separators=(",", ":"), ensure_ascii=False)


def _leaf(h):
    return hashlib.sha256(b"\x00" + bytes.fromhex(h)).hexdigest()


def _node(l, r):
    return hashlib.sha256(b"\x01" + bytes.fromhex(l) + bytes.fromhex(r)).hexdigest()


def fold_proof(entry_hash, proof):
    acc = _leaf(entry_hash)
    for s in proof:
        acc = _node(s["hash"], acc) if s["side"] == "left" else _node(acc, s["hash"])
    return acc


def merkle_root(entry_hashes):
    if not entry_hashes:
        return ""
    level = [_leaf(h) for h in entry_hashes]
    while len(level) > 1:
        nxt = []
        for i in range(0, len(level), 2):
            nxt.append(_node(level[i], level[i + 1]) if i + 1 < len(level) else level[i])
        level = nxt
    return level[0]


def recompute_entry_hash(e):
    parts = [
        str(e["seq"]), e["prev_hash"], e["server_ts"], e["payload_hash"],
        e["actor_sub"], e.get("counterparty_sub") or "", e["actor_sig"],
    ]
    # Beacon-bound entries append one line, so the committed drand round is covered by entry_hash.
    # Absent → byte-identical to the legacy preimage; present-but-malformed → reject (fail closed).
    b = e.get("server_beacon")
    if b is not None:
        if not isinstance(b, dict) or not all(k in b for k in ("chain", "round", "randomness")):
            return "malformed-server_beacon"
        parts.append("beacon:%s:%s:%s" % (b["chain"], b["round"], b["randomness"]))
    return hashlib.sha256("\n".join(parts).encode("utf-8")).hexdigest()


def signed_content(recorder, event_type, actor_sub, counterparty_sub, payload_hash, client_ts):
    return jcs({
        "v": 1, "recorder_id": recorder, "event_type": event_type, "actor_sub": actor_sub,
        "counterparty_sub": counterparty_sub, "payload_hash": payload_hash, "client_ts": client_ts,
    }).encode("utf-8")


def get_json(url):
    req = urllib.request.Request(url, headers={"Accept": "application/json", "User-Agent": "completeness-verify/1"})
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.loads(r.read().decode("utf-8"))


def enumerate_entries(feed):
    """All of a recorder's committed entries (paginated). Returns (list, committed_through)."""
    out, frm, through = [], 0, None
    while True:
        d = get_json(feed + "/entries?from=" + str(frm))
        through = d.get("committed_through", 0)
        out.extend(d.get("entries", []))
        nxt = d.get("next_from")
        if nxt is None:
            break
        frm = nxt
    return out, through


def prove_complete(feed):
    """Prove B's enumerated entries ARE the committed set: every checkpoint's Merkle root recomputes
    from the enumerated entry_hashes. Returns (entries_by_seq, committed_through, ok, latest_cp)."""
    entries, through = enumerate_entries(feed)
    by_seq = {e["seq"]: e for e in entries}
    feed_doc = get_json(feed)
    cps = feed_doc.get("checkpoints", [])
    ok = True
    latest = None
    for cp in cps:
        rng = [by_seq[s]["entry_hash"] for s in range(cp["seq_start"], cp["seq_end"] + 1) if s in by_seq]
        if len(rng) != (cp["seq_end"] - cp["seq_start"] + 1) or merkle_root(rng) != cp["merkle_root"]:
            ok = False
        if latest is None or cp["seq_end"] > latest["seq_end"]:
            latest = cp
    # contiguity 0..through
    if any(s not in by_seq for s in range(0, through + 1)):
        ok = False
    return by_seq, through, ok, latest


def main(argv):
    a_feed = a_seq = b_feed = None
    for x in argv[1:]:
        if x.startswith("--a-feed="):
            a_feed = x.split("=", 1)[1].rstrip("/")
        elif x.startswith("--a-seq="):
            a_seq = int(x.split("=", 1)[1])
        elif x.startswith("--b-feed="):
            b_feed = x.split("=", 1)[1].rstrip("/")
    if not (a_feed and a_seq is not None and b_feed):
        print(__doc__)
        return 2

    print("Completeness verifier — did the counterparty record what they acknowledged?\n")

    # --- [1/4] A committed the interaction (lower bound on A's side). ---
    pf = get_json(a_feed + "/entry/" + str(a_seq))
    e, cp = pf["entry"], pf["checkpoint"]
    a_recorder = pf["recorder"]
    eh_ok = recompute_entry_hash(e) == e["entry_hash"]
    root_ok = fold_proof(e["entry_hash"], pf["inclusion_proof"]) == cp["merkle_root"]
    H = e["payload_hash"]
    B = e.get("counterparty_sub")
    print("[1/4] A's record")
    print("  A recorder : %s  (entry seq %s)" % (a_recorder, e["seq"]))
    print("  interaction: payload_hash %s, counterparty %s" % (H, B))
    print("  %s A's entry folds to checkpoint #%s → Bitcoin (curl -O %s%s && ots verify)"
          % ("✓" if (eh_ok and root_ok) else "✗", cp["id"], a_feed.split("/.well-known/")[0], cp["ots"]))
    if not (eh_ok and root_ok and B):
        print("\nRESULT: UNVERIFIABLE — A's entry doesn't anchor or names no counterparty.")
        return 2

    # --- [2/4] B acknowledged it (co-signature over the same canonical content). ---
    b_origin = b_feed.split("/.well-known/")[0]
    pk, cosig = e.get("counterparty_pubkey"), e.get("counterparty_sig")
    msg = signed_content(a_recorder, e["event_type"], e["actor_sub"], B, H, e.get("client_ts"))
    cosig_ok = bool(pk and cosig and ed_verify(pk, msg, cosig))
    grade = None
    if cosig_ok:
        try:
            pubs = [b["signing_pubkey"] for b in get_json(b_origin + "/.well-known/touchstone/pubkeys/"
                    + urllib.parse.quote(B, safe="")).get("bindings", []) if b.get("verified")]
            grade = "verified" if pk in pubs else "claimed"
        except Exception:
            grade = "claimed"
    print("\n[2/4] B's acknowledgement")
    print("  %s B's co-signature over the interaction verifies%s"
          % (("✓" if cosig_ok else "✗"), (" (grade %s)" % grade) if grade else " — none present"))

    # --- [3/4] B's own log is provably complete to its latest Bitcoin checkpoint. ---
    by_seq, through, complete_ok, b_cp = prove_complete(b_feed)
    print("\n[3/4] B's log completeness")
    print("  %s every checkpoint root recomputes from B's enumerated entries (committed through seq %s)"
          % ("✓" if complete_ok else "✗", through))
    if b_cp:
        print("      B anchored to Bitcoin at checkpoint #%s (curl -O %s%s && ots verify)"
              % (b_cp["id"], b_origin, b_cp.get("proof_ots", "")))
    if not complete_ok:
        print("\nRESULT: UNVERIFIABLE — could not prove B's log enumeration is the committed set.")
        return 2

    # --- [4/4] Is the interaction present in B's committed log? ---
    present = any(x.get("payload_hash") == H for x in by_seq.values())
    print("\n[4/4] Presence in B's log")
    if present:
        print("  ✓ payload_hash %s… is in B's committed log — both sides recorded it." % H[:12])
        verdict = "BILATERAL ✓ — both parties committed this interaction to Bitcoin-anchored logs."
    elif cosig_ok:
        print("  ✗ payload_hash %s… is NOT in B's complete log, through checkpoint #%s." % (H[:12], b_cp["id"] if b_cp else "?"))
        verdict = ("CO-SIGNED-BUT-ABSENT ✗ — B acknowledged this interaction (co-signature%s) yet B's own "
                   "complete, Bitcoin-anchored log omits it as of checkpoint #%s. A provable, timestamped fact; "
                   "whether it is 'suppression' is the consumer's call, but the acknowledgement and the absence "
                   "are both bound to Bitcoin." % ((" grade " + grade) if grade else "", b_cp["id"] if b_cp else "?"))
    else:
        print("  · payload_hash %s… is not in B's log, and B did not co-sign it." % H[:12])
        verdict = ("ONE-SIDED — only A recorded this, with no counterparty acknowledgement. Uncorroborated; "
                   "NOT proof of suppression (you can't prove B was obliged to record it).")

    print("\nRESULT: " + verdict)
    print("\nThe honest boundary: absence in B's log is never suppression on its own — B may not record its "
          "side. What IS provable is absence PLUS B's own signature over the thing, each bound to Bitcoin. "
          "Everything above was recomputed here; only Bitcoin is trusted.")
    return 0 if present else (1 if cosig_ok else 3)


if __name__ == "__main__":
    sys.exit(main(sys.argv))
