Подскажите декодер raw транзакции eth

Перерыл кучу скриптов, так и не смог подобрать. Можете подсказать нормальную бибилу или код. Мне нужен декодер raw транзакции для eth на PHP именно. Может кто сталкивался, но я почему то я 2 дня ковыряю, но решение пока не нашел. В общем основное это получить адрес from из transactionRaw, отсылаемой командой eth_sendRawTransaction. Заранее балгодарен. Вот код который у меня есть, но в итоге получаю не тот адрес form:

<?php
// methods/eth_sendRawTransaction.php

require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../config/JKSNetwork.php';

use FurqanSiddiqui\ECDSA\Curves\Secp256k1;
use JKCNetwork\JKSNetwork;
use FurqanSiddiqui\Ethereum\Ethereum;
use FurqanSiddiqui\Ethereum\Transactions\LegacyTx;
use Charcoal\Buffers\Buffer;
use kornrunner\Signature\Signature as KSignature;
use kornrunner\Secp256k1 as KSecp256k1;
use kornrunner\Keccak;
use kornrunner\RLP\RLP;

// === Получение rawTx из параметров RPC ===
$rawTx = $params[0] ?? null;
if (!$rawTx) {
    throw new Exception("Missing raw transaction");
}

// === Инициализация сети
$ecc = new Secp256k1();
$network = JKSNetwork::JKSnet();
$eth = new Ethereum($ecc, $network);

// === Декодирование raw-транзакции ===
$buffer = Buffer::fromBase16(ltrim($rawTx, '0x'));
$tx = LegacyTx::DecodeRawTransaction($eth, $buffer);

// --- Извлечение данных подписи и рековерри ---
$r = $tx->signatureR;
$s = $tx->signatureS;
$v = $tx->signatureV;

if (!ctype_xdigit($r)) $r = bin2hex($r);
if (!ctype_xdigit($s)) $s = bin2hex($s);

// recovery param (и chainId, если есть)
if ($v >= 35) {
    $chainId = intval(($v - 35) / 2);
    $recoveryId = ($v - 35) % 2;
} else {
    $chainId = null;
    $recoveryId = $v - 27;
}

// === КОРРЕКТНО формируем msgHash по EIP-155 ===
$nonce    = ltrim(eth_field_to_string($tx->nonce), '0') ?: '0';
$gasPrice = ltrim(eth_field_to_string($tx->gasPrice), '0') ?: '0';
$gasLimit = ltrim(eth_field_to_string($tx->gasLimit), '0') ?: '0';
$to       = (string)$tx->to;
$value    = ltrim(eth_field_to_string($tx->value), '0') ?: '0';
$data     = $tx->data ?: '';

if ($chainId !== null) {
    $rlpTx = [
        $nonce,
        $gasPrice,
        $gasLimit,
        $to,
        $value,
        $data,
        $chainId,
        0,
        0
    ];
} else {
    $rlpTx = [
        $nonce,
        $gasPrice,
        $gasLimit,
        $to,
        $value,
        $data
    ];
}

var_dump(        $nonce,
        $gasPrice,
        $gasLimit,
        $to,
        $value,
        $data,
        $chainId,);

$rlp = new RLP();
$rlpEncoded = $rlp->encode($rlpTx);
//var_dump($rlpEncoded);
$msgHash = Keccak::hash($rlpEncoded, 256);
//var_dump($r, $s, $v);

// --- Восстановление публичного ключа отправителя ---
$signature = new KSignature(gmp_init($r, 16), gmp_init($s, 16), $recoveryId);
$secp256k1 = new KSecp256k1();
$pubKeyPoint = $secp256k1->recoverPublicKey($msgHash, $signature, $recoveryId);
$x = gmp_strval($pubKeyPoint->getX(), 16);
$y = gmp_strval($pubKeyPoint->getY(), 16);
$pubKey = str_pad($x, 64, '0', STR_PAD_LEFT) . str_pad($y, 64, '0', STR_PAD_LEFT);
$pubKeyBin = hex2bin($pubKey);
$keccak = Keccak::hash($pubKeyBin, 256);
$from = '0x' . substr($keccak, -40);
echo $from;exit;

Ответы (2 шт):

Автор решения: Solt

Код выглядит корректным, а обрабатываемые данные корректны? Может имеет смысл навтыкать var_dump почаще и искать на каком шаге формат некорректный, или даже программных проверок побольше.

require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../config/JKSNetwork.php';

use FurqanSiddiqui\ECDSA\Curves\Secp256k1;
use JKCNetwork\JKSNetwork;
use FurqanSiddiqui\Ethereum\Ethereum;
use FurqanSiddiqui\Ethereum\Transactions\LegacyTx;
use Charcoal\Buffers\Buffer;
use kornrunner\Signature\Signature as KSignature;
use kornrunner\Secp256k1 as KSecp256k1;
use kornrunner\Keccak;
use kornrunner\RLP\RLP;

// === Получение rawTx из параметров RPC ===
$rawTx = $params[0] ?? null;

// Проверка шестнадцатеричного значения
if (!preg_match('/^0x[a-fA-F0-9]+$/', $rawTx)) {
    throw new Exception("Missing raw transaction");
}

// === Инициализация сети
$ecc = new Secp256k1();
$network = JKSNetwork::JKSnet();
$eth = new Ethereum($ecc, $network);

// === Декодирование raw-транзакции ===
$buffer = Buffer::fromBase16(ltrim($rawTx, '0x'));
$tx = LegacyTx::DecodeRawTransaction($eth, $buffer);

// --- Извлечение данных подписи и рековерри ---
$r = $tx->signatureR;
$s = $tx->signatureS;
$v = $tx->signatureV;

if (!ctype_xdigit($r)) $r = bin2hex($r);
if (!ctype_xdigit($s)) $s = bin2hex($s);

// Проверка, на 32-байтные значения
if (strlen($r) !== 64 || strlen($s) !== 64) {
    throw new Exception("Invalid signature length for r or s");
}

// recovery param (и chainId, если есть)
if ($v >= 35) {
    $chainId = intval(($v - 35) / 2);
    //Проверка соответствия сети
    if($chainId !== $network->chainId) {
        throw new Exception("Chain ID mismatch");
    }
    $recoveryId = ($v - 35) % 2;
} elseif($v==27 || $v==28) {
    $chainId = null;
    $recoveryId = $v - 27;
} else{
    //Некорректный v
    throw new Exception("Wrong v");
}

// === КОРРЕКТНО формируем msgHash по EIP-155 ===
$nonce    = ltrim(eth_field_to_string($tx->nonce), '0') ?: '0';
$gasPrice = ltrim(eth_field_to_string($tx->gasPrice), '0') ?: '0';
$gasLimit = ltrim(eth_field_to_string($tx->gasLimit), '0') ?: '0';
$to       = (string)$tx->to;
$value    = ltrim(eth_field_to_string($tx->value), '0') ?: '0';
$data     = $tx->data ?: '';

$rlpTx = ($chainId !== null)
        ? [$nonce, $gasPrice, $gasLimit, $to, $value, $data, $chainId, 0, 0]
        : [$nonce, $gasPrice, $gasLimit, $to, $value, $data];

var_dump($rlpTx);

$rlp = new RLP();
$rlpEncoded = $rlp->encode($rlpTx);
//var_dump($rlpEncoded);
$msgHash = Keccak::hash($rlpEncoded, 256);

// хеш должен быть 32 байта (64 символа)
if (strlen($msgHash) !== 64) {
    throw new Exception("Invalid msgHash");
}

//var_dump($r, $s, $v);

// --- Восстановление публичного ключа отправителя ---
$signature = new KSignature(gmp_init($r, 16), gmp_init($s, 16), $recoveryId);
$secp256k1 = new KSecp256k1();
$pubKeyPoint = $secp256k1->recoverPublicKey($msgHash, $signature, $recoveryId);

//Проверка публичного ключа
if (!$pubKeyPoint) {
    throw new Exception("Failed to recover pubKeyPoint");
}

$x = gmp_strval($pubKeyPoint->getX(), 16);
$y = gmp_strval($pubKeyPoint->getY(), 16);
$pubKey = str_pad($x, 64, '0', STR_PAD_LEFT) . str_pad($y, 64, '0', STR_PAD_LEFT);
$pubKeyBin = hex2bin($pubKey);
$keccak = Keccak::hash($pubKeyBin, 256);
$from = '0x' . substr($keccak, -40);
echo $from;
exit;

Ещё я слышал, что recoveryId может оказаться не таким, как надо и рекомендуют при восстановлении публичного ключа пробовать оба варианта и 0 и 1.

→ Ссылка
Автор решения: Александр Орлов

В итоге я сделал очень пошло, просто поставил Node.js + Ethers.js и из PHP обращаюсь за получением адреса:

// === Получаем адрес from через ethers.js ===
$nodeJsPath = '/usr/bin/node'; // Проверь через `which node` если не уверен
$jsPath = '/var/www/.../node/decode.js';
$cmd = $nodeJsPath . ' ' . escapeshellarg($jsPath) . ' ' . escapeshellarg($rawTx) . ' 2>&1';
$from = trim(shell_exec($cmd));


// decode.js
const { ethers } = require("ethers");
const rawTx = process.argv[2];
if (!rawTx) {
    console.error("No rawTx provided");
    process.exit(1);
}
try {
    const parsed = ethers.utils.parseTransaction(rawTx);
    console.log(parsed.from || "");
} catch (err) {
    console.error("Invalid tx:", err.message);
    process.exit(2);
}
→ Ссылка