<?php
// =============================================================
// Secp256k1 — ECDSA-подпись для Ethereum/Hyperliquid (чистый PHP).
// =============================================================
// Подписывает заранее вычисленный 32-байтовый дайджест (EIP-712 hash) и
// возвращает каноническую подпись {r, s, v} с low-s нормализацией и
// recovery-id (v = 27/28), как eth_account.sign_message в Python-SDK.
//
// Детерминированный nonce k — RFC 6979 (HMAC-SHA256), чтобы подпись
// побайтово совпадала с эталоном SDK (golden vectors в tests/).
//
// Точки умножаются в Jacobian-координатах: одна модульная инверсия на всю
// подпись вместо ~256 — критично для скорости чистого PHP.

namespace HL\Crypto;

require_once __DIR__ . '/BigInt.php';
require_once __DIR__ . '/Keccak.php';

final class Secp256k1
{
    private static ?BigInt $p = null;   // характеристика поля
    private static ?BigInt $n = null;   // порядок группы
    private static ?BigInt $gx = null;
    private static ?BigInt $gy = null;
    private static ?BigInt $halfN = null;

    private static function init(): void
    {
        if (self::$p !== null) {
            return;
        }
        self::$p  = BigInt::fromHex('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f');
        self::$n  = BigInt::fromHex('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141');
        self::$gx = BigInt::fromHex('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798');
        self::$gy = BigInt::fromHex('483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8');
        // halfN = (n-1)/2
        self::$halfN = self::$n->sub(BigInt::fromInt(1));
        // делим на 2: используем sub-битовый трюк не нужен — n-1 чётно, /2 через powMod(inv 2)? нет.
        // Проще: halfN = (n >> 1). Реализуем сдвигом через hex.
        self::$halfN = self::shiftRight1(self::$n);
    }

    private static function shiftRight1(BigInt $x): BigInt
    {
        // n нечётное → (n>>1) = (n-1)/2; используем hex-арифметику деления на 2
        $hex = $x->toHex();
        // бинарный сдвиг вправо на 1 через перевод в биты дорог; делаем через BigInt:
        // x>>1 = floor(x/2). Реализуем как (x - (x mod 2)) * inv? Нет деления.
        // Делаем побитово на hex: long division base 16.
        $out = '';
        $rem = 0;
        for ($i = 0, $len = strlen($hex); $i < $len; $i++) {
            $cur = $rem * 16 + hexdec($hex[$i]);
            $out .= dechex(intdiv($cur, 2));
            $rem = $cur % 2;
        }
        $out = ltrim($out, '0');
        return BigInt::fromHex($out === '' ? '0' : $out);
    }

    // --------------------------------------------------------- точки (Jacobian)
    // Точка: [X, Y, Z] (BigInt). Бесконечность: Z = 0.

    private static function jacobianDouble(array $pt): array
    {
        [$X, $Y, $Z] = $pt;
        $p = self::$p;
        if ($Y->isZero() || $Z->isZero()) {
            return [BigInt::fromInt(1), BigInt::fromInt(1), BigInt::zero()];
        }
        $A = $X->mulMod($X, $p);                       // X^2
        $B = $Y->mulMod($Y, $p);                       // Y^2
        $C = $B->mulMod($B, $p);                       // B^2
        $XplusB = $X->addMod($B, $p);
        $D = $XplusB->mulMod($XplusB, $p)->subMod($A, $p)->subMod($C, $p);
        $D = $D->addMod($D, $p);                       // 2*((X+B)^2 - A - C)
        $E = $A->addMod($A, $p)->addMod($A, $p);       // 3*A
        $F = $E->mulMod($E, $p);                       // E^2
        $X3 = $F->subMod($D, $p)->subMod($D, $p);      // F - 2D
        $C8 = $C->addMod($C, $p); $C8 = $C8->addMod($C8, $p); $C8 = $C8->addMod($C8, $p); // 8C
        $Y3 = $E->mulMod($D->subMod($X3, $p), $p)->subMod($C8, $p);
        $Z3 = $Y->mulMod($Z, $p);
        $Z3 = $Z3->addMod($Z3, $p);                    // 2*Y*Z
        return [$X3, $Y3, $Z3];
    }

    private static function jacobianAdd(array $a, array $b): array
    {
        [$X1, $Y1, $Z1] = $a;
        [$X2, $Y2, $Z2] = $b;
        $p = self::$p;
        if ($Z1->isZero()) {
            return $b;
        }
        if ($Z2->isZero()) {
            return $a;
        }
        $Z1Z1 = $Z1->mulMod($Z1, $p);
        $Z2Z2 = $Z2->mulMod($Z2, $p);
        $U1 = $X1->mulMod($Z2Z2, $p);
        $U2 = $X2->mulMod($Z1Z1, $p);
        $S1 = $Y1->mulMod($Z2->mulMod($Z2Z2, $p), $p);
        $S2 = $Y2->mulMod($Z1->mulMod($Z1Z1, $p), $p);
        if ($U1->equals($U2)) {
            if (!$S1->equals($S2)) {
                return [BigInt::fromInt(1), BigInt::fromInt(1), BigInt::zero()];
            }
            return self::jacobianDouble($a);
        }
        $H = $U2->subMod($U1, $p);
        $R = $S2->subMod($S1, $p);
        $H2 = $H->mulMod($H, $p);
        $H3 = $H2->mulMod($H, $p);
        $U1H2 = $U1->mulMod($H2, $p);
        $X3 = $R->mulMod($R, $p)->subMod($H3, $p)->subMod($U1H2, $p)->subMod($U1H2, $p);
        $Y3 = $R->mulMod($U1H2->subMod($X3, $p), $p)->subMod($S1->mulMod($H3, $p), $p);
        $Z3 = $H->mulMod($Z1->mulMod($Z2, $p), $p);
        return [$X3, $Y3, $Z3];
    }

    /** k * point (point в affine [x,y]) → Jacobian. */
    private static function scalarMult(BigInt $k, BigInt $px, BigInt $py): array
    {
        $result = [BigInt::fromInt(1), BigInt::fromInt(1), BigInt::zero()]; // ∞
        $addend = [$px, $py, BigInt::fromInt(1)];
        $bits = $k->bitLength();
        $hex = $k->toHex();
        // обходим биты от младшего к старшему
        $kk = $k;
        for ($i = 0; $i < $bits; $i++) {
            if (self::testBit($kk, $i)) {
                $result = self::jacobianAdd($result, $addend);
            }
            $addend = self::jacobianDouble($addend);
        }
        return $result;
    }

    private static function testBit(BigInt $x, int $i): bool
    {
        // бит i через hex: дорого, но вызывается ~256 раз — приемлемо.
        // Реализуем через целочисленную проверку младших лимбов невозможно
        // (нет доступа), поэтому используем сдвиг hex-строкой один раз.
        // Для производительности кэшируем биты.
        static $cacheKey = null;
        static $cacheBits = null;
        $hex = $x->toHex();
        if ($cacheKey !== $hex) {
            $cacheKey = $hex;
            $cacheBits = self::hexToBits($hex);
        }
        return ($cacheBits[$i] ?? 0) === 1;
    }

    private static function hexToBits(string $hex): array
    {
        $bits = [];
        // от младшего нибла к старшему
        for ($i = strlen($hex) - 1; $i >= 0; $i--) {
            $nib = hexdec($hex[$i]);
            $bits[] = $nib & 1;
            $bits[] = ($nib >> 1) & 1;
            $bits[] = ($nib >> 2) & 1;
            $bits[] = ($nib >> 3) & 1;
        }
        return $bits;
    }

    private static function toAffine(array $pt): array
    {
        [$X, $Y, $Z] = $pt;
        $p = self::$p;
        if ($Z->isZero()) {
            throw new \RuntimeException('toAffine: точка на бесконечности');
        }
        $zInv = $Z->invMod($p);
        $zInv2 = $zInv->mulMod($zInv, $p);
        $zInv3 = $zInv2->mulMod($zInv, $p);
        return [$X->mulMod($zInv2, $p), $Y->mulMod($zInv3, $p)];
    }

    // ----------------------------------------------------------- публичный ключ

    /** Приватный ключ (hex/0x) → несжатый публичный ключ (64 байта: X||Y). */
    public static function privateToPublic(string $privHex): string
    {
        self::init();
        $d = self::normalizePriv($privHex);
        [$x, $y] = self::toAffine(self::scalarMult($d, self::$gx, self::$gy));
        return $x->toBytesBE(32) . $y->toBytesBE(32);
    }

    /** Ethereum-адрес (0x..., нижний регистр) из приватного ключа. */
    public static function privateToAddress(string $privHex): string
    {
        $pub = self::privateToPublic($privHex);
        $hash = Keccak::hash256($pub);            // keccak256 от 64-байтового pubkey
        return '0x' . bin2hex(substr($hash, 12)); // последние 20 байт
    }

    // ----------------------------------------------------------------- подпись

    /**
     * ECDSA-подпись 32-байтового дайджеста.
     * @return array{r:string,s:string,v:int} r/s — hex с 0x, v — 27|28
     */
    public static function sign(string $digest32, string $privHex): array
    {
        self::init();
        $n = self::$n;
        $d = self::normalizePriv($privHex);
        $z = BigInt::fromBytesBE($digest32);
        // z приводим в диапазон порядка (для 256-битной кривой обычно без изменений)
        $zr = $z->cmp($n) >= 0 ? $z->mod($n) : $z;

        foreach (self::rfc6979Nonces($d, $digest32, $n) as $k) {
            if ($k->isZero() || $k->cmp($n) >= 0) {
                continue;
            }
            [$rx, $ry] = self::toAffine(self::scalarMult($k, self::$gx, self::$gy));
            $r = $rx->cmp($n) >= 0 ? $rx->mod($n) : $rx;
            if ($r->isZero()) {
                continue;
            }
            // s = k^-1 (z + r*d) mod n
            $kInv = $k->invMod($n);
            $s = $kInv->mulMod($zr->addMod($r->mulMod($d, $n), $n), $n);
            if ($s->isZero()) {
                continue;
            }
            // recovery id: чётность Ry (+ перенос если rx>=n, практически никогда)
            $recId = ($ry->isEven() ? 0 : 1);
            if ($rx->cmp($n) >= 0) {
                $recId |= 2;
            }
            // low-s нормализация
            if ($s->cmp(self::$halfN) > 0) {
                $s = $n->sub($s);
                $recId ^= 1;
            }
            return [
                'r' => '0x' . ltrim($r->toHex(), '0'),
                's' => '0x' . ltrim($s->toHex(), '0'),
                'v' => 27 + ($recId & 1),
            ];
        }
        throw new \RuntimeException('ECDSA: не удалось подобрать nonce');
    }

    // ------------------------------------------------------------- RFC 6979

    /**
     * Генератор детерминированных nonce по RFC 6979 (HMAC-SHA256).
     * @return \Generator<BigInt>
     */
    private static function rfc6979Nonces(BigInt $d, string $h1, BigInt $n): \Generator
    {
        $holen = 32;
        $rolen = 32; // qlen/8 для secp256k1
        $x = $d->toBytesBE($rolen);
        // bits2octets(h1): z2 = (int(h1) mod n), 32 байта BE
        $z1 = BigInt::fromBytesBE($h1);
        $z2 = $z1->cmp($n) >= 0 ? $z1->mod($n) : $z1;
        $bx = $x . $z2->toBytesBE($rolen);

        $V = str_repeat("\x01", $holen);
        $K = str_repeat("\x00", $holen);
        $K = hash_hmac('sha256', $V . "\x00" . $bx, $K, true);
        $V = hash_hmac('sha256', $V, $K, true);
        $K = hash_hmac('sha256', $V . "\x01" . $bx, $K, true);
        $V = hash_hmac('sha256', $V, $K, true);

        for ($guard = 0; $guard < 1000; $guard++) {
            $T = '';
            while (strlen($T) < $rolen) {
                $V = hash_hmac('sha256', $V, $K, true);
                $T .= $V;
            }
            $k = BigInt::fromBytesBE(substr($T, 0, $rolen));
            yield $k;
            // если потребитель попросит ещё — обновляем K,V
            $K = hash_hmac('sha256', $V . "\x00", $K, true);
            $V = hash_hmac('sha256', $V, $K, true);
        }
    }

    // ---------------------------------------------------------------- утилиты

    private static function normalizePriv(string $privHex): BigInt
    {
        $h = strtolower($privHex);
        if (str_starts_with($h, '0x')) {
            $h = substr($h, 2);
        }
        if (strlen($h) !== 64) {
            // допускаем короче (ведущие нули), но не длиннее
            if (strlen($h) > 64) {
                throw new \InvalidArgumentException('Secp256k1: приватный ключ длиннее 32 байт');
            }
        }
        return BigInt::fromHex($h);
    }
}
