#!/usr/bin/env python3
"""Offline verifier for GAVEL Sandbox receipts (DEMO key). Needs: pip install cryptography

Usage:  python3 verify.py receipt.json pubkey.json
  receipt.json - a signed receipt as returned by the sandbox ({"receipt":..., "receipt_sha256":..., "signature":...})
  pubkey.json  - the DEMO public key from /sandbox/api/pubkey
Recomputes SHA-256 over the RFC 8785 (JCS) canonical receipt and checks the Ed25519 signature.
"""
import base64, hashlib, json, sys
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey


def jcs(v):
    if v is None: return "null"
    if v is True: return "true"
    if v is False: return "false"
    if isinstance(v, int): return str(v)
    if isinstance(v, float): raise ValueError("floats not allowed")
    if isinstance(v, str): return json.dumps(v, ensure_ascii=False)
    if isinstance(v, list): return "[" + ",".join(jcs(x) for x in v) + "]"
    if isinstance(v, dict):
        ks = sorted(v, key=lambda k: k.encode("utf-16-be"))
        return "{" + ",".join(json.dumps(k, ensure_ascii=False) + ":" + jcs(v[k]) for k in ks) + "}"
    raise ValueError(type(v))


def b64u(s): return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))


def no_dupes(pairs):
    d = {}
    for k, v in pairs:
        if k in d: raise SystemExit("FAILED: duplicate JSON key " + repr(k))
        d[k] = v
    return d


signed = json.load(open(sys.argv[1]), object_pairs_hook=no_dupes)
pub = json.load(open(sys.argv[2]))
sig = signed.get("signature", {})
problems = []
if set(signed) != {"receipt", "canonicalization", "receipt_sha256", "signature"}: problems.append("unexpected envelope fields")
if sig.get("alg") != "Ed25519": problems.append("alg is not Ed25519")
if sig.get("key_id") != pub["key_id"] or signed["receipt"].get("issuer", {}).get("key_id") != pub["key_id"]: problems.append("key_id does not match the DEMO public key")
for p in problems: print("envelope problem :", p)
canon = jcs(signed["receipt"]).encode()
h = hashlib.sha256(canon).hexdigest()
print("recomputed sha256:", h)
print("hash matches     :", h == signed["receipt_sha256"])
try:
    Ed25519PublicKey.from_public_bytes(b64u(pub["public_key_b64url"])).verify(b64u(signed["signature"]["sig_b64url"]), canon)
    print("signature valid  : True  (key", pub["key_id"], "- DEMO key)")
    ok = h == signed["receipt_sha256"] and not problems
except Exception:
    print("signature valid  : False")
    ok = False
print("VERIFIED" if ok else "FAILED")
sys.exit(0 if ok else 1)
