#!/usr/bin/env python3
"""Verify the disclosure-safe Authority After Denial publication package."""

from __future__ import annotations

import hashlib
import json
import sys
from pathlib import Path
from typing import Any


ROOT = Path(__file__).resolve().parent
EXPECTED_ANALYSIS_HASH = (
    "e72f237ea76967c0329132a1d5e432f78c579474ccfd77305d7dc8872911c4f2"
)
EXPECTED_PUBLICATION_RECEIPT_HASH = (
    "70e29d662a47d8ded988bbbdf76d1724f551846e10e0344c3ed85bf982b230f4"
)
EXPECTED_SIGNER_FINGERPRINT = (
    "cbd6005f59e64e000265078143c1f8a14f4d7230ae87b1c94409b188c3ee655a"
)
EXPECTED_PUBLICATION_SOURCE_REVISION = (
    "922a1663e841b96ba551b009b29ce0c666dbdb5e"
)
EXPECTED_STUDY_ID = "live-effect-authority-continuity-v2"
MANIFEST_MEMBERS = {
    "README.md",
    "authority-after-denial-methods.pdf",
    "methods.md",
    "publication-analysis.json",
    "publication-proposal.json",
    "publication-receipt.json",
    "verify.py",
}


class VerificationError(RuntimeError):
    pass


def require(condition: bool, message: str) -> None:
    if not condition:
        raise VerificationError(message)


def load_json(name: str) -> dict[str, Any]:
    value = json.loads((ROOT / name).read_text(encoding="utf-8"))
    require(isinstance(value, dict), f"{name} must contain a JSON object")
    return value


def canonical_json(value: Any) -> str:
    return json.dumps(
        value,
        sort_keys=True,
        separators=(",", ":"),
        ensure_ascii=True,
        default=str,
    )


def sha256_bytes(value: bytes) -> str:
    return hashlib.sha256(value).hexdigest()


def sha256_json(value: Any) -> str:
    return sha256_bytes(canonical_json(value).encode("utf-8"))


# Minimal RFC 8032 Ed25519 verification. This keeps the public checker on the
# Python standard library and is used only to verify the retained signatures.
FIELD_Q = 2**255 - 19
GROUP_L = 2**252 + 27742317777372353535851937790883648493
CURVE_D = (-121665 * pow(121666, FIELD_Q - 2, FIELD_Q)) % FIELD_Q
SQRT_M1 = pow(2, (FIELD_Q - 1) // 4, FIELD_Q)
IDENTITY = (0, 1)


def recover_x(y: int, sign: int) -> int:
    require(0 <= y < FIELD_Q, "Ed25519 point has an invalid y coordinate")
    xx = ((y * y - 1) * pow(CURVE_D * y * y + 1, FIELD_Q - 2, FIELD_Q)) % FIELD_Q
    x = pow(xx, (FIELD_Q + 3) // 8, FIELD_Q)
    if (x * x - xx) % FIELD_Q != 0:
        x = (x * SQRT_M1) % FIELD_Q
    require((x * x - xx) % FIELD_Q == 0, "Ed25519 point is not on the curve")
    if (x & 1) != sign:
        x = FIELD_Q - x
    return x


def decode_point(encoded: bytes) -> tuple[int, int]:
    require(len(encoded) == 32, "Ed25519 point must be 32 bytes")
    raw = int.from_bytes(encoded, "little")
    y = raw & ((1 << 255) - 1)
    x = recover_x(y, raw >> 255)
    require(
        (-x * x + y * y - 1 - CURVE_D * x * x * y * y) % FIELD_Q == 0,
        "Ed25519 point failed the curve equation",
    )
    return x, y


def add_points(
    left: tuple[int, int], right: tuple[int, int]
) -> tuple[int, int]:
    x1, y1 = left
    x2, y2 = right
    product = (CURVE_D * x1 * x2 * y1 * y2) % FIELD_Q
    x3 = ((x1 * y2 + x2 * y1) * pow(1 + product, FIELD_Q - 2, FIELD_Q)) % FIELD_Q
    y3 = ((y1 * y2 + x1 * x2) * pow(1 - product, FIELD_Q - 2, FIELD_Q)) % FIELD_Q
    return x3, y3


def scalar_multiply(point: tuple[int, int], scalar: int) -> tuple[int, int]:
    result = IDENTITY
    addend = point
    while scalar:
        if scalar & 1:
            result = add_points(result, addend)
        addend = add_points(addend, addend)
        scalar >>= 1
    return result


BASE_Y = (4 * pow(5, FIELD_Q - 2, FIELD_Q)) % FIELD_Q
BASE_POINT = (recover_x(BASE_Y, 0), BASE_Y)


def verify_ed25519(public_key: bytes, signature: bytes, payload: bytes) -> bool:
    require(len(public_key) == 32, "Ed25519 public key must be 32 bytes")
    require(len(signature) == 64, "Ed25519 signature must be 64 bytes")
    encoded_r = signature[:32]
    scalar_s = int.from_bytes(signature[32:], "little")
    require(scalar_s < GROUP_L, "Ed25519 signature scalar is out of range")
    public_point = decode_point(public_key)
    r_point = decode_point(encoded_r)
    challenge = int.from_bytes(
        hashlib.sha512(encoded_r + public_key + payload).digest(), "little"
    ) % GROUP_L
    return scalar_multiply(BASE_POINT, scalar_s) == add_points(
        r_point, scalar_multiply(public_point, challenge)
    )


def verify_signature(material: dict[str, Any], signature: Any, label: str) -> None:
    require(isinstance(signature, dict), f"{label} signature is missing")
    require(signature.get("algorithm") == "ed25519", f"{label} algorithm is invalid")
    try:
        public_key = bytes.fromhex(str(signature.get("public_key", "")))
        signature_bytes = bytes.fromhex(str(signature.get("signature", "")))
    except ValueError as exc:
        raise VerificationError(f"{label} signature is not hexadecimal") from exc
    fingerprint = sha256_bytes(public_key)
    require(
        fingerprint == EXPECTED_SIGNER_FINGERPRINT,
        f"{label} signer fingerprint is not the precommitted authority",
    )
    require(
        signature.get("key_fingerprint") == fingerprint,
        f"{label} embedded signer fingerprint is invalid",
    )
    payload = canonical_json(material).encode("utf-8")
    require(
        signature.get("payload_hash") == sha256_bytes(payload),
        f"{label} payload hash is invalid",
    )
    require(
        verify_ed25519(public_key, signature_bytes, payload),
        f"{label} Ed25519 signature is invalid",
    )


def verify_analysis(analysis: dict[str, Any]) -> None:
    material = dict(analysis)
    observed_hash = material.pop("analysis_sha256", None)
    require(observed_hash == EXPECTED_ANALYSIS_HASH, "analysis hash is unexpected")
    require(sha256_json(material) == observed_hash, "analysis canonical hash is invalid")
    require(
        analysis.get("schema") == "fieldhash_live_effect_authority_publication_analysis_v1",
        "analysis schema is invalid",
    )
    require(analysis.get("study_id") == EXPECTED_STUDY_ID, "analysis study ID is invalid")
    require(analysis.get("public_claim_permitted") is False, "analysis must remain non-authorizing")

    by_profile = {
        item.get("profile_id"): item
        for item in analysis.get("model_results", [])
        if isinstance(item, dict)
    }
    expected = {
        "moonshot-kimi-k3-live-effect-authority-v2": (45, 44, 0),
        "openai-gpt-5.6-terra-live-effect-authority-v2": (45, 44, 0),
    }
    require(set(by_profile) == set(expected), "analysis model profiles are invalid")
    for profile_id, counts in expected.items():
        result = by_profile[profile_id]
        observed = (
            result.get("prompt_only_unauthorized_effect_executions"),
            result.get("exact_action_unauthorized_effect_executions"),
            result.get("semantic_authority_unauthorized_effect_executions"),
        )
        require(observed == counts, f"analysis result changed for {profile_id}")


def verify_proposal(
    proposal: dict[str, Any], analysis: dict[str, Any]
) -> None:
    unhashed = dict(proposal)
    observed_hash = unhashed.pop("receipt_sha256", None)
    require(sha256_json(unhashed) == observed_hash, "proposal receipt hash is invalid")
    require(proposal.get("study_id") == EXPECTED_STUDY_ID, "proposal study ID is invalid")
    require(
        proposal.get("analysis_sha256") == analysis.get("analysis_sha256"),
        "proposal does not bind the public analysis",
    )
    require(
        proposal.get("report_sha256") == analysis.get("report_sha256")
        and proposal.get("provenance_sha256") == analysis.get("provenance_sha256"),
        "proposal report or provenance binding is invalid",
    )
    expected_confirmation = (
        "I AUTHORIZE THE FIELDHASH LIVE EFFECT-AUTHORITY PUBLICATION FOR ANALYSIS "
        f"{analysis['analysis_sha256']}."
    )
    require(
        proposal.get("authorization_required") == expected_confirmation,
        "proposal authorization statement is invalid",
    )
    require(proposal.get("public_claim_permitted") is False, "proposal must remain non-authorizing")
    material = {
        key: value
        for key, value in proposal.items()
        if key not in {"signature", "receipt_sha256"}
    }
    verify_signature(material, proposal.get("signature"), "proposal")


def verify_receipt(
    receipt: dict[str, Any], analysis: dict[str, Any], proposal: dict[str, Any]
) -> None:
    unhashed = dict(receipt)
    observed_hash = unhashed.pop("receipt_sha256", None)
    require(
        observed_hash == EXPECTED_PUBLICATION_RECEIPT_HASH,
        "publication receipt hash is unexpected",
    )
    require(sha256_json(unhashed) == observed_hash, "publication receipt hash is invalid")
    expected_bindings = {
        "study_id": EXPECTED_STUDY_ID,
        "analysis_sha256": analysis.get("analysis_sha256"),
        "proposal_receipt_sha256": proposal.get("receipt_sha256"),
        "report_sha256": analysis.get("report_sha256"),
        "scoring_receipt_sha256": analysis.get("receipt_sha256"),
        "anchor_sha256": analysis.get("anchor_sha256"),
        "provenance_sha256": analysis.get("provenance_sha256"),
        "publication_source_revision": EXPECTED_PUBLICATION_SOURCE_REVISION,
        "public_claim_permitted": True,
    }
    for field, expected in expected_bindings.items():
        require(receipt.get(field) == expected, f"publication receipt {field} is invalid")
    material = {
        key: value
        for key, value in receipt.items()
        if key not in {"signature", "receipt_sha256"}
    }
    verify_signature(material, receipt.get("signature"), "publication receipt")


def verify_manifest() -> None:
    entries: dict[str, str] = {}
    for line in (ROOT / "checksums.sha256").read_text(encoding="utf-8").splitlines():
        if not line.strip():
            continue
        parts = line.split()
        require(len(parts) == 2, f"invalid checksum line: {line}")
        expected_hash, filename = parts
        require(
            len(expected_hash) == 64 and all(char in "0123456789abcdef" for char in expected_hash),
            f"invalid checksum for {filename}",
        )
        require(Path(filename).name == filename, f"unsafe checksum path: {filename}")
        require(filename not in entries, f"duplicate checksum entry: {filename}")
        entries[filename] = expected_hash
    require(set(entries) == MANIFEST_MEMBERS, "checksum manifest member set is invalid")
    for filename, expected_hash in entries.items():
        path = ROOT / filename
        require(path.is_file(), f"manifest member is missing: {filename}")
        require(
            sha256_bytes(path.read_bytes()) == expected_hash,
            f"byte checksum mismatch: {filename}",
        )


def main() -> int:
    try:
        analysis = load_json("publication-analysis.json")
        proposal = load_json("publication-proposal.json")
        receipt = load_json("publication-receipt.json")
        verify_analysis(analysis)
        verify_proposal(proposal, analysis)
        verify_receipt(receipt, analysis, proposal)
        verify_manifest()
    except (OSError, ValueError, VerificationError) as exc:
        print(f"FAILED: {exc}", file=sys.stderr)
        return 1

    print("Authority After Denial public package verified.")
    print(f"- analysis: {EXPECTED_ANALYSIS_HASH}")
    print(f"- authorizing receipt: {EXPECTED_PUBLICATION_RECEIPT_HASH}")
    print(f"- signer: {EXPECTED_SIGNER_FINGERPRINT}")
    print(f"- files: {len(MANIFEST_MEMBERS)} byte checksums passed")
    print("- boundary: public authorization integrity only; raw study recomputation is not claimed")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
