签名与验证
使用案例
请注意,并非所有DApps都需要 ton_proof 验证。 这对于后端的授权是必要的,以确保用户确实拥有声明的地址,因此可以推断出用户有权访问其在后端的数据。
如果您想验证用户以便从后端提供其个人信息,这将是有用的。
ton_proof 如何工作?
- 向客户端发送DAppid。通常,DAppid嵌入在二维码中。
- 检索带有 ton_proof 实体的已签名交易
- 在后端验证 ton_proof
ton_proof 的结构
ton_proof 在 TON Connect 中与特殊的 TonProof 实体一起工作,该实体在连接器内部实现。
type TonProofItemReply = TonProofItemReplySuccess | TonProofItemReplyError;
type TonProofItemReplySuccess = {
  name: "ton_proof";
  proof: {
    timestamp: string; // 64位unix时代签名操作的时间(秒)
    domain: {
      lengthBytes: number; // AppDomain 长度
      value: string;  // app 域名(作为url部分,无编码)
    };
    signature: string; // base64编码的签名
    payload: string; // 来自请求的有效载荷
  }
}
在服务器端检查 ton_proof
- 从用户处检索 TonProofItemReply。
- 验证接收到的域是否对应于应用程序的域。
- 检查 TonProofItemReply.payload是否被原始服务器允许且仍然有效。
- 检查 timestamp在当前是否真实。
- 根据消息方案组装消息。
- 通过API (a) 或 (b) 在后端实现的逻辑获取 public_key
- 6a:- 通过 TON API 方法 POST /v2/tonconnect/stateinit从walletStateInit中检索{public_key, address}。
- 验证从 walletStateInit提取的address或对应于用户声明的钱包address。
 
- 通过 TON API 方法 
- 6b:- 通过钱包合约的 get 方法 获得钱包的 public_key。
- 如果合约未激活,或者缺少在旧钱包版本(v1-v3)中发现的 get_method,则以这种方式获取密钥将是不可能的。相反,您将需要解析前端提供的 walletStateInit。确保 TonAddressItemReply.walletStateInit.hash() 等于 TonAddressItemReply.address.hash(),表示一个BoC哈希。
 
- 通过钱包合约的 get 方法 获得钱包的 
- 验证前端的 signature实际上签署了组装的消息,并且对应于地址的public_key。
React 示例
- 将token提供器添加到应用的根部:
function App() {
    const [token, setToken] = useState<string | null>(null);
  return (
      <BackendTokenContext.Provider value={{token, setToken}}>
            { /* Your app */ }
      </BackendTokenContext.Provider>
  )
}
- 描述后端认证:
示例
import {useContext, useEffect, useRef} from "react";
import {BackendTokenContext} from "./BackendTokenContext";
import {useIsConnectionRestored, useTonConnectUI, useTonWallet} from "@tonconnect/ui-react";
import {backendAuth} from "./backend-auth";
const localStorageKey = 'my-dapp-auth-token';
const payloadTTLMS = 1000 * 60 * 20;
export function useBackendAuth() {
    const { setToken } = useContext(BackendTokenContext);
    const isConnectionRestored = useIsConnectionRestored();
    const wallet = useTonWallet();
    const [tonConnectUI] = useTonConnectUI();
    const interval = useRef<ReturnType<typeof setInterval> | undefined>();
    useEffect(() => {
        if (!isConnectionRestored || !setToken) {
            return;
        }
        clearInterval(interval.current);
        if (!wallet) {
            localStorage.removeItem(localStorageKey);
            setToken(null);
            const refreshPayload = async () => {
                tonConnectUI.setConnectRequestParameters({ state: 'loading' });
                const value = await backendAuth.generatePayload();
                if (!value) {
                    tonConnectUI.setConnectRequestParameters(null);
                } else {
                    tonConnectUI.setConnectRequestParameters({state: 'ready', value});
                }
            }
            refreshPayload();
            setInterval(refreshPayload, payloadTTLMS);
            return;
        }
        const token = localStorage.getItem(localStorageKey);
        if (token) {
            setToken(token);
            return;
        }
        if (wallet.connectItems?.tonProof && !('error' in wallet.connectItems.tonProof)) {
            backendAuth.checkProof(wallet.connectItems.tonProof.proof, wallet.account).then(result => {
                if (result) {
                    setToken(result);
                    localStorage.setItem(localStorageKey, result);
                } else {
                    alert('Please try another wallet');
                    tonConnectUI.disconnect();
                }
            })
        } else {
            alert('Please try another wallet');
            tonConnectUI.disconnect();
        }
    }, [wallet, isConnectionRestored, setToken])
}
概念解释
如果请求了 TonProofItem,钱包证明了账户所选密钥的所有权。签名消息绑定到:
- 独特的前缀,以将消息与链上消息分开。(ton-connect)
- 钱包地址
- App域
- 签名时间戳
- 应用的自定义有效载荷(服务器可能在其中放置其随机数、cookie id、过期时间)
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是长度 ++ 编码的域名
- 长度是utf-8编码的应用域名长度的32位值(以字节为单位)
- 编码的域名是长度字节的utf-8编码的应用域名
- Timestamp是签名操作的64位unix时代时间
- Payload是一个可变长度的二进制字符串。
注意:有效载荷是可变长度的不受信任数据。为避免使用不必要的长度前缀,我们将其放在消息的最后。
公钥必须验证签名:
- 首先,尝试通过在 - Address部署的智能合约上的- get_public_keyget-method 获得公钥。
- 如果智能合约尚未部署,或缺少get-method,您需要: - 解析 - TonAddressItemReply.walletStateInit并从stateInit获取公钥。您可以将- walletStateInit.code与标准钱包合约的代码进行比较,并根据找到的钱包版本解析数据。
- 检查 - TonAddressItemReply.publicKey是否等于获得的公钥。
- 检查 - TonAddressItemReply.walletStateInit.hash()是否等于- TonAddressItemReply.address。- .hash()意味着BoC哈希。