<?php
// recover_pubkey.php
// Usage: put this file on a PHP server (with ext-gmp) and visit in browser.
// Requires composer package: kornrunner/keccak
// Install: composer require kornrunner/keccak

require __DIR__ . '/../vendor/autoload.php';
use kornrunner\Keccak;

function hexClean($h){
    $h = trim($h);
    if ($h === '') return '';
    if (strpos($h, '0x') === 0) $h = substr($h,2);
    $h = preg_replace('/[^0-9a-fA-F]/', '', $h);
    if ($h === '') return '';
    if (strlen($h) % 2) $h = '0' . $h;
    return strtolower($h);
}

function hex2gmp($h){
    $h = hexClean($h);
    return gmp_init($h === '' ? '0' : $h, 16);
}

function gmp2hex($g){
    $s = gmp_strval($g, 16);
    if (strlen($s) % 2) $s = '0'.$s;
    return strtolower($s);
}

// secp256k1 params
const SECP_P_HEX  = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F';
const SECP_N_HEX  = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141';
const SECP_GX_HEX = '79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798';
const SECP_GY_HEX = '483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8';

$p = hex2gmp(SECP_P_HEX);
$n = hex2gmp(SECP_N_HEX);
$Gx = hex2gmp(SECP_GX_HEX);
$Gy = hex2gmp(SECP_GY_HEX);

// modular inverse (mod m)
function modInverse($a, $m){
    // using gmp_invert if available
    $inv = gmp_invert($a, $m);
    if ($inv === false) return null;
    return $inv;
}

// modular sqrt for p where p % 4 == 3: sqrt(a) = a^((p+1)/4) mod p
function modSqrt($a, $p){
    // assume p % 4 == 3 for secp256k1
    $exp = gmp_div_q(gmp_add($p, gmp_init(1)), gmp_init(4));
    return gmp_powm($a, $exp, $p);
}

// point addition on curve y^2 = x^3 + 7 (mod p)
function pointAdd($P, $Q, $p){
    if ($P === null) return $Q;
    if ($Q === null) return $P;
    list($x1,$y1) = $P;
    list($x2,$y2) = $Q;
    if (gmp_cmp($x1, $x2) == 0 && gmp_cmp($y1, gmp_mod(gmp_neg($y2), $p)) == 0) {
        return null; // P + (-P) = O
    }
    if (gmp_cmp($x1,$x2) == 0 && gmp_cmp($y1,$y2) == 0){
        // point doubling
        $lambda_num = gmp_mul(gmp_init(3), gmp_mul($x1,$x1));
        $lambda_den = gmp_mul(gmp_init(2), $y1);
    } else {
        // general addition
        $lambda_num = gmp_sub($y2, $y1);
        $lambda_den = gmp_sub($x2, $x1);
    }
    $inv = modInverse(gmp_mod($lambda_den, $p), $p);
    if ($inv === null) return null;
    $lambda = gmp_mod(gmp_mul(gmp_mod($lambda_num,$p), $inv), $p);
    $x3 = gmp_mod(gmp_sub(gmp_sub(gmp_mul($lambda,$lambda), $x1), $x2), $p);
    $y3 = gmp_mod(gmp_sub(gmp_mul($lambda, gmp_sub($x1,$x3)), $y1), $p);
    return [$x3, $y3];
}

function pointNeg($P, $p){
    if ($P === null) return null;
    return [$P[0], gmp_mod(gmp_neg($P[1]), $p)];
}

function pointSub($P, $Q, $p){
    return pointAdd($P, pointNeg($Q, $p), $p);
}

// scalar multiplication (double-and-add)
function pointMul($P, $k, $p){
    $k = gmp_init($k);
    $R = null;
    $A = $P;
    while (gmp_cmp($k, 0) > 0){
        if (gmp_cmp(gmp_and($k, gmp_init(1)), 0) != 0){
            $R = pointAdd($R, $A, $p);
        }
        $A = pointAdd($A, $A, $p);
        $k = gmp_div_q($k, gmp_init(2));
    }
    return $R;
}

// recover public key from (r, s, v) and message hash (32 bytes hex)
function recoverPubKeyFromSignature($msgHashHex, $rHex, $sHex, $v){
    global $p, $n, $Gx, $Gy;
    $z = hex2gmp($msgHashHex);
    $r = hex2gmp($rHex);
    $s = hex2gmp($sHex);

    // v might be 27/28 or >=35 (EIP155). normalize to recovery id 0/1/2/3
    $vInt = intval($v);
    if ($vInt >= 35) {
        // EIP-155: recId = v - 35 - 2*chainId  -> but we don't need chainId here if user supplies v as 27/28 or 0/1
        $recId = ($vInt - 35) % 2; // best-effort -> 0 or 1
    } else {
        $recId = $vInt - 27;
    }
    if ($recId < 0 || $recId > 3) {
        return ['error'=>'invalid recovery id'];
    }

    // j = recId // 2  (which 2^? to add n)
    $j = intdiv($recId, 2);

    // x = r + j*n
    $x = gmp_add($r, gmp_mul(gmp_init($j), $n));
    if (gmp_cmp($x, $p) >= 0) {
        return ['error'=>'x >= p, invalid'];
    }

    // compute y from x: y^2 = x^3 + 7 mod p
    $x3 = gmp_mod(gmp_powm($x, 3, $p), $p);
    $alpha = gmp_mod(gmp_add($x3, gmp_init(7)), $p);
    $y = modSqrt($alpha, $p);
    if ($y === null) return ['error'=>'no sqrt (invalid)'];

    // choose correct y based on recId parity
    $yParity = gmp_mod($y, gmp_init(2));
    if (intval($yParity) !== ($recId & 1)) {
        $y = gmp_mod(gmp_neg($y), $p);
    }

    $R = [$x, $y];
    // compute r^{-1} mod n
    $rInv = modInverse($r, $n);
    if ($rInv === null) return ['error'=>'r is not invertible mod n'];

    // Q = r^{-1} ( s * R - z * G )
    $sR = pointMul($R, $s, $p);
    $zG = pointMul([$Gx, $Gy], gmp_mod($z, $n), $p);
    $sR_minus_zG = pointSub($sR, $zG, $p);
    $Q = pointMul($sR_minus_zG, $rInv, $p);

    if ($Q === null) return ['error'=>'computed point at infinity'];

    // return hex pubkey (uncompressed)
    $Qx_hex = gmp2hex($Q[0]);
    $Qy_hex = gmp2hex($Q[1]);
    // pad to 64 bytes each
    $Qx_hex = str_pad($Qx_hex, 64, '0', STR_PAD_LEFT);
    $Qy_hex = str_pad($Qy_hex, 64, '0', STR_PAD_LEFT);
    $pubkey_hex = '04' . $Qx_hex . $Qy_hex;

    // address = last 20 bytes of keccak(pubkey[1:])
    $pubkey_bin = hex2bin($Qx_hex . $Qy_hex);
    $keccak = Keccak::hash($pubkey_bin, 256);
    $address = '0x' . substr($keccak, -40);

    return ['pubkey' => '0x' . $pubkey_hex, 'address' => $address];
}

// ----------------- handle form -----------------
$errors = [];
$result = null;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // fields: msghash, r, s, v
    $msghash = hexClean($_POST['msghash'] ?? '');
    $r = hexClean($_POST['r'] ?? '');
    $s = hexClean($_POST['s'] ?? '');
    $v = trim($_POST['v'] ?? '');

    if ($msghash === '' || $r === '' || $s === '' || $v === '') {
        $errors[] = 'لطفاً همه‌ی فیلدها را پر کنید: message hash (32 بایت)، r، s، v';
    } else {
        // check length of msghash (should be 32 bytes)
        if (strlen($msghash) !== 64) {
            $errors[] = 'message hash باید 32 بایت (64 هگز) باشد. مثال: '.str_repeat('0',64);
        } else {
            $result = recoverPubKeyFromSignature($msghash, $r, $s, $v);
            if (isset($result['error'])) {
                $errors[] = 'خطا در بازیابی: ' . $result['error'];
                $result = null;
            }
        }
    }
}
?>
<!doctype html>
<html lang="fa">
<head>
<meta charset="utf-8">
<title>بازیابی Public Key از r,s,v و message hash (secp256k1)</title>
<style>
body{font-family:Tahoma, Arial; padding:18px; direction:rtl}
label{display:block;margin-top:12px}
input[type=text]{width:100%;padding:8px;font-family:monospace}
textarea{width:100%;height:80px;font-family:monospace}
button{margin-top:10px;padding:8px 14px}
.box{background:#f7f7f7;padding:12px;border-radius:6px}
.err{color:#800}
.ok{color:#060}
.small{font-size:12px;color:#666}
</style>
</head>
<body>
<h2>بازیابی Public Key از امضا (secp256k1)</h2>
<div class="box">
<p class="small">این فرم از شما: <strong>message hash</strong> (همان 32 بایت که امضا شده)، و مقادیر <strong>r</strong> و <strong>s</strong> و <strong>v</strong> را می‌گیرد و کلید عمومی غیر فشرده و آدرس اتریوم را محاسبه می‌کند.</p>
<form method="post">
<label>Message hash (32 bytes hex, بدون 0x)</label>
<input type="text" name="msghash" value="<?php echo htmlspecialchars($_POST['msghash'] ?? '') ?>" placeholder="مثال: 82ff... (64 hex chars)">

<label>r (hex)</label>
<input type="text" name="r" value="<?php echo htmlspecialchars($_POST['r'] ?? '') ?>" placeholder="مثال: 0x1255e1... یا بدون 0x">

<label>s (hex)</label>
<input type="text" name="s" value="<?php echo htmlspecialchars($_POST['s'] ?? '') ?>">

<label>v (decimal)</label>
<input type="text" name="v" value="<?php echo htmlspecialchars($_POST['v'] ?? '') ?>" placeholder="مثال: 27 یا 28 یا 37 یا 38">

<button type="submit">محاسبه Public Key</button>
</form>
</div>

<?php if (!empty($errors)): ?>
    <div class="err"><h4>خطاها:</h4><ul><?php foreach($errors as $e) echo '<li>'.htmlspecialchars($e).'</li>'; ?></ul></div>
<?php endif; ?>

<?php if ($result): ?>
    <div class="box" style="margin-top:12px">
        <h3 class="ok">نتیجه</h3>
        <p><strong>Public key (uncompressed):</strong><br><code><?php echo htmlspecialchars($result['pubkey']); ?></code></p>
        <p><strong>Ethereum address (lowercase):</strong><br><code><?php echo htmlspecialchars($result['address']); ?></code></p>
        <p class="small">اگر می‌خواهید آدرس با EIP-55 checksum نشان داده شود، می‌توانم تابع checksum هم اضافه کنم.</p>
    </div>
<?php endif; ?>

</body>
</html>
