Подскажите декодер 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 шт):
Код выглядит корректным, а обрабатываемые данные корректны? Может имеет смысл навтыкать 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);
}