#!/usr/bin/env python3 """Standalone verifier for a CAIN-42 Quorum-QaaS notarization receipt. No CAIN imports. Needs Python 3.8+, the `cryptography` package, and verify_pbft_qc_bundle.py (the published PBFT certificate verifier) next to it. python3 verify_qaas_receipt.py receipt.json [--pin CONFIGURATION_HASH] [--digest SHA256_HEX] A receipt says: "a 4-replica Byzantine-fault-tolerant cluster (f=1, quorum 3) ordered and committed this SHA-256 digest at this sequence". It is NOTARIZED only if, from the bytes in the receipt alone: * the membership (member ids + Ed25519 keys) hashes to the receipt's configuration_hash, and to --pin if given (pin the hash published at /api/v1/live-cluster/membership; without a pin a forger could ship their own membership); * the operation is a qaas_notarize of exactly the receipt's digest (and of --digest if given); * the COMMIT_QC is a commit certificate, carries at least 3 distinct valid Ed25519 COMMIT votes by members, and its proposal_digest is the digest of exactly (operation, request_id, client_id); * the PREPARE_QC verifies, is for the same sequence and proposal digest, and its leader proposal (signed by the primary of that view) carries exactly this operation. It does not prove what the digest is a hash of, who submitted it beyond the pseudonymous client_id, or the wall-clock time of the commit (issued_at is the gateway's statement, not a consensus fact). """ from __future__ import annotations import json import os import sys from typing import Any, Dict, List, Optional sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import verify_pbft_qc_bundle as Q # noqa: E402 RECEIPT_TYPE = "CAIN42/QUORUM-QAAS/RECEIPT/v1" ACTION = "qaas_notarize" def _hex64(s: Any) -> bool: return isinstance(s, str) and len(s) == 64 and all(c in "0123456789abcdef" for c in s) def verify_receipt(r: Dict[str, Any], pin: Optional[str] = None, digest: Optional[str] = None) -> Dict[str, Any]: checks: List[Dict[str, Any]] = [] def check(name: str, ok: bool, detail: str = "") -> bool: checks.append({"check": name, "ok": bool(ok), "detail": detail}) return bool(ok) try: check("receipt type", r.get("type") == RECEIPT_TYPE, str(r.get("type"))) mb = Q.Membership(r["membership"]) check("membership hashes to configuration_hash", mb.configuration_hash == r.get("configuration_hash")) if pin is not None: check("configuration_hash matches the pinned hash", r.get("configuration_hash") == pin) op = r["operation"] d = r.get("digest") check("digest is lowercase SHA-256 hex", _hex64(d)) check("operation notarizes exactly this digest", isinstance(op, dict) and op.get("action") == ACTION and (op.get("data") or {}).get("digest") == d) if digest is not None: check("digest matches the expected digest", d == digest.lower()) pd = Q.proposal_request_digest({"operation": op, "request_id": r["request_id"], "client_id": r["client_id"]}) cq, pq = r["commit_qc"], r["prepare_qc"] ok, why = Q.verify_qc(cq, mb) check("COMMIT_QC verifies (>= quorum distinct member signatures)", ok, why) ok, why = Q.is_commit(cq) check("COMMIT_QC is a commit certificate", ok, why) check("COMMIT_QC certifies exactly this request", cq.get("proposal_digest") == pd) check("COMMIT_QC is for the receipt's sequence", cq.get("sequence") == r.get("sequence")) ok, why = Q.verify_qc(pq, mb) check("PREPARE_QC verifies (leader proposal signed by the view's primary)", ok, why) check("PREPARE_QC is for the same sequence and proposal", pq.get("certificate_type") == "PREPARE_QC" and (pq.get("sequence"), pq.get("proposal_digest")) == (cq.get("sequence"), pd)) lp = (pq.get("leader_proposal") or {}).get("payload") or {} check("leader proposal carries exactly this operation", lp.get("operation") == op and lp.get("request_id") == r["request_id"] and lp.get("client_id") == r["client_id"]) except (KeyError, TypeError, ValueError, AttributeError) as e: check("receipt well-formed", False, f"{type(e).__name__}: {e}") ok = bool(checks) and all(c["ok"] for c in checks) return {"verdict": "NOTARIZED" if ok else "NOT_VERIFIED", "checks": checks, "sequence": r.get("sequence") if ok else None, "cluster_id": r.get("cluster_id") if ok else None, "pinned": pin is not None} def main(argv: List[str]) -> int: if not argv or argv[0] in ("-h", "--help"): print(__doc__) return 2 pin = argv[argv.index("--pin") + 1] if "--pin" in argv else None digest = argv[argv.index("--digest") + 1] if "--digest" in argv else None with open(argv[0], encoding="utf-8") as fh: receipt = json.load(fh) res = verify_receipt(receipt, pin=pin, digest=digest) for c in res["checks"]: print(f" [{'PASS' if c['ok'] else 'FAIL'}] {c['check']}" + (f" -- {c['detail']}" if c["detail"] and not c["ok"] else "")) print(res["verdict"] + ("" if res["pinned"] else " (membership not pinned: pass --pin to bind the cluster)")) return 0 if res["verdict"] == "NOTARIZED" else 1 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))