#!/usr/bin/env python3
"""Deterministic payment-rail address verification. Pure stdlib, no network.

check(chain, address) -> dict verdict:
  {"chain": ..., "address": ..., "valid": bool, "checks": {...}, "detail": str}
"""
import hashlib

NANO_ALPHABET = "13456789abcdefghijkmnopqrstuwxyz"
B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"


def _b32dec_nano(s):
    v = 0
    for c in s:
        v = (v << 5) | NANO_ALPHABET.index(c)
    return v


def _b58dec(s):
    v = 0
    for c in s:
        v = v * 58 + B58_ALPHABET.index(c)
    # leading '1's are leading zero bytes
    pad = len(s) - len(s.lstrip("1"))
    return pad, v


# --- nano ----------------------------------------------------------------

def check_nano(addr):
    out = {"prefix_ok": False, "length_ok": False, "charset_ok": False,
           "checksum_ok": False, "pubkey_hex": None}
    if not isinstance(addr, str):
        return False, out, "address is not a string"
    if not (addr.startswith("nano_") or addr.startswith("xrb_")):
        return False, out, "missing nano_/xrb_ prefix"
    out["prefix_ok"] = True
    body = addr.split("_", 1)[1]
    if len(body) != 60:
        return False, out, "payload must be 60 chars, got %d" % len(body)
    out["length_ok"] = True
    if any(c not in NANO_ALPHABET for c in body):
        return False, out, "char outside nano base32 alphabet"
    out["charset_ok"] = True
    payload, chk = body[:52], body[52:]
    pub_int = _b32dec_nano(payload) & ((1 << 256) - 1)  # top 4 bits are pad
    pub = pub_int.to_bytes(32, "big")
    out["pubkey_hex"] = pub.hex()
    want = _b32dec_nano(chk).to_bytes(5, "big")
    got = hashlib.blake2b(pub, digest_size=5).digest()[::-1]
    out["checksum_ok"] = (want == got)
    return out["checksum_ok"], out, ("ok" if out["checksum_ok"]
                                     else "checksum mismatch")


# --- evm (EIP-55) --------------------------------------------------------

# keccak-256, pure python (Ethereum variant; differs from SHA3-256 padding)
_RC = [0x0000000000000001, 0x0000000000008082, 0x800000000000808A,
       0x8000000080008000, 0x000000000000808B, 0x0000000080000001,
       0x8000000080008081, 0x8000000000008009, 0x000000000000008A,
       0x0000000000000088, 0x0000000080008009, 0x000000008000000A,
       0x000000008000808B, 0x800000000000008B, 0x8000000000008089,
       0x8000000000008003, 0x8000000000008002, 0x8000000000000080,
       0x000000000000800A, 0x800000008000000A, 0x8000000080008081,
       0x8000000000008080, 0x0000000080000001, 0x8000000080008008]
_ROTC = [[0, 36, 3, 41, 18], [1, 44, 10, 45, 2], [62, 6, 43, 15, 61],
         [28, 55, 25, 21, 56], [27, 20, 39, 8, 14]]
_MASK64 = (1 << 64) - 1


def _rol(x, n):
    return ((x << n) | (x >> (64 - n))) & _MASK64 if n else x


def _keccak_f(st):
    for rc in _RC:
        c = [st[x] ^ st[x + 5] ^ st[x + 10] ^ st[x + 15] ^ st[x + 20]
             for x in range(5)]
        d = [c[(x - 1) % 5] ^ _rol(c[(x + 1) % 5], 1) for x in range(5)]
        for x in range(5):
            for y in range(5):
                st[x + 5 * y] ^= d[x]
        b = [0] * 25
        for x in range(5):
            for y in range(5):
                b[y + 5 * ((2 * x + 3 * y) % 5)] = _rol(st[x + 5 * y], _ROTC[x][y])
        for x in range(5):
            for y in range(5):
                st[x + 5 * y] = b[x + 5 * y] ^ ((~b[(x + 1) % 5 + 5 * y]) & b[(x + 2) % 5 + 5 * y])
        st[0] ^= rc


def keccak256(data):
    rate, st = 136, [0] * 25
    msg = bytearray(data)
    msg.append(0x01)
    while len(msg) % rate != rate - 1:
        msg.append(0)
    msg.append(0x80)
    for off in range(0, len(msg), rate):
        blk = msg[off:off + rate]
        for i in range(rate // 8):
            st[i] ^= int.from_bytes(blk[i * 8:i * 8 + 8], "little")
        _keccak_f(st)
    out = b""
    while len(out) < 32:
        for i in range(rate // 8):
            out += st[i].to_bytes(8, "little")
        if len(out) >= 32:
            break
        _keccak_f(st)
    return out[:32]


def check_evm(addr):
    out = {"hex_ok": False, "length_ok": False, "eip55_applies": False,
           "eip55_ok": None}
    a = addr
    if isinstance(a, str) and a.startswith("0x"):
        a = a[2:]
    else:
        return False, out, "missing 0x prefix"
    if len(a) != 40:
        return False, out, "must be 40 hex chars, got %d" % len(a)
    out["length_ok"] = True
    try:
        int(a, 16)
    except ValueError:
        return False, out, "non-hex characters"
    out["hex_ok"] = True
    if a.islower() or a.isupper():
        return True, out, "ok (all-%s, no checksum to verify)" % (
            "lower" if a.islower() else "upper")
    out["eip55_applies"] = True
    h = keccak256(a.lower().encode()).hex()
    ok = all((c.isdigit() or (c.isupper() == (int(h[i], 16) >= 8)))
             for i, c in enumerate(a) if c.isalpha() or True)
    # rebuild per EIP-55 and compare
    rec = "".join(c.upper() if (c.isalpha() and int(h[i], 16) >= 8) else c
                  for i, c in enumerate(a))
    ok = (rec == a)
    out["eip55_ok"] = ok
    return ok, out, ("ok" if ok else "EIP-55 checksum mismatch")


# --- solana (base58 + ed25519) -------------------------------------------

_P = 2 ** 255 - 19
_D = (-121665 * pow(121666, _P - 2, _P)) % _P
_I = pow(2, (_P - 1) // 4, _P)


def _ed25519_on_curve(b):
    if len(b) != 32:
        return False
    y = int.from_bytes(b, "little") & ((1 << 255) - 1)
    if y >= _P:
        return False
    y2, x2 = y * y % _P, (y * y - 1) * pow((_D * y * y + 1) % _P, _P - 2, _P) % _P
    x = pow(x2, (_P + 3) // 8, _P)
    if (x * x - x2) % _P != 0:
        x = x * _I % _P
    if (x * x - x2) % _P != 0:
        return False
    return True   # a valid compressed y with a real x root is on-curve


def check_solana(addr):
    out = {"charset_ok": False, "length_ok": False, "curve_ok": False,
           "decoded_len": None}
    if not isinstance(addr, str) or not addr:
        return False, out, "empty"
    if any(c not in B58_ALPHABET for c in addr):
        return False, out, "char outside base58 alphabet"
    out["charset_ok"] = True
    pad, v = _b58dec(addr)
    raw = b"\x00" * pad + (v.to_bytes((v.bit_length() + 7) // 8, "big") if v else b"")
    out["decoded_len"] = len(raw)
    if len(raw) != 32:
        return False, out, "decoded length %d != 32 bytes" % len(raw)
    out["length_ok"] = True
    out["curve_ok"] = _ed25519_on_curve(raw)
    return out["curve_ok"], out, ("ok" if out["curve_ok"]
                                  else "not a valid ed25519 point")


CHAINS = {"nano": check_nano, "xno": check_nano, "evm": check_evm,
          "ethereum": check_evm, "base": check_evm,
          "solana": check_solana, "sol": check_solana}


def check(chain, address):
    fn = CHAINS.get((chain or "").lower())
    if not fn:
        return {"chain": chain, "address": address, "valid": False,
                "checks": {}, "detail": "unknown chain; supported: "
                + ", ".join(sorted(set(CHAINS)))}
    valid, checks, detail = fn(address)
    return {"chain": chain.lower(), "address": address, "valid": valid,
            "checks": checks, "detail": detail}


if __name__ == "__main__":
    import json, sys
    print(json.dumps(check(sys.argv[1], sys.argv[2]), indent=1))
