Files
hashbro 74a67c3900 init
2026-08-08 05:09:01 +08:00

104 lines
3.2 KiB
PHP

<?php
namespace App\Services\Chain;
use kornrunner\Keccak;
use RuntimeException;
final class TronAddress
{
public static function fromUncompressedPublicKey(string $publicKeyHex): string
{
$hex = strtolower($publicKeyHex);
if (str_starts_with($hex, '0x')) {
$hex = substr($hex, 2);
}
if (str_starts_with($hex, '04')) {
$hex = substr($hex, 2);
}
if (strlen($hex) !== 128) {
throw new RuntimeException('Expected uncompressed secp256k1 public key');
}
$hash = Keccak::hash(hex2bin($hex), 256);
$addrHex = '41'.substr($hash, -40);
return self::hexToBase58Check($addrHex);
}
public static function isValid(string $address): bool
{
if (! preg_match('/^T[1-9A-HJ-NP-Za-km-z]{33}$/', $address)) {
return false;
}
try {
$hex = self::base58CheckToHex($address);
} catch (\Throwable) {
return false;
}
return str_starts_with(strtolower($hex), '41') && strlen($hex) === 42;
}
public static function toHex(string $base58): string
{
return self::base58CheckToHex($base58);
}
public static function hexToBase58Check(string $hex): string
{
$alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
$bin = hex2bin($hex);
if ($bin === false) {
throw new RuntimeException('Invalid hex');
}
$checksum = substr(hash('sha256', hash('sha256', $bin, true), true), 0, 4);
$bytes = $bin.$checksum;
$num = gmp_init(bin2hex($bytes), 16);
$encoded = '';
while (gmp_cmp($num, 0) > 0) {
[$num, $rem] = [gmp_div_q($num, 58), gmp_intval(gmp_mod($num, 58))];
$encoded = $alphabet[$rem].$encoded;
}
for ($i = 0, $len = strlen($bytes); $i < $len && $bytes[$i] === "\x00"; $i++) {
$encoded = '1'.$encoded;
}
return $encoded;
}
public static function base58CheckToHex(string $address): string
{
$alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
$num = gmp_init(0);
for ($i = 0, $len = strlen($address); $i < $len; $i++) {
$pos = strpos($alphabet, $address[$i]);
if ($pos === false) {
throw new RuntimeException('Invalid base58 character');
}
$num = gmp_add(gmp_mul($num, 58), $pos);
}
$hex = gmp_strval($num, 16);
if (strlen($hex) % 2 !== 0) {
$hex = '0'.$hex;
}
// leading ones => leading zero bytes
for ($i = 0, $len = strlen($address); $i < $len && $address[$i] === '1'; $i++) {
$hex = '00'.$hex;
}
$bin = hex2bin($hex);
if ($bin === false || strlen($bin) < 5) {
throw new RuntimeException('Invalid address payload');
}
$payload = substr($bin, 0, -4);
$checksum = substr($bin, -4);
$expected = substr(hash('sha256', hash('sha256', $payload, true), true), 0, 4);
if (! hash_equals($expected, $checksum)) {
throw new RuntimeException('Invalid address checksum');
}
return bin2hex($payload);
}
}