#!/usr/bin/env python3
"""Verify that one PROOFLOOP record is inside a signed, quorum-anchored RFC 6962 Merkle tree. No CAIN imports.

    pip install cryptography
    python3 -I verify_proofloop_seal.py inclusion.json --pinned-pub <base64 Ed25519 key>

Checks, each printed PASS/FAIL:
  1. the record's own hash: SHA-256 of its canonical JSON (sorted keys, compact) without record_hash /
     signature_b64 / public_key_b64 equals record_hash
  2. the record's Ed25519 signature over record_hash, against the PINNED key (not the key inside the file)
  3. RFC 6962 inclusion: the audit path from leaf SHA256(0x00||record_hash) reaches the tree head's root
  4. the tree head's own hash and its Ed25519 signature against the pinned key
  5. the quorum anchor names exactly this root (its replica signatures are checked by the Quorum-QaaS verifier)
Exit 0 only if 1-4 pass (5 is reported; an unanchored head is said to be unanchored).
"""
import base64
import hashlib
import json
import sys


def canon(o):
    return json.dumps(o, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()


def ed_ok(pub_b64, sig_b64, msg):
    from cryptography.exceptions import InvalidSignature
    from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
    try:
        Ed25519PublicKey.from_public_bytes(base64.b64decode(pub_b64)).verify(base64.b64decode(sig_b64), msg)
        return True
    except (InvalidSignature, ValueError):
        return False


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


def node(a, b):
    return hashlib.sha256(b"\x01" + a + b).digest()


def root_from_path(leaf_hash, index, size, path):
    """RFC 9162 section 2.1.3.2 (iterative RFC 6962 audit-path verification)."""
    if not 0 <= index < size:
        return None
    fn, sn, r = index, size - 1, leaf(leaf_hash)
    for p in path:
        p = bytes.fromhex(p)
        if sn == 0:
            return None
        if fn & 1 or fn == sn:
            r = node(p, r)
            if not fn & 1:
                while fn and not fn & 1:
                    fn >>= 1
                    sn >>= 1
        else:
            r = node(r, p)
        fn >>= 1
        sn >>= 1
    return r.hex() if sn == 0 else None


def main(argv):
    if len(argv) < 4 or argv[2] != "--pinned-pub":
        print(__doc__)
        return 2
    proof = json.load(open(argv[1], encoding="utf-8"))
    pub = argv[3]
    rec, sth = proof["record"], proof["sth"]
    results = []
    body = {k: v for k, v in rec.items() if k not in ("record_hash", "signature_b64", "public_key_b64")}
    results.append(("record hash", hashlib.sha256(canon(body)).hexdigest() == rec.get("record_hash")
                    == proof.get("record_hash")))
    results.append(("record signature (pinned key)", ed_ok(pub, rec.get("signature_b64", ""),
                                                           str(rec.get("record_hash", "")).encode())))
    got = root_from_path(proof["record_hash"], proof["leaf_index"], proof["tree_size"], proof["audit_path"])
    results.append(("RFC 6962 inclusion in the tree head's root", got is not None and got == sth.get("root")
                    and proof["tree_size"] == sth.get("tree_size")))
    sbody = {k: v for k, v in sth.items() if k not in ("sth_hash", "signature_b64", "public_key_b64",
                                                       "quorum_anchor", "quorum_anchor_error")}
    results.append(("tree head hash + signature (pinned key)",
                    hashlib.sha256(canon(sbody)).hexdigest() == sth.get("sth_hash")
                    and ed_ok(pub, sth.get("signature_b64", ""), str(sth.get("sth_hash", "")).encode())))
    for name, ok in results:
        print(("PASS  " if ok else "FAIL  ") + name)
    qa = sth.get("quorum_anchor")
    if qa and qa.get("digest") == sth.get("root"):
        print(f"INFO  quorum anchor: cluster {qa.get('cluster_id')} sequence {qa.get('sequence')} names this root "
              "(verify its replica signatures with the Quorum-QaaS verifier)")
    elif qa:
        print("FAIL  quorum anchor names a different root")
        return 1
    else:
        print("INFO  this tree head is NOT anchored on the quorum" + (f" ({sth.get('quorum_anchor_error')})"
                                                                      if sth.get("quorum_anchor_error") else ""))
    ok = all(o for _, o in results)
    print("RESULT: " + ("RECORD IS IN THE SIGNED TREE" if ok else "NOT VERIFIED"))
    return 0 if ok else 1


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