Web PHP

Code sample for verifying wallet signature and wallet address.

PHP environment:
version > 7.2
extension support: --enable-sodium  Enable "php-sodium-ext" 
<?php
class CryptoEd25519Verify
{
    const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
    // Copy for displaying on wallet signature page, allowing customization
    const SPRINTF_STR = "Welcome to %s!\n\nClick to sign in and accept the %s Terms of Service: %s\n\nThis request will not trigger a blockchain transaction or cost any gas fees.\n\nNonce:%s";
    const COMPANY_NAME = "Your Project Name";                   // The project name used to display on the wallet signature page
    const SERVICE_DOMAIN = "https://www.youdomain.com";           // Current website address for services provided
    const CHECK_TIME = false;

    /**
     * getSignMsg
     * @return string sign message str
     */
    public function getSignMsg()
    {
        return rawurlencode(sprintf(self::SPRINTF_STR, self::COMPANY_NAME, self::COMPANY_NAME, self::SERVICE_DOMAIN, time()));
    }

    /**
     * checkSign
     * @param string $signature base58 format
     * @param string $message  base58 format
     * @param string $publicKey  base58 format
     * @return string address || empty string
     */
    public function checkSign($signature, $message, $publicKey)
    {
        if (empty($signature) || empty($message) || empty($publicKey)) {
            throw new Exception('Parameter error');
        }

        if (!function_exists('sodium_crypto_sign_verify_detached')) {
            throw new Exception('--enable-sodium  Enable "php-sodium-ext" extension support');
        }

        //Verify expiration time
        if (self::CHECK_TIME) {
            $messageText = $this->base58_decode($message);
            $len = mb_strlen($messageText) - 18;
            $nonce = substr($messageText, $len);
            if (mb_strlen($nonce) == 18 && strpos($messageText, 'Nonce%3A') !== false) {
                $clientTime = intval(substr($nonce, 8));
                if (!$this->checkTime($clientTime)) {
                    throw new Exception('Signature Expiration');
                }
            } else {
                throw new Exception('Format error, please keep ending with the string "Nonce%3A" and a timestamp of length 10, like this "Nonce%3A1689054559"');
            }
        }

        $message = $this->base58_decode($message);
        $signature = $this->base58_decode($signature);
        $publicKey = $this->base58_decode($publicKey);

        if (\sodium_crypto_sign_verify_detached($signature, $message, $publicKey)) {
            return $this->base58_encode($publicKey);
        } else {
            return "";
        }
    }

    /**
     * base58_encode
     * @param string str
     * @return string base58 str
     */
    public function base58_encode($string)
    {
        $base = mb_strlen(self::ALPHABET);
        if (is_string($string) === false) {
            return false;
        }
        if (mb_strlen($string) === 0) {
            return '';
        }
        $bytes = array_values(unpack('C*', $string));
        $decimal = $bytes[0];
        for ($i = 1, $l = count($bytes); $i < $l; $i++) {
            $decimal = bcmul($decimal, 256);
            $decimal = bcadd($decimal, $bytes[$i]);
        }
        $output = '';
        while ($decimal >= $base) {
            $div = bcdiv($decimal, $base, 0);
            $mod = bcmod($decimal, $base);
            $output .= self::ALPHABET[$mod];
            $decimal = $div;
        }
        if ($decimal > 0) {
            $output .= self::ALPHABET[$decimal];
        }
        $output = strrev($output);
        foreach ($bytes as $byte) {
            if ($byte === 0) {
                $output = self::ALPHABET[0] . $output;
                continue;
            }
            break;
        }
        return (string) $output;
    }

    /**
     * base58_decode
     * @param string base58
     * @return string
     */
    public function base58_decode($base58)
    {
        $base = strlen(self::ALPHABET);
        if (is_string($base58) === false) {
            return false;
        }
        if (strlen($base58) === 0) {
            return '';
        }
        $indexes = array_flip(str_split(self::ALPHABET));
        $chars = str_split($base58);
        foreach ($chars as $char) {
            if (isset($indexes[$char]) === false) {
                return false;
            }
        }
        $decimal = $indexes[$chars[0]];
        for ($i = 1, $l = count($chars); $i < $l; $i++) {
            $decimal = bcmul($decimal, $base);
            $decimal = bcadd($decimal, $indexes[$chars[$i]]);
        }
        $output = '';
        while ($decimal > 0) {
            $byte = bcmod($decimal, 256);
            $output = pack('C', $byte) . $output;
            $decimal = bcdiv($decimal, 256, 0);
        }
        foreach ($chars as $char) {
            if ($indexes[$char] === 0) {
                $output = "\x00" . $output;
                continue;
            }
            break;
        }
        return $output;
    }

    /**
     * CheckTime Check if the client time is within a certain range
     * @param int $clientTime
     * @return bool
     */
    private function checkTime($clientTime)
    {
        $sysTime = time();
        $time = $sysTime + 120;
        $time2 = $sysTime - 120;
        if ($clientTime < $time && $clientTime > $time2) {
            //Client time is less than system time -60 and client time is greater than system time -60
            return true;
        } else {
            return false;
        }
    }
}


// Example
$signature = '21SNZbbXyhUkN9RXyfKBthzCq8fKA3asV5xcZ6RuQpDcsGRzr6aRreMfrV63jjE5Wc9yx1TzmSuf8h1nvtzSe9Rz';  // Wallet signature string, encoded with base58
$message   = '3Qm87Hq6eP6pgoyMa5SraCsWJFB1HcwoTYWbmvhzhFVBTr7Sbm2NgX7JvZ5DEcEXXirwewpsPzDmLoV8Bz8HmQSv4ZJ1kJzbg2NKhk7HcshtczUssJSrfJosev87EcwpXb5GAjJGJunv96P63n36v9WHjaqkqSiRwRetx8GhGPZ3jVCFphEo96Ciz8oQaggqG9TnxP9P1JiyGKuewJkcWnEXyc581yjEF3iwqEVQcmL8BDoLS9kpa17oLPmiPLNXxUXhUXd8bGUrncQmfhG8yNjNQ44wpacbxMZXrtw4ACrBY6qUBRGPbU469RmmWEenF4R8R4rH9Wb22YmRWUmvBPCDVJCCauLvpjwA6Gapv9Lb5HSMtix3241pxd2JhoqhkWK'; // Base58 encoding of signed data
$publicKey = '7x9XDk1JZTukhN2KQSGqSL1SaoEbGwzdMR3tYLpiJeex';                                              // wallet address  ==> publicKey encoded with base58

$cryptoEd25519 = new CryptoEd25519Verify();
echo $cryptoEd25519->getSignMsg();

try {
    $res = $cryptoEd25519->checkSign($signature, $message, $publicKey);
    if (!empty($res)) {
        echo 'Verification passed, wallet address is: ' . $res;
        //TODO This implements login or registration logic. If there is a return here, it indicates that the wallet address is valid and the real owner
    } else {
        echo 'Verification failed, signature error';
    }
} catch (Exception $e) {
    echo 'Error Message: ' . $e->getMessage();
}

Last updated