52 lines
1.3 KiB
PHP
52 lines
1.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Chain;
|
|
|
|
use kornrunner\Keccak;
|
|
use RuntimeException;
|
|
|
|
final class EthAddress
|
|
{
|
|
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);
|
|
|
|
return '0x'.substr($hash, -40);
|
|
}
|
|
|
|
public static function isValid(string $address): bool
|
|
{
|
|
return (bool) preg_match('/^0x[0-9a-fA-F]{40}$/', trim($address));
|
|
}
|
|
|
|
public static function normalize(string $address): string
|
|
{
|
|
return strtolower(trim($address));
|
|
}
|
|
|
|
/** 20-byte address without 0x, left-padded to 32 bytes for ABI. */
|
|
public static function toWord(string $address): string
|
|
{
|
|
$hex = self::normalize($address);
|
|
if (str_starts_with($hex, '0x')) {
|
|
$hex = substr($hex, 2);
|
|
}
|
|
if (strlen($hex) !== 40) {
|
|
throw new RuntimeException('Invalid ETH address');
|
|
}
|
|
|
|
return str_pad($hex, 64, '0', STR_PAD_LEFT);
|
|
}
|
|
}
|