#!/usr/bin/env python3
"""Standalone CAIN-42 PBFT quorum-certificate verifier (no CAIN service, pinned deps).

Verifies the individual Ed25519 signatures over the exact canonical signing payload, enforces the quorum rule from
the supplied membership, and rejects duplicate signers and unknown keys. It does NOT verify stateful replay prevention
or live revocation, which an offline verifier cannot observe.

Usage:
  python3 verify_qc.py qc_sample.json membership.json
Exit code 0 iff the certificate meets the quorum rule with valid, unique, authorized signers.
"""
import base64, hashlib, json, sys

try:
    from cryptography.hazmat.primitives.asymmetric import ed25519
except Exception:
    print(json.dumps({"ok": False, "error": "cryptography package required (pip install cryptography==46.0.3)"}))
    raise SystemExit(2)


def cjb(d):
    return json.dumps(d, sort_keys=True, separators=(",", ":"), default=str).encode()


def signing_payload(m):
    return {"msg_type": m["msg_type"], "sender_id": m["sender_id"], "cluster_id": m["cluster_id"],
            "view": m["view"], "sequence": m["sequence"], "request_digest": m["request_digest"],
            "payload": json.loads(cjb(m["payload"])), "epoch": m["epoch"], "nonce": m["nonce"],
            "timestamp": round(m["timestamp"], 4), "engine_version": m["engine_version"]}


def verify_message(m, pinned_pub):
    if pinned_pub is None:
        return False, "UNKNOWN_KEY"
    digest = hashlib.sha256(cjb(signing_payload(m))).hexdigest().encode()
    try:
        ed25519.Ed25519PublicKey.from_public_bytes(base64.b64decode(pinned_pub)).verify(
            base64.b64decode(m["signature_b64"]), digest)
        return True, "OK"
    except Exception:
        return False, "SIGNATURE_INVALID"


def main(qc_path, membership_path):
    qc = json.load(open(qc_path))
    membership = json.load(open(membership_path))
    keys = {m["node_id"]: m["public_key_b64"] for m in membership["members"]}
    n = len(membership["members"]); f = membership["f"]; quorum = 2 * f + 1
    seen, valid, problems = set(), [], []
    for sender, m in sorted(qc["individual_signatures"].items()):
        if sender in seen:
            problems.append(f"DUPLICATE_SIGNER:{sender}"); continue
        seen.add(sender)
        if m.get("sender_id") != sender:
            problems.append(f"MISMATCHED_SENDER:{sender}"); continue
        if m.get("cluster_id") != qc["cluster_id"]:
            problems.append(f"WRONG_CLUSTER:{sender}"); continue
        ok, why = verify_message(m, keys.get(sender))
        if not ok:
            problems.append(f"{why}:{sender}")
        else:
            valid.append(sender)
    ok = len(valid) >= quorum and not any(p.startswith("DUPLICATE") for p in problems)
    print(json.dumps({"ok": ok, "cluster_id": qc["cluster_id"], "quorum_required": quorum, "n": n, "f": f,
                      "valid_signers": valid, "valid_count": len(valid), "problems": problems,
                      "note": "stateless certificate validity only; replay/revocation are stateful and out of scope"},
                     indent=1))
    return 0 if ok else 1


if __name__ == "__main__":
    if len(sys.argv) != 3:
        print(__doc__); raise SystemExit(2)
    raise SystemExit(main(sys.argv[1], sys.argv[2]))
