> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ton.org/llms.txt
> Use this file to discover all available pages before exploring further.

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.ton.org/feedback

```json
{
  "path": "/contract-dev/signing",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# Signing messages

export const Aside = ({type = "note", title = "", icon = "", iconType = "regular", children}) => {
  const asideVariants = ["note", "tip", "caution", "danger"];
  const asideComponents = {
    note: {
      outerStyle: "border-sky-500/20 bg-sky-50/50 dark:border-sky-500/30 dark:bg-sky-500/10",
      innerStyle: "text-sky-900 dark:text-sky-200",
      calloutType: "note",
      icon: <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-sky-500" aria-label="Note">
          <path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.3C10.14 1.3 12.7 3.86 12.7 7C12.7 10.14 10.14 12.7 7 12.7C5.48908 12.6974 4.0408 12.096 2.97241 11.0276C1.90403 9.9592 1.30264 8.51092 1.3 7C1.3 3.86 3.86 1.3 7 1.3ZM7 0C3.14 0 0 3.14 0 7C0 10.86 3.14 14 7 14C10.86 14 14 10.86 14 7C14 3.14 10.86 0 7 0ZM8 3H6V8H8V3ZM8 9H6V11H8V9Z"></path>
        </svg>
    },
    tip: {
      outerStyle: "border-emerald-500/20 bg-emerald-50/50 dark:border-emerald-500/30 dark:bg-emerald-500/10",
      innerStyle: "text-emerald-900 dark:text-emerald-200",
      calloutType: "tip",
      icon: <svg width="11" height="14" viewBox="0 0 11 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="text-emerald-600 dark:text-emerald-400/80 w-3.5 h-auto" aria-label="Tip">
          <path d="M3.12794 12.4232C3.12794 12.5954 3.1776 12.7634 3.27244 12.907L3.74114 13.6095C3.88471 13.8248 4.21067 14 4.46964 14H6.15606C6.41415 14 6.74017 13.825 6.88373 13.6095L7.3508 12.9073C7.43114 12.7859 7.49705 12.569 7.49705 12.4232L7.50055 11.3513H3.12521L3.12794 12.4232ZM5.31288 0C2.52414 0.00875889 0.5 2.26889 0.5 4.78826C0.5 6.00188 0.949566 7.10829 1.69119 7.95492C2.14321 8.47011 2.84901 9.54727 3.11919 10.4557C3.12005 10.4625 3.12175 10.4698 3.12261 10.4771H7.50342C7.50427 10.4698 7.50598 10.463 7.50684 10.4557C7.77688 9.54727 8.48281 8.47011 8.93484 7.95492C9.67728 7.13181 10.1258 6.02703 10.1258 4.78826C10.1258 2.15486 7.9709 0.000106649 5.31288 0ZM7.94902 7.11267C7.52078 7.60079 6.99082 8.37878 6.6077 9.18794H4.02051C3.63739 8.37878 3.10743 7.60079 2.67947 7.11294C2.11997 6.47551 1.8126 5.63599 1.8126 4.78826C1.8126 3.09829 3.12794 1.31944 5.28827 1.3126C7.2435 1.3126 8.81315 2.88226 8.81315 4.78826C8.81315 5.63599 8.50688 6.47551 7.94902 7.11267ZM4.87534 2.18767C3.66939 2.18767 2.68767 3.16939 2.68767 4.37534C2.68767 4.61719 2.88336 4.81288 3.12521 4.81288C3.36705 4.81288 3.56274 4.61599 3.56274 4.37534C3.56274 3.6515 4.1515 3.06274 4.87534 3.06274C5.11719 3.06274 5.31288 2.86727 5.31288 2.62548C5.31288 2.38369 5.11599 2.18767 4.87534 2.18767Z"></path>
        </svg>
    },
    caution: {
      outerStyle: "border-amber-500/20 bg-amber-50/50 dark:border-amber-500/30 dark:bg-amber-500/10",
      innerStyle: "text-amber-900 dark:text-amber-200",
      calloutType: "warning",
      icon: <svg className="flex-none w-5 h-5 text-amber-400 dark:text-amber-300/80" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-label="Warning">
          <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
        </svg>
    },
    danger: {
      outerStyle: "border-red-500/20 bg-red-50/50 dark:border-red-500/30 dark:bg-red-500/10",
      innerStyle: "text-red-900 dark:text-red-200",
      calloutType: "danger",
      icon: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" className="text-red-600 dark:text-red-400/80 w-4 h-4" aria-label="Danger">
          <path d="M17.1 292c-12.9-22.3-12.9-49.7 0-72L105.4 67.1c12.9-22.3 36.6-36 62.4-36l176.6 0c25.7 0 49.5 13.7 62.4 36L494.9 220c12.9 22.3 12.9 49.7 0 72L406.6 444.9c-12.9 22.3-36.6 36-62.4 36l-176.6 0c-25.7 0-49.5-13.7-62.4-36L17.1 292zm41.6-48c-4.3 7.4-4.3 16.6 0 24l88.3 152.9c4.3 7.4 12.2 12 20.8 12l176.6 0c8.6 0 16.5-4.6 20.8-12L453.4 268c4.3-7.4 4.3-16.6 0-24L365.1 91.1c-4.3-7.4-12.2-12-20.8-12l-176.6 0c-8.6 0-16.5 4.6-20.8 12L58.6 244zM256 128c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"></path>
        </svg>
    }
  };
  let variant = type;
  let gotInvalidVariant = false;
  if (!asideVariants.includes(type)) {
    gotInvalidVariant = true;
    variant = "danger";
  }
  const iconVariants = ["regular", "solid", "light", "thin", "sharp-solid", "duotone", "brands"];
  if (!iconVariants.includes(iconType)) {
    iconType = "regular";
  }
  return <>
      <div className={`callout my-4 px-5 py-4 overflow-hidden rounded-2xl flex gap-3 border ${asideComponents[variant].outerStyle}`} data-callout-type={asideComponents[variant].calloutType}>
        <div className="mt-0.5 w-4" data-component-part="callout-icon">
          {}
          {icon === "" ? asideComponents[variant].icon : <Icon icon={icon} iconType={iconType} size={14} />}
        </div>
        <div className={`text-sm prose min-w-0 w-full ${asideComponents[variant].innerStyle}`} data-component-part="callout-content">
          {gotInvalidVariant ? <p>
              <span className="font-bold">
                Invalid <code>type</code> passed!
              </span>
              <br />
              <span className="font-bold">Received: </span>
              {type}
              <br />
              <span className="font-bold">Expected one of: </span>
              {asideVariants.join(", ")}
            </p> : <>
              {title && <p className="font-bold">{title}</p>}
              {children}
            </>}
        </div>
      </div>
    </>;
};

Cryptographic signatures are the foundation of access control. Contracts verify signatures on-chain and implement their own authorization policies for [wallet operations](/standard/wallets/how-it-works#how-ownership-verification-works), server-authorized actions, [gasless transactions](/standard/wallets/v5#gasless-transactions), multisig wallets, and delegation.

## Ed25519 in TON

TON uses [Ed25519](https://en.wikipedia.org/wiki/EdDSA#Ed25519) as the [standard](/foundations/whitepapers/tblkch#a-3-10-cryptographic-ed25519-signatures) signature scheme. All [wallets](/standard/wallets/history) (v1–v5) and [highload wallets](/standard/wallets/highload/overview) rely on Ed25519.

Specification:

* Public key size: 256 bits
* Signature size: 512 bits
* Curve: Ed25519

<Aside type="caution">
  The public key is *not* the wallet address. The address is derived from the contract's initial code and data that are included in [`StateInit`](/foundations/messages/deploy) structure. Multiple contracts can use the same public key but have [different addresses](/foundations/addresses/overview).
</Aside>

## Other primitives

TVM exposes additional cryptographic primitives beyond Ed25519. These are useful for cross-chain compatibility and advanced protocols:

| Primitive                                                                                     | Purpose                                                                      |
| --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| [secp256k1](https://en.wikipedia.org/wiki/Secp256k1)                                          | Ethereum-style ECDSA via `ECRECOVER`; x-only pubkey operations (TVM v9+)     |
| [secp256r1 (P-256)](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm) | ECDSA verification via `P256_CHKSIGNS` and `P256_CHKSIGNU`                   |
| [BLS12-381](https://en.wikipedia.org/wiki/BLS_digital_signature)                              | Pairing-based operations for signature aggregation and zero-knowledge proofs |
| [Ristretto255](https://en.wikipedia.org/wiki/Curve25519#Ristretto)                            | Prime-order group over Curve25519 for advanced cryptographic constructions   |

For details, see crypto instructions in [TVM instructions](/tvm/instructions).

This guide focuses on Ed25519, usually used for TON.

## Signing pipeline

Ed25519 signatures in TON typically work with hashes rather than raw data, because signing the hash takes a smaller and constant amount of 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

TVM provides two signature verification methods:

* [`CHKSIGNS`](/tvm/instructions#f911-chksigns) checks signature of data;
* [`CHKSIGNU`](/tvm/instructions#f910-chksignu) checks signature of hash.

Hash-based verification (`CHKSIGNU`) is preferred because `CHKSIGNS` only processes data from a single cell (up to $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](/standard/wallets/history) in the wallets section.

How it works:

1. User signs a message off-chain (includes replay protection data and transfer details)
2. User sends external message to blockchain
3. Wallet contract verifies the signature
4. Wallet contract checks seqno for replay protection
5. Wallet contract accepts message (pays gas from wallet balance)
6. Wallet contract increments seqno
7. Wallet contract executes the transfer

Key characteristics:

* Who signs: User
* Who sends: User (external message)
* Who pays gas: Wallet contract

This is the most common pattern.

### Example 2: Gasless transactions (Wallet v5)

How it works:

1. User signs a message off-chain that includes two transfers: one to recipient, one to service as payment
2. User sends signed message to service via API
3. Service verifies the signature
4. Service wraps signed message in internal message
5. Service sends internal message to user's wallet (pays gas in TON)
6. Wallet contract verifies user's signature
7. Wallet contract checks seqno for replay protection
8. Wallet contract increments seqno
9. Wallet contract executes both transfers (to recipient and to service)

Key characteristics:

* Who signs: User
* Who sends: Service (internal message)
* Who pays gas: Service (in TON), gets compensated in Jettons

This pattern enables users to pay gas in Jettons instead of TON.

### Example 3: Server-controlled operations

How it works:

1. User requests authorization from server
2. Server validates request and signs authorization message (includes validity period and operation parameters)
3. User sends server-signed message to contract (with payment)
4. Contract verifies server's signature
5. Contract checks validity period
6. Contract performs authorized action (deploy, mint, claim)
7. If user tries to send same message again, contract ignores it (state already changed)

Key characteristics:

* Who signs: Server
* Who sends: User (internal message with payment)
* Who pays gas: User

This pattern is useful when backend needs to authorize specific operations (auctions, mints, claims) without managing private keys for each user.

Real-world example: [telemint contract](https://github.com/TelegramMessenger/telemint) uses server-signed messages to authorize NFT deployments.

## Message structure for signing

When designing a signed message, the choice is 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:

```tlb theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
msg_body$_ signature:bits512 subwallet_id:uint32
           seqno:uint32 valid_until:uint32
           mode:uint8 message_to_send:^Cell
           = ExternalInMessage;
```

```mermaid theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
graph
    subgraph Cell["Message body"]
        direction LR
        A[**signature**]
        subgraph Signed["Signed data"]
        direction LR
            B[subwallet_id]
            C[seqno]
            D[valid_until]
            E[mode]
            F((message<br>to send))
        end
    end

    A ~~~ B
    B ~~~ C
    C ~~~ D
    D ~~~ E
    E ~~~ F

```

Verification in FunC:

```func theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
slice signature = in_msg_body~load_bits(512);
slice signed_data = in_msg_body;  // Remaining data

int hash = slice_hash(signed_data);  // 526 gas
throw_unless(35, check_signature(hash, signature, public_key));
```

Gas analysis:

After loading the signature, the remaining data is a **slice**. To verify the signature, the contract needs to hash this slice. In TVM, the method for hashing a slice is `slice_hash()`, which costs `526` gas.

Why expensive?\
`slice_hash()` internally rebuilds a cell from the slice, copying all data and references.

<Aside type="tip" title="Optimization available">
  Recent TVM versions support `builder_hash()` for efficient hashing. Convert the slice to a builder and hash it — this costs less than 100 gas total. See [Optimization: Builder hashing](#optimization-builder-hashing) below for details.
</Aside>

### 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:

```tlb theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
_ valid_until:uint64 seqno:uint16 actions:^Cell = MsgInner;

msg_body$_ signature:bits512 msg_inner:^MsgInner = ExternalInMessage;
```

```mermaid theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
graph LR
    subgraph Root["Message body"]
        direction LR
        A[**signature**]
        B((msg_inner))
    end

    subgraph Ref["Signed data (next cell)"]
        direction LR
        C[valid_until]
        D[seqno]
        E((actions))
    end

    A ~~~ B
    B -.-> Ref
    C ~~~ D
    D ~~~ E

```

Verification in FunC:

```func theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
slice signature = in_msg_body~load_bits(512);
cell signed_data = in_msg_body~load_ref();  // Signed data as cell

int hash = cell_hash(signed_data);  // 26 gas
throw_unless(35, check_signature(hash, signature, public_key));
```

Gas analysis:

The signed data is loaded as a **cell** from the reference. To get its hash, the contract uses `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.

### Optimization: Builder hashing

Recent TVM versions introduced efficient builder hashing (`HASHBU` instruction), which makes **signed data as slice** approach much more gas-efficient.

Verification in FunC (optimized):

```func theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
slice signature = in_msg_body~load_bits(512);
slice signed_data = in_msg_body;

builder b = begin_cell().store_slice(signed_data);
int hash = b.builder_hash();
throw_unless(35, check_signature(hash, signature, public_key));
```

Gas comparison:

| 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         |

Conclusion:\
With builder hashing optimization, both approaches are gas-efficient. New contracts can choose based on code simplicity and forward fee considerations.

Reference: [`GlobalVersions.md` — TVM improvements](https://github.com/ton-blockchain/ton/blob/5c0349110bb03dd3a241689f2ab334ae1a554ffb/doc/GlobalVersions.md#new-tvm-instructions-4)

## How to sign messages in TypeScript

### Prerequisites

* Node.js 18+ or TypeScript environment
* `@ton/core`, `@ton/crypto` packages installed

Install required packages:

```bash theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
npm install @ton/core @ton/crypto
```

### Step 1: Generate or load a mnemonic

A mnemonic is the wallet's master secret. It derives the private key used to sign messages.

Generate a new mnemonic:

```typescript theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
import { mnemonicNew } from '@ton/crypto';

const mnemonic = await mnemonicNew(24); // Array of 24 words
```

Load an existing mnemonic:

```typescript theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
// Read from environment; do not inline secrets
const mnemonic = (process.env.MNEMONIC ?? 'MNEMONIC_WORDS').split(' ');
```

<Aside type="danger" title="Protect the mnemonic">
  Anyone with access to the mnemonic can control the wallet and all funds. Store it securely (password manager, hardware wallet, encrypted storage). Never commit it to version control.
</Aside>

### Step 2: Derive the keypair

Convert the mnemonic to an Ed25519 keypair:

```typescript theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
import { mnemonicToPrivateKey } from '@ton/crypto';

const keyPair = await mnemonicToPrivateKey(mnemonic);
// keyPair.publicKey  — stored in contract
// keyPair.secretKey  — used to sign messages
```

### Step 3: Build the signed data

Build the message data that will be signed:

```typescript theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
import { beginCell } from '@ton/core';

// Build signed data (example: wallet message)
const seqno = 5;
const validUntil = Math.floor(Date.now() / 1000) + 60; // 60 seconds from now

const signedData = beginCell()
    .storeUint(seqno, 32)
    .storeUint(validUntil, 32)
    // ... other fields (subwallet_id, actions, etc.)
    .endCell();
```

### Step 4: Create the signature

Sign the hash of the signed data:

```typescript theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
import { sign } from '@ton/crypto';

const signature = sign(signedData.hash(), keyPair.secretKey);
// signature is a Buffer with 512 bits
```

### Step 5: Build the message body

Choose the structure based on the contract design (see [Message structure for signing](#message-structure-for-signing) above):

```typescript theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
// Approach 1: Signed data as slice
const messageBodyInline = beginCell()
    .storeBuffer(signature)             // 512 bits
    .storeSlice(signedData.asSlice())   // Signed data as slice
    .endCell();

// Approach 2: Signed data as cell
const messageBodySeparate = beginCell()
    .storeBuffer(signature)             // 512 bits
    .storeRef(signedData)               // Signed data as cell
    .endCell();
```
