"""Offline verification of frozen legacy Litecoin message-signing fixtures.

Python3.10+ standard library. No network, wallet, Core binary or secret required.
This small public-data verifier is educational, not an audited security library.
It recovers a secp256k1 public key from the compact signature and checks the
regtest P2PKH address. It cannot prove how the original node was operated.
"""
from datetime import datetime
from pathlib import Path
import base64
import csv
import hashlib
import io
import json
import sys

P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
     0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8)
CHECKS = 0


def check(name, passed):
    global CHECKS
    CHECKS += 1
    if not passed:
        raise AssertionError(name)


def sha256d(value):
    return hashlib.sha256(hashlib.sha256(value).digest()).digest()


def compact_size(size):
    if size < 253:
        return bytes([size])
    if size <= 65535:
        return b"\xfd" + size.to_bytes(2, "little")
    if size <= 4294967295:
        return b"\xfe" + size.to_bytes(4, "little")
    return b"\xff" + size.to_bytes(8, "little")


def add(a, b):
    if a is None:
        return b
    if b is None:
        return a
    if a[0] == b[0] and (a[1] + b[1]) % P == 0:
        return None
    slope = (3 * a[0] * a[0] * pow(2 * a[1], -1, P)) % P if a == b else ((b[1] - a[1]) * pow(b[0] - a[0], -1, P)) % P
    x = (slope * slope - a[0] - b[0]) % P
    return x, (slope * (a[0] - x) - a[1]) % P


def multiply(number, point):
    result = None
    while number:
        if number & 1:
            result = add(result, point)
        point = add(point, point)
        number >>= 1
    return result


def decode_address(address):
    alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
    number = 0
    for char in address:
        number = number * 58 + alphabet.index(char)
    encoded = number.to_bytes((number.bit_length() + 7) // 8, "big")
    encoded = b"\0" * (len(address) - len(address.lstrip("1"))) + encoded
    if len(encoded) != 25 or sha256d(encoded[:-4])[:4] != encoded[-4:] or encoded[0] != 111:
        raise ValueError("Expected valid regtest legacy P2PKH address")
    return encoded[1:21]


def verify_message(address, signature, message):
    try:
        payload = base64.b64decode(signature, validate=True)
        if len(payload) != 65 or not 27 <= payload[0] <= 34:
            return False
        recover_id = (payload[0] - 27) & 3
        compressed = bool((payload[0] - 27) & 4)
        r, s = int.from_bytes(payload[1:33], "big"), int.from_bytes(payload[33:65], "big")
        if not 0 < r < N or not 0 < s < N:
            return False
        x = r + (recover_id >> 1) * N
        if x >= P:
            return False
        y_square = (x * x * x + 7) % P
        y = pow(y_square, (P + 1) // 4, P)
        if y * y % P != y_square:
            return False
        if y & 1 != recover_id & 1:
            y = P - y
        point = (x, y)
        if multiply(N, point) is not None:
            return False
        magic, text = b"Litecoin Signed Message:\n", message.encode("utf-8")
        digest = sha256d(compact_size(len(magic)) + magic + compact_size(len(text)) + text)
        z = int.from_bytes(digest, "big")
        inverse = pow(r, -1, N)
        public = add(multiply(s * inverse % N, point), multiply(-z * inverse % N, G))
        if public is None:
            return False
        pubkey = bytes([2 + (public[1] & 1)]) + public[0].to_bytes(32, "big") if compressed else b"\x04" + public[0].to_bytes(32, "big") + public[1].to_bytes(32, "big")
        recovered_hash = hashlib.new("ripemd160", hashlib.sha256(pubkey).digest()).digest()
        return recovered_hash == decode_address(address)
    except (ValueError, ZeroDivisionError):
        return False


def application_result(case):
    """Independently repeat the declared model; not a real account policy."""
    if not case["cryptographic_valid"]:
        return "reject_signature"
    challenge, expected = case["challenge"], case["expected_context"]
    if [challenge[key] for key in ("site", "purpose", "address", "nonce")] != [expected[key] for key in ("site", "purpose", "address", "nonce")]:
        return "reject_context"
    at = datetime.fromisoformat(case["checked_at_utc"].replace("Z", "+00:00"))
    expiry = datetime.fromisoformat(challenge["expires_utc"].replace("Z", "+00:00"))
    if at >= expiry:
        return "reject_expired"
    return "reject_replay" if challenge["nonce"] in case["used_nonces"] else "accept_once"


def main(folder):
    for line in (folder / "SHA256SUMS.txt").read_text(encoding="utf-8").splitlines():
        expected, name = line.split("  ", 1)
        path = (folder / name).resolve()
        check("Manifest stays within package", path.is_relative_to(folder.resolve()))
        check("File hash " + name, hashlib.sha256(path.read_bytes()).hexdigest() == expected)
    result = json.loads((folder / "results.json").read_text(encoding="utf-8"))
    cases = result["cases"]
    csv_rows = list(csv.DictReader(io.StringIO((folder / "cases.csv").read_text(encoding="utf-8"))))
    check("Exactly11 recorded verification cases", len(cases) == len(csv_rows) == 11)
    check("Regtest and pinned runtime", result["network"] == "regtest" and result["core_version"] == "/LitecoinCore:0.21.5.8/")
    check("Signer held zero LTC before and after", result["balances_ltc"]["signer_before"] == result["balances_ltc"]["signer_after"] == "0.00000000")
    for row, csv_row in zip(cases, csv_rows):
        encoded = row["message"].encode("utf-8")
        check(row["case_id"] + ": exact UTF8 bytes preserved", encoded.hex() == row["message_utf8_hex"])
        check(row["case_id"] + ": CSV identity and value", csv_row["case_id"] == row["case_id"] and csv_row["observed"] == str(row["observed"]))
        check(row["case_id"] + ": CSV message SHA", csv_row["message_utf8_sha256"] == hashlib.sha256(encoded).hexdigest())
        observed = verify_message(row["address"], row["signature_base64"], row["message"])
        check(row["case_id"] + ": independent recovered-key result matches Core", observed is row["observed"])
    first = cases[0]
    check("Original challenge file is exact, without trailing newline", (folder / "challenge.txt").read_bytes() == first["message"].encode("utf-8"))
    check("Replay identity exactly preserved", all(cases[0][key] == cases[3][key] for key in ("address", "message", "signature_base64")))
    check("Both exact attempts valid", cases[0]["observed"] is True and cases[3]["observed"] is True)
    check("Bad signature encoding rejected", verify_message(first["address"], "not base64", first["message"]) is False)
    damaged = bytearray(base64.b64decode(first["signature_base64"]))
    damaged[0] = 0
    check("Invalid compact header rejected", verify_message(first["address"], base64.b64encode(damaged).decode(), first["message"]) is False)
    check("Unsigned message alteration rejected independently", verify_message(first["address"], first["signature_base64"], first["message"] + "X") is False)
    unsupported = result["unsupported_signing"]
    check("Native SegWit actual capability error", unsupported["ordinary_native_segwit"]["code"] == -3 and "Address does not refer to key" in unsupported["ordinary_native_segwit"]["message"])
    check("Watch-only actual missing-private-key error", unsupported["watch_only_legacy"]["code"] == -4 and "Private key not available" in unsupported["watch_only_legacy"]["message"])
    models = json.loads((folder / "application-model.json").read_text(encoding="utf-8"))
    check("Six separately labeled application model cases", models == result["application_model_cases"] and len(models) == 6)
    for case in models:
        check(case["case_id"] + ": application model independently reproduced", application_result(case) == case["observed_model_result"] == case["expected"])
    check("Expired original challenge still had valid Core signature", models[2]["challenge"] == result["original_challenge"] and models[2]["cryptographic_valid"] is True and models[2]["observed_model_result"] == "reject_expired")
    check("Fresh nonce is a64hex fixture", len(result["original_challenge"]["nonce"]) == 64 and all(char in "0123456789abcdef" for char in result["original_challenge"]["nonce"]))
    trace = json.loads((folder / "rpc-trace.json").read_text(encoding="utf-8"))["calls"]
    check("No payment or private material export/import methods", not {"sendtoaddress", "sendrawtransaction", "signrawtransactionwithwallet", "walletprocesspsbt", "dumpprivkey", "dumpwallet", "backupwallet", "importprivkey", "importwallet", "listdescriptors"}.intersection(row["method"] for row in trace))
    check("Recorded peers zero", all(row["result"] == 0 for row in trace if row["method"] == "getconnectioncount"))
    check("Recorded chain height0", all(row["result"] == 0 for row in trace if row["method"] == "getblockcount"))
    check("All recorded experiment assertions passed", result["assertions_passed"] == len(result["assertions"]) == 32 and all(row["passed"] for row in result["assertions"]))
    check("Recorded successful shutdown and directory cleanup", result["cleanup"] == {"own_node_stopped": True, "fresh_directory_removed": True, "exit_code": 0})
    print(json.dumps({"success": True, "checks_passed": CHECKS, "independent_signature_cases": len(cases), "application_model_cases": len(models), "mode": "offline frozen-evidence verification, not a real identity or account check"}))


if __name__ == "__main__":
    main(Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else Path(__file__).resolve().parent)
