#!/usr/bin/env python3
"""
Tre'gent Receipt — Offline Ed25519 Verifier
Hive Civilization · canon/verify_receipt.py

Verifies an issuer signature over a Tre'gent receipt using only the receipt
JSON and the issuer's published Ed25519 public key. No network calls to Hive
infrastructure. Runs in ~50ms.

Usage:
    python3 verify_receipt.py <receipt.json> <issuer-pubkey.txt>

Or, fetch both straight from production and verify:
    curl -sSL https://thehiveryiq.com/manuka/demo/sample-receipt.json   -o receipt.json
    curl -sSL https://thehiveryiq.com/manuka/demo/issuer-pubkey.txt     -o issuer.pub
    python3 verify_receipt.py receipt.json issuer.pub

Exit codes:
    0  signature valid
    1  signature invalid OR receipt malformed
    2  bad arguments / missing files

Canonicalization (canonical_alg = "json-sorted-no-whitespace"):
    1. Parse receipt JSON.
    2. Remove the "signature" field.
    3. Re-serialize with sort_keys=True, separators=(",",":"), UTF-8.
    4. Verify Ed25519 signature over those exact bytes.

Dependencies: cryptography  (pip install cryptography)
"""

import sys
import json
import base64
import hashlib
from pathlib import Path

try:
    from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
    from cryptography.exceptions import InvalidSignature
except ImportError:
    print("error: this verifier requires the 'cryptography' package", file=sys.stderr)
    print("       pip install cryptography", file=sys.stderr)
    sys.exit(2)


def b64u_decode(s: str) -> bytes:
    """URL-safe base64 decode with auto-padding."""
    s = s.strip()
    pad = (-len(s)) % 4
    return base64.urlsafe_b64decode(s + ("=" * pad))


def canonicalize(receipt: dict) -> bytes:
    """Strip signature, sort keys, no whitespace, UTF-8."""
    to_sign = {k: v for k, v in receipt.items() if k != "signature"}
    return json.dumps(
        to_sign,
        sort_keys=True,
        separators=(",", ":"),
        ensure_ascii=False,
    ).encode("utf-8")


def main() -> int:
    if len(sys.argv) != 3:
        print("usage: python3 verify_receipt.py <receipt.json> <issuer-pubkey.txt>", file=sys.stderr)
        return 2

    receipt_path = Path(sys.argv[1])
    pubkey_path = Path(sys.argv[2])

    if not receipt_path.is_file():
        print(f"error: receipt file not found: {receipt_path}", file=sys.stderr)
        return 2
    if not pubkey_path.is_file():
        print(f"error: pubkey file not found: {pubkey_path}", file=sys.stderr)
        return 2

    try:
        receipt = json.loads(receipt_path.read_text(encoding="utf-8"))
    except json.JSONDecodeError as e:
        print(f"error: receipt is not valid JSON: {e}", file=sys.stderr)
        return 1

    sig_field = receipt.get("signature")
    if not sig_field or not sig_field.startswith("ed25519:"):
        print("error: receipt has no ed25519: signature field", file=sys.stderr)
        return 1

    pub_text = pubkey_path.read_text(encoding="utf-8").strip()
    # Accept either "ed25519:<b64u>" or bare "<b64u>"
    if pub_text.startswith("ed25519:"):
        pub_text = pub_text[len("ed25519:"):]

    try:
        pub_bytes = b64u_decode(pub_text)
        sig_bytes = b64u_decode(sig_field[len("ed25519:"):])
    except Exception as e:
        print(f"error: base64 decode failed: {e}", file=sys.stderr)
        return 1

    if len(pub_bytes) != 32:
        print(f"error: Ed25519 pubkey must be 32 bytes, got {len(pub_bytes)}", file=sys.stderr)
        return 1
    if len(sig_bytes) != 64:
        print(f"error: Ed25519 signature must be 64 bytes, got {len(sig_bytes)}", file=sys.stderr)
        return 1

    canonical = canonicalize(receipt)
    canon_hash = hashlib.sha256(canonical).hexdigest()

    try:
        Ed25519PublicKey.from_public_bytes(pub_bytes).verify(sig_bytes, canonical)
    except InvalidSignature:
        print()
        print("  RESULT:           FAIL")
        print(f"  receipt_id:       {receipt.get('receipt_id','?')}")
        print(f"  issuer_pubkey:    ed25519:{pub_text}")
        print(f"  canonical sha256: {canon_hash}")
        print("  reason:           Ed25519 signature does not verify against pubkey")
        print()
        return 1

    print()
    print("  RESULT:           PASS")
    print(f"  receipt_id:       {receipt.get('receipt_id','?')}")
    print(f"  symbol:           {receipt.get('symbol','?')}")
    print(f"  units:            {receipt.get('units','?')}")
    print(f"  issuer_pubkey:    ed25519:{pub_text}")
    print(f"  canonical sha256: {canon_hash}")
    print(f"  hahs_anchor:      base block {receipt.get('hahs_anchor',{}).get('block','?')}")
    print(f"  settlement:       {receipt.get('settlement',{}).get('amount_units','?')} {receipt.get('settlement',{}).get('denominated_in','?')} via {receipt.get('settlement',{}).get('instrument','?')}")
    print("  verified offline. no Hive infrastructure contacted.")
    print()
    return 0


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