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