<?php
// get_msghash_from_tx.php
// Requires: composer require kornrunner/keccak
require __DIR__ . '/../vendor/autoload.php';
use kornrunner\Keccak;

/**
 * Simple JSON-RPC POST
 */
function rpc_post($rpcUrl, $method, $params = []) {
    $data = json_encode(['jsonrpc'=>'2.0','id'=>1,'method'=>$method,'params'=>$params]);
    $ch = curl_init($rpcUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    $res = curl_exec($ch);
    if ($res === false) throw new Exception('RPC request failed: '.curl_error($ch));
    $arr = json_decode($res, true);
    if (isset($arr['error'])) throw new Exception('RPC error: '.json_encode($arr['error']));
    return $arr['result'] ?? null;
}

/* ---- helpers ---- */
function strip0x($s){ if (!$s) return ''; return (strpos($s,'0x')===0) ? substr($s,2) : $s; }
function is_zero_hex($h){ $h = strip0x($h); $h = ltrim($h, "0"); return $h === ''; }
// convert quantity hex like "0x00" to minimal binary (big-endian), empty string for zero
function qtyHexToBin($hex){
    $h = strip0x($hex);
    $h = preg_replace('/^0+/', '', $h);
    if ($h === '') return '';
    if (strlen($h) % 2) $h = '0' . $h;
    return hex2bin($h);
}
// arbitrary hex data (0x-prefixed) to binary — keep full bytes (don't strip leading zeros)
function dataHexToBin($hex){
    $h = strip0x($hex);
    if ($h === '') return '';
    if (strlen($h) % 2) $h = '0' . $h;
    return hex2bin($h);
}

/* ---- RLP encode ---- */
function rlp_encode_string($input){ // $input is binary string
    $len = strlen($input);
    if ($len === 1 && ord($input) < 0x80) {
        return $input;
    } elseif ($len <= 55) {
        return chr(0x80 + $len) . $input;
    } else {
        $lenBin = to_binary_length($len);
        return chr(0xb7 + strlen($lenBin)) . $lenBin . $input;
    }
}
function rlp_encode_list($items){ // $items are already binary strings (encoded)
    $payload = implode('', $items);
    $len = strlen($payload);
    if ($len <= 55) {
        return chr(0xc0 + $len) . $payload;
    } else {
        $lenBin = to_binary_length($len);
        return chr(0xf7 + strlen($lenBin)) . $lenBin . $payload;
    }
}
function to_binary_length($len){
    $hex = dechex($len);
    if (strlen($hex) % 2) $hex = '0'.$hex;
    return hex2bin($hex);
}

/* ---- encode AccessList (EIP-2930/EIP-1559) ----
AccessList is array of {address, storageKeys[]}
It is encoded as list of [address (20 bytes), list_of_storageKeys (each 32 bytes)]
*/
function encodeAccessList($accessList){
    $out = [];
    foreach ($accessList as $entry) {
        $addrBin = dataHexToBin($entry['address'] ?? '');
        // storage keys
        $keysEnc = [];
        if (!empty($entry['storageKeys']) && is_array($entry['storageKeys'])) {
            foreach ($entry['storageKeys'] as $k) {
                $kb = dataHexToBin($k);
                $keysEnc[] = rlp_encode_string($kb);
            }
        }
        $out[] = rlp_encode_list([ rlp_encode_string($addrBin), rlp_encode_list($keysEnc) ]);
    }
    return rlp_encode_list($out);
}

/* ---- build unsigned serialized for legacy / eip155 / eip1559 ---- */
function buildUnsignedLegacy($tx) {
    // legacy or EIP-155 (we will include chainId,0,0 if chainId present)
    $nonce = qtyHexToBin($tx['nonce'] ?? '0x0');
    $gasPrice = qtyHexToBin($tx['gasPrice'] ?? '0x0');
    $gasLimit = qtyHexToBin($tx['gas'] ?? $tx['gasLimit'] ?? '0x0');
    $to = isset($tx['to']) && $tx['to'] !== null ? hex2bin(str_pad(strip0x($tx['to']), 40, '0', STR_PAD_LEFT)) : ''; // empty for contract creation
    $value = qtyHexToBin($tx['value'] ?? '0x0');
    $data = dataHexToBin($tx['input'] ?? $tx['data'] ?? '');

    $fields = [
        rlp_encode_string($nonce),
        rlp_encode_string($gasPrice),
        rlp_encode_string($gasLimit),
        rlp_encode_string($to),
        rlp_encode_string($value),
        rlp_encode_string($data)
    ];

    // check chainId (rpc may supply chainId)
    if (isset($tx['chainId']) && $tx['chainId'] !== null) {
        $chainBin = qtyHexToBin($tx['chainId']);
        // For EIP-155 unsigned payload: [..., chainId, 0, 0]
        $fields[] = rlp_encode_string($chainBin);
        $fields[] = rlp_encode_string(''); // r = 0
        $fields[] = rlp_encode_string(''); // s = 0
    }

    return rlp_encode_list($fields);
}

function buildUnsigned1559($tx) {
    // fields: [chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList]
    $chainId = qtyHexToBin($tx['chainId'] ?? '0x0');
    $nonce = qtyHexToBin($tx['nonce'] ?? '0x0');
    $maxPriority = qtyHexToBin($tx['maxPriorityFeePerGas'] ?? '0x0');
    $maxFee = qtyHexToBin($tx['maxFeePerGas'] ?? '0x0');
    $gasLimit = qtyHexToBin($tx['gas'] ?? $tx['gasLimit'] ?? '0x0');
    $to = isset($tx['to']) && $tx['to'] !== null ? hex2bin(str_pad(strip0x($tx['to']), 40, '0', STR_PAD_LEFT)) : '';
    $value = qtyHexToBin($tx['value'] ?? '0x0');
    $data = dataHexToBin($tx['input'] ?? $tx['data'] ?? '');

    $fields = [
        rlp_encode_string($chainId),
        rlp_encode_string($nonce),
        rlp_encode_string($maxPriority),
        rlp_encode_string($maxFee),
        rlp_encode_string($gasLimit),
        rlp_encode_string($to),
        rlp_encode_string($value),
        rlp_encode_string($data),
    ];

    // accessList
    $accessList = $tx['accessList'] ?? [];
    $fields[] = encodeAccessList($accessList);

    return rlp_encode_list($fields);
}

/* ---- main handler ---- */
$err = null;
$msgHashHex = null;
$tx = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $rpc = trim($_POST['rpc'] ?? '');
    $txHash = trim($_POST['txhash'] ?? '');
    if ($rpc === '' || $txHash === '') {
        $err = 'لطفاً RPC URL و txHash را وارد کنید.';
    } else {
        try {
            $tx = rpc_post($rpc, 'eth_getTransactionByHash', [$txHash]);
            if (!$tx) throw new Exception('Transaction not found from RPC.');
            // determine type
            $type = $tx['type'] ?? null; // may be "0x2" or null
            // Some providers set tx['chainId'] numeric; ensure hex string if needed
            // Build unsigned serialized bytes (binary)
            if ($type === '0x2' || $type === 2 || (isset($tx['maxFeePerGas']) || isset($tx['maxPriorityFeePerGas']))) {
                // EIP-1559
                $unsignedRlp = buildUnsigned1559($tx); // binary RLP
                $serialized = chr(0x02) . $unsignedRlp;
                $hash = Keccak::hash($serialized, 256); // returns hex string (no 0x)
                $msgHashHex = '0x' . $hash;
            } else {
                // legacy or EIP-155
                $unsignedRlp = buildUnsignedLegacy($tx);
                $serialized = $unsignedRlp;
                $hash = Keccak::hash($serialized, 256);
                $msgHashHex = '0x' . $hash;
            }
        } catch (Exception $e) {
            $err = 'خطا: ' . $e->getMessage();
        }
    }
}
?>
<!doctype html>
<html lang="fa">
<head>
<meta charset="utf-8">
<title>استخراج message hash از txHash (PHP)</title>
<style>body{font-family:Tahoma;padding:18px;direction:rtl}label{display:block;margin-top:10px}input{width:100%;padding:8px;font-family:monospace}button{padding:8px 12px;margin-top:10px}pre{background:#f7f7f7;padding:10px;border-radius:6px}</style>
</head>
<body>
<h2>استخراج message hash از txHash</h2>
<p class="small">این ابزار از RPC JSON (eth_getTransactionByHash) استفاده می‌کند و بر اساس نوع تراکنش، پیامِ امضا‌شده (message hash) را محاسبه می‌کند.</p>

<form method="post">
<label>RPC URL (مثال: https://mainnet.infura.io/v3/YOUR_KEY)</label>
<input name="rpc" value="<?php echo htmlspecialchars($_POST['rpc'] ?? '') ?>">

<label>Transaction Hash (txHash)</label>
<input name="txhash" value="<?php echo htmlspecialchars($_POST['txhash'] ?? '') ?>">

<button type="submit">استخراج message hash</button>
</form>

<?php if ($err): ?>
    <div style="color:#900;margin-top:12px"><strong>خطا:</strong> <?php echo htmlspecialchars($err); ?></div>
<?php endif; ?>

<?php if ($msgHashHex): ?>
    <div style="margin-top:12px">
        <h3>نتیجه</h3>
        <p><strong>نوع تراکنش:</strong> <?php echo htmlspecialchars($tx['type'] ?? 'legacy / not specified'); ?></p>
        <p><strong>Message hash (hex):</strong></p>
        <pre><?php echo $msgHashHex; ?></pre>

        <p class="small">توضیح: این مقدار همان keccak256(serialized unsigned transaction) است. برای بازیابی public key از r,s,v باید از همین مقدار استفاده شود.</p>
    </div>

    <div style="margin-top:12px">
        <h4>توضیحات فنی</h4>
        <ul>
            <li>برای تراکنش‌های EIP-1559 (type=0x2) مقدار امضا روی: <code>0x02 || RLP([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList])</code> محاسبه می‌شود.</li>
            <li>برای تراکنش‌های legacy/EIP-155 اگر <code>chainId</code> موجود باشد، unsigned RLP همان <code>[nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0]</code> است وگرنه <code>[nonce, gasPrice, gasLimit, to, value, data]</code>.</li>
        </ul>
    </div>
<?php endif; ?>

</body>
</html>
