Ed25519 in TON
TON uses Ed25519 as the standard signature scheme. All wallets (v1–v5) and highload wallets rely on Ed25519. Specification:- Public key size: 256 bits
- Signature size: 512 bits
- Curve: Ed25519
Other primitives
TVM exposes additional cryptographic primitives beyond Ed25519. These are useful for cross-chain compatibility and advanced protocols:| Primitive | Purpose |
|---|---|
| secp256k1 | Ethereum-style ECDSA via ECRECOVER; x-only pubkey operations (TVM v9+). |
| secp256r1 (P-256) | ECDSA verification via P256_CHKSIGNS and P256_CHKSIGNU. |
| BLS12-381 | Pairing-based operations for signature aggregation and zero-knowledge proofs. |
| Ristretto255 | Prime-order group over Curve25519 for advanced cryptographic constructions. |
Signing pipeline
Ed25519 signatures in TON typically operate on hashes rather than raw data. This ensures a fixed-size input and consistent signing time.-
Off-chain (TypeScript)
- Serialize message data into a cell
- Compute its hash (256 bits)
- Sign the hash with private key
- Signature (512 bits)
-
On-chain (Tolk)
- Contract receives signature and data
- Recomputes the hash
- Verifies signature matches the hash and public key
CHKSIGNU) is preferred because CHKSIGNS only processes data from a single cell with a maximum size of 127 × 8 = 1016 bits and ignores cell references. For messages containing multiple cells or references, hashing the entire structure first is required.
Signature interaction 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)
Standard wallet contracts are described in more detail in the wallets section. How it works:- User signs a message off-chain that 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 the message and pays gas from the 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 using API.
- Service verifies the signature.
- Service wraps signed message in internal message.
- Service sends internal message to user’s wallet and 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 the recipient and to the 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 that 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, including deploy, mint, and claim.
- If the user tries to send the same message again, the contract ignores it because the state has 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, decide how to organize the data to be signed — the message fields that are hashed and verified. The data can be represented as a slice or as a cell. This choice 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 — part of the cell that may contain additional data and references. This approach is used in wallets v1–v5. Schema for wallet v3r2:slice.hash(), which costs 526 gas.
This cost is high because slice.hash() internally rebuilds a cell from the slice, copying all data and references.
Approach 2: Signed data as cell
The signed data is stored in a separate cell, placed as a reference in the message body. This approach is used in preprocessed wallet v2, highload wallet v3 Schema for preprocessed wallet v2:cell.hash(), which costs 26 gas.
This is efficient because every cell in TON stores its hash as metadata. cell.hash() reads this precomputed value directly without rebuilding or copying the data.
This approach adds one extra cell to the message, increasing the forward fee. However, the gas savings of ~500 gas outweigh the increase in forward fees.
Optimization: Builder hashing
TVM versions introduce builder hashing through theHASHBU instruction. This optimization reduces the gas cost of the signed data as a slice approach.
Verification (optimized):
| Method | Gas cost | Notes |
|---|---|---|
slice.hash() | 526 gas | Rebuilds cell from slice |
| Builder hashing (slice) | <100 gas | With HASHBU: cheap builder hashing |
cell.hash() (cell) | 26 gas | Uses precomputed cell hash |
GlobalVersions.md — TVM improvements
How to sign messages in TypeScript
Prerequisites
- Node.js 18 or later LTS