> For the complete documentation index, see [llms.txt](https://wallet-docs.broearn.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wallet-docs.broearn.com/advanced/auth/web-php.md).

# Web PHP

```
PHP environment:
version > 7.2
extension support: --enable-sodium  Enable "php-sodium-ext" 
```

{% code overflow="wrap" %}

```php
<?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();
}
```

{% endcode %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://wallet-docs.broearn.com/advanced/auth/web-php.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
