如何在PHP中正确验证TON Proof?当前实现签名错误
PHP实现TON Proof验证的正确方案
问题描述
我需要用PHP验证TON Proof,官方给出的验证方案如下:
message = utf8_encode("ton-proof-item-v2/") ++ Address ++ AppDomain ++ Timestamp ++ Payload signature = Ed25519Sign(privkey, sha256(0xffff ++ utf8_encode("ton-connect") ++ sha256(message)))
其中各字段要求:
Address:钱包地址,需编码为:workchain:32位有符号大端序整数;hash:256位无符号大端序整数;
AppDomain:格式为Length ++ EncodedDomainNameLength:UTF-8编码的应用域名的字节长度,32位数值EncodedDomainName:长度为Length字节的UTF-8编码应用域名
Timestamp:签名操作的64位Unix时间戳Payload:可变长度二进制字符串。
我写了这段代码,但生成的签名不正确:
$pubKey = "05264d80c7ad5a953cf56cdecf68ec******"; $address = "0:312ed56825e7455a7948c34677e418e6c371d263c0fd7*****"; $payload = "d0143e296879ed8f0000000064e328d8d751ea7c3c44827c8b6fcd5c83efcbfa"; $domainLengthBytes = 21; $domainValue = "ton-connect.github.io"; $timestamp = 1692608455; $message = utf8_encode("ton-proof-item-v2/").$address.$domainLengthBytes.$domainValue.$timestamp.$payload; $signature = sodium_crypto_sign_detached($message,$pubKey); print_r($message);
错误分析
你的代码存在几个核心问题:
- 地址处理错误:直接拼接字符串格式的地址,没有按要求将workchain和hash编码为大端序二进制数据
- AppDomain格式错误:直接拼接数字长度,没有将长度编码为32位大端序二进制
- Timestamp格式错误:直接拼接数字,没有编码为64位大端序二进制
- Payload处理错误:直接拼接十六进制字符串,没有转换为二进制数据
- 签名流程错误:没有按照官方要求对message做两次SHA256嵌套哈希,也没有添加前缀
0xffff ++ utf8_encode("ton-connect") - 密钥格式错误:sodium库要求密钥是二进制格式,不是十六进制字符串
正确实现步骤
- 解析并编码钱包地址:将
0:xxxx格式的地址拆分为workchain和hash,分别编码为32位有符号大端序、256位无符号大端序二进制 - 编码AppDomain:计算域名的UTF-8字节长度,将长度编码为32位大端序二进制,再拼接域名的UTF-8二进制数据
- 编码Timestamp:将Unix时间戳编码为64位大端序二进制
- 转换Payload:将十六进制字符串的Payload转换为二进制数据
- 组装原始message:按官方顺序拼接所有二进制部分
- 计算嵌套哈希:先对message做SHA256,再拼接
0xffff、ton-connect的UTF-8编码,再做一次SHA256 - 验证签名:使用sodium库的
crypto_sign_verify_detached方法,传入签名、哈希后的内容和二进制公钥
完整代码实现
<?php // 输入参数 $pubKeyHex = "05264d80c7ad5a953cf56cdecf68ec******"; // 替换为实际公钥十六进制 $addressStr = "0:312ed56825e7455a7948c34677e418e6c371d263c0fd7*****"; // 替换为实际地址 $payloadHex = "d0143e296879ed8f0000000064e328d8d751ea7c3c44827c8b6fcd5c83efcbfa"; // 替换为实际Payload十六进制 $domain = "ton-connect.github.io"; $timestamp = 1692608455; $signatureHex = ""; // 替换为钱包返回的签名十六进制 // 1. 处理公钥:十六进制转二进制 $pubKey = hex2bin($pubKeyHex); // 2. 处理地址:拆分workchain和hash并编码 list($workchainStr, $hashHex) = explode(':', $addressStr); $workchain = (int)$workchainStr; // 32位有符号大端序 $workchainBin = pack('N', $workchain); // 256位无符号大端序(32字节) $hashBin = hex2bin($hashHex); $addressBin = $workchainBin . $hashBin; // 3. 处理AppDomain $domainUtf8 = utf8_encode($domain); $domainLen = strlen($domainUtf8); // 32位大端序长度 $domainLenBin = pack('N', $domainLen); $appDomainBin = $domainLenBin . $domainUtf8; // 4. 处理Timestamp:64位大端序 $timestampBin = pack('J', $timestamp); // J表示无符号64位大端序,PHP 7.2+支持 // 5. 处理Payload:十六进制转二进制 $payloadBin = hex2bin($payloadHex); // 6. 组装原始message $prefix = utf8_encode("ton-proof-item-v2/"); $message = $prefix . $addressBin . $appDomainBin . $timestampBin . $payloadBin; // 7. 计算嵌套哈希 $messageHash = hash('sha256', $message, true); $signPrefix = hex2bin('ffff') . utf8_encode("ton-connect"); $signHash = hash('sha256', $signPrefix . $messageHash, true); // 8. 验证签名(将签名十六进制转二进制) $signature = hex2bin($signatureHex); $isValid = sodium_crypto_sign_verify_detached($signature, $signHash, $pubKey); echo $isValid ? "验证通过" : "验证失败"; ?>
注意事项
- 确保PHP版本≥7.2,支持
pack函数的J格式(64位大端序) - 所有十六进制输入(公钥、地址hash、payload、签名)必须是正确的十六进制字符串,不含空格或前缀
- 若使用的是有符号时间戳,可将
pack('J', $timestamp)改为pack('P', $timestamp)(有符号64位大端序)
内容的提问来源于stack exchange,提问作者Anton Bogomolov
相关产品推荐
相关产品推荐

