Verifying TON Proof
TON Proof is connection-time authentication: the wallet signs a message bound to its address, your app’s domain, a timestamp, and a backend-issued payload nonce. The frontend sends your backend a dict in this shape:
Validate the dict into a typed DTO with
TonProofPayloadDto.model_validate(...) and verify it with VerifyTonProof. Verification performs five checks and raises nacl.exceptions.BadSignatureError if any fails:
- The public key extracted from
walletStateInitmatches thepublicKeyfield. - The address derived from
walletStateInitmatches theaddressfield. - The timestamp is within the
valid_auth_timewindow (prevents replay). - The domain is in
allowed_domains(prevents cross-app reuse). - The Ed25519 signature over the
ton-proof-item-v2message is valid.
The public key can only be extracted locally from
walletStateInit for known standard wallet contracts (V1-V5). For wallet contracts the library does not recognize, pass an async resolver as get_wallet_public_key to verify(...) — it receives the wallet Address and should return the PublicKey (for example, by calling the contract’s get_public_key get method on-chain) or None.Verifying Signed Data
Unlike TON Proof,signData is called explicitly to sign off-chain content — agreeing to terms, authorizing actions, and similar. The frontend sends a flat dict with the signature fields at the top level:
payload is the data the user signed; supported types are text, binary, and cell. Verification follows the same pattern with SignDataPayloadDto and VerifySignData, performing the same five checks. The canonical signing message varies by payload type: text and binary use SHA-256, cell payloads use a TL-B cell hash with a CRC32 schema fingerprint.
Challenge Payloads
A TON Proof is only meaningful when itspayload nonce was issued by your backend. Otherwise an attacker could replay a valid proof the user produced for another service. The library provides an HMAC-based challenge that requires no server-side session state:
verify_ton_proof_payload confirms the nonce was issued by your backend and is still within its TTL, which binds the proof to your service and prevents replay. Run it before VerifyTonProof(...).verify(...).
For the full connect-time flow where the same Python process both connects the wallet and verifies the proof, see Connect Wallet.
check_ton_proof.py
Standalone TON Proof verification example on GitHub.
check_sign_data.py
Standalone signed-data verification example on GitHub.