"""Ed25519 signatures in pure Python — the release-authenticity primitive.

Public-domain reference implementation (D. J. Bernstein et al.,
<https://ed25519.cr.yp.to/python/ed25519.py>), transcribed with the modular
exponentiation delegated to the built-in ``pow(b, e, m)`` (C-fast) so a
verification runs in a few milliseconds rather than seconds.

Why vendored and pure-stdlib, not a library or a binary:

- **The verify path must never add a dependency.** The zip-update recovery
  entry point runs under a bare system ``python3`` (no venv), and the update
  itself must not gate on a pip-installed crypto wheel — so signature
  verification has to work with the standard library alone.
- **No `openssl` shell-out.** macOS ships LibreSSL, whose Ed25519 CLI support is
  inconsistent with OpenSSL on Linux; a vendored implementation behaves
  identically on every machine.

Correctness is pinned by the RFC 8032 test vectors (`tests/test_ed25519.py`),
including negative cases (a flipped bit in the signature, the message, or the
key must fail). This module is verification-first; ``sign`` exists for the
build/release side and is covered by the same round-trip tests.
"""
from __future__ import annotations

import hashlib

_b = 256
_q = 2 ** 255 - 19
_L = 2 ** 252 + 27742317777372353535851937790883648493


def _H(m: bytes) -> bytes:
    return hashlib.sha512(m).digest()


def _inv(x: int) -> int:
    return pow(x, _q - 2, _q)


_d = -121665 * _inv(121666) % _q
_I = pow(2, (_q - 1) // 4, _q)


def _xrecover(y: int) -> int:
    xx = (y * y - 1) * _inv(_d * y * y + 1)
    x = pow(xx, (_q + 3) // 8, _q)
    if (x * x - xx) % _q != 0:
        x = (x * _I) % _q
    if x % 2 != 0:
        x = _q - x
    return x


_By = 4 * _inv(5) % _q
_Bx = _xrecover(_By)
_B = [_Bx % _q, _By % _q]


def _edwards(P, Q):
    x1, y1 = P
    x2, y2 = Q
    x3 = (x1 * y2 + x2 * y1) * _inv(1 + _d * x1 * x2 * y1 * y2)
    y3 = (y1 * y2 + x1 * x2) * _inv(1 - _d * x1 * x2 * y1 * y2)
    return [x3 % _q, y3 % _q]


def _scalarmult(P, e: int):
    """Iterative double-and-add — no deep recursion for a 256-bit scalar."""
    result = [0, 1]
    addend = P
    while e > 0:
        if e & 1:
            result = _edwards(result, addend)
        addend = _edwards(addend, addend)
        e >>= 1
    return result


def _encodeint(y: int) -> bytes:
    return y.to_bytes(_b // 8, "little")


def _encodepoint(P) -> bytes:
    x, y = P
    return (y | ((x & 1) << (_b - 1))).to_bytes(_b // 8, "little")


def _bit(h: bytes, i: int) -> int:
    return (h[i // 8] >> (i % 8)) & 1


def _secret_scalar(h: bytes) -> int:
    return 2 ** (_b - 2) + sum(2 ** i * _bit(h, i) for i in range(3, _b - 2))


def publickey(sk: bytes) -> bytes:
    """The 32-byte public key for a 32-byte secret seed."""
    if len(sk) != 32:
        raise ValueError("secret key must be 32 bytes")
    h = _H(sk)
    return _encodepoint(_scalarmult(_B, _secret_scalar(h)))


def _Hint(m: bytes) -> int:
    h = _H(m)
    return sum(2 ** i * _bit(h, i) for i in range(2 * _b))


def sign(m: bytes, sk: bytes, pk: bytes) -> bytes:
    """A 64-byte detached signature over ``m`` (release/build side only)."""
    if len(sk) != 32 or len(pk) != 32:
        raise ValueError("keys must be 32 bytes")
    h = _H(sk)
    a = _secret_scalar(h)
    r = _Hint(h[_b // 8:_b // 4] + m)
    R = _scalarmult(_B, r)
    S = (r + _Hint(_encodepoint(R) + pk + m) * a) % _L
    return _encodepoint(R) + _encodeint(S)


def _isoncurve(P) -> bool:
    x, y = P
    return (-x * x + y * y - 1 - _d * x * x * y * y) % _q == 0


def _decodeint(s: bytes) -> int:
    return int.from_bytes(s, "little")


def _decodepoint(s: bytes):
    y = int.from_bytes(s, "little") & ((1 << (_b - 1)) - 1)
    x = _xrecover(y)
    if x & 1 != _bit(s, _b - 1):
        x = _q - x
    P = [x, y]
    if not _isoncurve(P):
        raise ValueError("point is not on the curve")
    return P


def verify(signature: bytes, m: bytes, pk: bytes) -> bool:
    """True iff ``signature`` is a valid Ed25519 signature of ``m`` under ``pk``.

    Returns False on any malformed input rather than raising — a verifier's job
    is a yes/no, and every failure mode (wrong length, off-curve point, bad
    equation) must land on "no", never on an exception the caller forgets to
    catch."""
    try:
        if len(signature) != 64 or len(pk) != 32:
            return False
        R = _decodepoint(signature[:32])
        A = _decodepoint(pk)
        S = _decodeint(signature[32:])
        h = _Hint(signature[:32] + pk + m)
        return _scalarmult(_B, S) == _edwards(R, _scalarmult(A, h))
    except (ValueError, IndexError):
        return False
