Overview
Cryptographic signatures are the foundation of access control in TON. Contracts verify signatures on-chain and implement their own policies, enabling flexible designs such as wallet operations, server-authorized actions, gasless flows, multisig, and delegation.Ed25519 in TON
TON uses Ed25519 as the standard signature scheme. All wallets (v1–v5) and highload wallets rely on Ed25519. Specification:- Public key: 256 bits
- Signature: 512 bits
- Curve: Edwards form over Curve25519
The public key is not the wallet address. The address is derived from the contract’s
StateInit
(code + initial data). Multiple contracts can use the same public key but have different addresses. See Addresses in TON.Other cryptographic primitives in TVMTVM exposes additional cryptographic primitives beyond Ed25519. These are useful for cross-chain compatibility and advanced protocols:
- secp256k1 — Ethereum-style ECDSA via
ECRECOVER
; plus x-only pubkey tweak ops (v9+) - secp256r1 (P-256) — ECDSA verification via
P256_CHKSIGNS
/P256_CHKSIGNU
- BLS12-381 — pairing-based ops enabling signature aggregation
- Ristretto255 — prime-order group over Curve25519 for advanced constructions
What gets signed
Ed25519 signatures in TON work with hashes, not raw data:- Off-chain: Serialize message data into a cell → compute its hash (256 bits) → sign the hash with private key → signature (512 bits)
- On-chain: Contract receives signature and data → recomputes the hash → verifies signature matches the hash and public key
Common signing patterns
Signatures are used in different ways depending on who signs the message, who sends it, and who pays for execution. Here are three real-world examples.Example 1: Standard wallets (v1–v5)
How it works:- User signs a message off-chain (includes replay protection data and transfer details)
- User sends external message to blockchain
- Wallet contract verifies the signature
- Wallet contract checks seqno for replay protection
- Wallet contract accepts message (pays gas from wallet balance)
- Wallet contract increments seqno
- Wallet contract executes the transfer
- Who signs: User
- Who sends: User (external message)
- Who pays gas: Wallet contract
Example 2: Gasless transactions (Wallet v5)
How it works:- User signs a message off-chain that includes two transfers: one to recipient, one to service as payment
- User sends signed message to service via API
- Service verifies the signature
- Service wraps signed message in internal message
- Service sends internal message to user’s wallet (pays gas in TON)
- Wallet contract verifies user’s signature
- Wallet contract checks seqno for replay protection
- Wallet contract increments seqno
- Wallet contract executes both transfers (to recipient and to service)
- Who signs: User
- Who sends: Service (internal message)
- Who pays gas: Service (in TON), gets compensated in Jettons
Example 3: Server-controlled operations
How it works:- User requests authorization from server
- Server validates request and signs authorization message (includes validity period and operation parameters)
- User sends server-signed message to contract (with payment)
- Contract verifies server’s signature
- Contract checks validity period
- Contract performs authorized action (deploy, mint, claim)
- If user tries to send same message again, contract ignores it (state already changed)
- Who signs: Server
- Who sends: User (internal message with payment)
- Who pays gas: User
Message structure for signing
When designing a signed message, you choose how to organize the signed data — the message fields that will be hashed and verified. The key question: is the signed data a slice (part of a cell) or a cell (separate cell)? This affects gas consumption during signature verification.Approach 1: Signed data as slice
After loading the signature from the message body, the signed data remains as a slice — a part of the cell that may contain additional data and references. Used in: Wallet v1-v5 Schema — Wallet v3r2:slice_hash()
, which costs 526 gas.
Why expensive?slice_hash()
internally rebuilds a cell from the slice, copying all data and references.
TVM v12+ optimization: In TVM version 12+, use
builder_hash()
instead. Convert the slice to a builder and hash it — this costs less than 100 gas total. See TVM v12 improvement below for details.Approach 2: Signed data as cell
The signed data is stored in a separate cell, placed as a reference in the message body. Used in: Preprocessed Wallet v2, Highload Wallet v3 Schema — Preprocessed Wallet v2:cell_hash()
, which costs only 26 gas.
Why efficient?Every cell in TON stores its hash as metadata.
cell_hash()
reads this precomputed value directly — no rebuilding, no copying.
Trade-off:This approach adds one extra cell to the message, slightly increasing the forward fee. However, the gas savings (~500 gas) outweigh the forward fee increase.
TVM v12 improvement
TVM version 12 introduced efficient builder hashing (HASHBU
instruction), which makes signed data as slice approach much more gas-efficient.
Verification in FunC (TVM v12+):
Method | Gas cost | Notes |
---|---|---|
slice_hash() | 526 gas | Rebuilds cell from slice |
Builder hashing (slice) | <100 gas | v12+: cheap, uses HASHBU |
cell_hash() (cell) | 26 gas | Uses precomputed cell hash |
In TVM v12+, both approaches are gas-efficient. New contracts can choose based on code simplicity and forward fee considerations. Reference: GlobalVersions.md — TVM v12 updates
How to sign messages in TypeScript
Prerequisites
- Node.js 18+ or TypeScript environment
@ton/core
,@ton/crypto
packages installed
Step 1: Generate or load a mnemonic
A mnemonic is your wallet’s master secret. It derives the private key used to sign messages. Generate a new mnemonic:Protect your mnemonic: Anyone with access to your mnemonic can control your wallet and all funds. Store it securely (password manager, hardware wallet, encrypted storage). Never commit it to version control.