<?php

// eth_address_form.php
// Convert secp256k1 Public Key (compressed/uncompressed) -> Ethereum address (EIP-55)
// Requires: ext-gmp and composer package kornrunner/keccak
require __DIR__ . '/../vendor/autoload.php';
use kornrunner\Keccak;

function h($s){ return htmlspecialchars($s ?? '', ENT_QUOTES, 'UTF-8'); }

// ---------- secp256k1 params ----------
const P_HEX = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F'; // p
const B_HEX = '07'; // a=0, b=7

$p = gmp_init(P_HEX, 16);
$b = gmp_init(B_HEX, 16);

// ---------- helpers ----------
function clean_hex($hex): string {
    $hex = trim($hex);
    if (str_starts_with(strtolower($hex), '0x')) $hex = substr($hex, 2);
    $hex = preg_replace('/\s+/', '', $hex);
    if ($hex === '') throw new Exception("ورودی خالی است.");
    if (!preg_match('/^[0-9a-fA-F]+$/', $hex)) throw new Exception("فرمت هگز نامعتبر.");
    if (strlen($hex) % 2 === 1) $hex = '0' . $hex;
    return strtolower($hex);
}
function gmp_from_hex($h){ return gmp_init($h, 16); }
function hex64_from_gmp($n){
    $h = gmp_strval($n, 16);
    return strtoupper(str_pad($h, 64, '0', STR_PAD_LEFT));
}
function is_even(GMP $n): bool { return gmp_intval(gmp_mod($n, 2)) === 0; }
function mod_add(GMP $x, GMP $y, GMP $p): GMP { return gmp_mod(gmp_add($x, $y), $p); }
function mod_mul(GMP $x, GMP $y, GMP $p): GMP { return gmp_mod(gmp_mul($x, $y), $p); }

// sqrt mod p (p % 4 == 3): y = n^((p+1)/4) mod p
function mod_sqrt_secp256k1(GMP $n, GMP $p): ?GMP {
    if (gmp_cmp($n, 0) == 0) return gmp_init(0);
    $exp = gmp_div_q(gmp_add($p, 1), 4);
    $y = gmp_powm($n, $exp, $p);
    if (gmp_cmp(gmp_powm($y, 2, $p), gmp_mod($n, $p)) === 0) return $y;
    return null;
}

function decompress_xy_from_compressed(string $prefix, string $x_hex, GMP $p, GMP $b): array {
    $x = gmp_from_hex($x_hex);
    $x2 = mod_mul($x, $x, $p);
    $x3 = mod_mul($x2, $x, $p);
    $rhs = mod_add($x3, $b, $p);           // y^2 = x^3 + 7 (mod p)
    $y = mod_sqrt_secp256k1($rhs, $p);
    if ($y === null) throw new Exception("ریشهٔ مربّع مدولار یافت نشد (pubkey نامعتبر).");
    $y_alt = gmp_sub($p, $y);
    $want_even = ($prefix === '02');
    $y_final = (is_even($y) === $want_even) ? $y : $y_alt;
    return [$x, $y_final];
}

function eip55_checksum(string $addr40_hex): string {
    $lower = strtolower($addr40_hex);
    $hash = Keccak::hash($lower, 256); // hex
    $out = '';
    for ($i=0; $i<40; $i++){
        $c = $lower[$i];
        if ($c >= 'a' && $c <= 'f') {
            $nib = hexdec($hash[$i]);
            $out .= ($nib >= 8) ? strtoupper($c) : $c;
        } else {
            $out .= $c;
        }
    }
    return '0x' . $out;
}

function pubkey_to_eth(string $pubkey_hex, GMP $p, GMP $b): array {
    $hex = clean_hex($pubkey_hex);
    $len = strlen($hex);

    $x_hex = $y_hex = null;

    if ($len === 130 && substr($hex,0,2)==='04') {
        // 04 + X(64) + Y(64)
        $x_hex = substr($hex, 2, 64);
        $y_hex = substr($hex, 66, 64);
    } elseif ($len === 66 && (substr($hex,0,2)==='02' || substr($hex,0,2)==='03')) {
        // compressed: decompress to XY
        $prefix = substr($hex,0,2);
        $x_hex  = substr($hex, 2, 64);
        [$x,$y] = decompress_xy_from_compressed($prefix, $x_hex, $p, $b);
        $x_hex = hex64_from_gmp($x);
        $y_hex = hex64_from_gmp($y);
    } elseif ($len === 128) {
        // assume X||Y without 04
        $x_hex = substr($hex, 0, 64);
        $y_hex = substr($hex, 64, 64);
    } else {
        throw new Exception("طول/فرمت pubkey پشتیبانی نمی‌شود. ورودی باید 33 بایت (02/03+X) یا 65 بایت (04+X+Y) باشد.");
    }

    // 64 بایت (X||Y) به صورت باینری
    $xy_bin = hex2bin($x_hex . $y_hex);
    if ($xy_bin === false) throw new Exception("تبدیل هگز به باینری ناکام.");

    // Keccak-256 روی X||Y (بدون 0x04)
    $hash_hex = Keccak::hash($xy_bin, 256);
    $eth_hex40 = substr($hash_hex, -40);   // 20 bytes (40 hex)
    $eth_raw   = '0x' . $eth_hex40;
    $eth_eip55 = eip55_checksum($eth_hex40);

return [
    'x_hex'      => strtoupper($x_hex),
    'y_hex'      => strtoupper($y_hex),
    'x_dec'      => gmp_strval(gmp_from_hex($x_hex), 10),
    'y_dec'      => gmp_strval(gmp_from_hex($y_hex), 10),
    'keccak256'  => strtoupper($hash_hex), // 🔹 اضافه شد
    'eth_raw'    => $eth_raw,
    'eth_eip55'  => $eth_eip55,
    'uncompressed'=> '04' . strtoupper($x_hex . $y_hex),
    'compressed' => ( (hexdec(substr($y_hex, -1)) % 2 === 0) ? '02' : '03' ) . strtoupper($x_hex),
];
}

// ---------- UI ----------
$sample_compressed =
    '02'.'79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798'; // Gx (نمونه)
$sample_uncompressed =
    '04'.'D11F9D1D5D59F172F6F0467A0B3B0D6A9B7E2C84307EDE1F0C9D70D7D5C1C2E1'.'3C3C3B8B9E1F2A3B4C5D6E7F8091A2B3C4D5E6F708192A3B4C5D6E7F8091A2B3'; // صرفاً نمونهٔ ساختگی

$result = null;
$error  = null;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $in = $_POST['pubkey'] ?? '';
    try {
        if (!extension_loaded('gmp')) {
            throw new Exception("افزونهٔ PHP GMP فعال نیست.");
        }
        if (!class_exists(Keccak::class)) {
            throw new Exception("پکیج kornrunner/keccak نصب/لود نشده است.");
        }
        $result = pubkey_to_eth($in, $p, $b);
    } catch (Throwable $e) {
        $error = $e->getMessage();
    }
}
?>
<!doctype html>
<html lang="fa" dir="rtl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Public Key → Ethereum Address (EIP-55)</title>
<style>
  :root{--bg:#0f172a;--card:#111827;--fg:#e5e7eb;--muted:#9ca3af;--acc:#22c55e;}
  *{box-sizing:border-box}
  body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto;background:var(--bg);color:var(--fg)}
  .wrap{max-width:980px;margin:40px auto;padding:16px}
  .card{background:var(--card);border:1px solid #1f2937;border-radius:16px;padding:24px;box-shadow:0 10px 30px rgba(0,0,0,.25)}
  h1{margin:0 0 8px;font-size:22px}
  p.sub{color:var(--muted);margin-top:0}
  label{display:block;margin-top:16px;font-weight:700}
  textarea{width:100%;min-height:110px;padding:12px 14px;border-radius:12px;border:1px solid #334155;background:#0b1220;color:var(--fg);font-family:monospace}
  .btn{margin-top:18px;background:var(--acc);color:#052e12;font-weight:800;border:none;padding:12px 16px;border-radius:12px;cursor:pointer}
  .btn:hover{filter:brightness(1.1)}
  .out{margin-top:22px;padding:16px;background:#0b1220;border:1px dashed #334155;border-radius:12px;font-family:monospace;overflow:auto}
  .grid{display:grid;grid-template-columns:220px 1fr;gap:8px;align-items:center}
  .kv{display:contents}
  .err{background:#3b0d0d;color:#fecaca;border:1px solid #7f1d1d;padding:12px 14px;border-radius:12px;margin-top:14px}
  code{background:#0b1220;padding:2px 6px;border-radius:6px;border:1px solid #334155}
  .muted{color:var(--muted)}
  .copy{float:left;margin-top:10px;font-size:12px;color:#86efac;cursor:pointer}
</style>
</head>
<body>
  <div class="wrap">
    <div class="card">
      <h1>تبدیل کلید عمومی به آدرس اتریوم (EIP-55)</h1>
      <p class="sub">کلید عمومی را به صورت هگز وارد کنید: <code>02/03 + X</code> (فشرده، ۳۳ بایت) یا <code>04 + X + Y</code> (غیرفشرده، ۶۵ بایت). آدرس از Keccak-256 روی <strong>X||Y</strong> (بدون ۰۴) تولید می‌شود.</p>

      <form method="post" action="">
        <label for="pubkey">Public Key (Hex)</label>
        <textarea id="pubkey" name="pubkey" placeholder="مثال: <?=$sample_compressed?>"><?=
          h($_POST['pubkey'] ?? $sample_compressed)
        ?></textarea>
        <button class="btn" type="submit">تبدیل به آدرس اتریوم</button>
      </form>

      <?php if ($error): ?>
        <div class="err">خطا: <?=h($error)?></div>
      <?php endif; ?>

      <?php if ($result && !$error): ?>
        <div class="out">
          <div class="grid">
            <div class="kv"><strong>X (hex 64b):</strong><div><?=h($result['x_hex'])?></div></div>
            <div class="kv"><strong>Y (hex 64b):</strong><div><?=h($result['y_hex'])?></div></div>
            <div class="kv"><strong>X (decimal):</strong><div style="white-space:pre-wrap"><?=h($result['x_dec'])?></div></div>
            <div class="kv"><strong>Y (decimal):</strong><div style="white-space:pre-wrap"><?=h($result['y_dec'])?></div></div>
            <div class="kv"><strong>Compressed (rebuild):</strong><div><?=h($result['compressed'])?></div></div>
            <div class="kv"><strong>Uncompressed (rebuild):</strong><div style="word-break:break-all"><?=h($result['uncompressed'])?></div></div>
            <div class="kv"><strong>Keccak-256 (X||Y):</strong><div style="word-break:break-all"><?=h($result['keccak256'])?></div></div>
          
            <div class="kv"><strong>ETH (raw):</strong><div id="raw"><?=h($result['eth_raw'])?></div></div>
            <div class="kv"><strong>ETH (EIP-55):</strong><div id="eip55"><?=h($result['eth_eip55'])?></div></div>
          </div>
          <span class="copy" onclick="copyAll()">کپی آدرس‌ها</span>
        </div>
        <p class="muted">توجه: EIP-55 فقط نمایش checksummed است؛ مقدار واقعی آدرس همان ۲۰ بایت پایانی Keccak-256 از X||Y است.</p>
      <?php endif; ?>
    </div>
  </div>

<script>
function copyAll(){
  const raw = document.getElementById('raw')?.innerText || '';
  const e55 = document.getElementById('eip55')?.innerText || '';
  const text = `ETH raw: ${raw}\nETH EIP-55: ${e55}\n`;
  navigator.clipboard.writeText(text).then(()=>alert('کپی شد!'));
}
</script>
</body>
</html>
