# ADNL over UDP: internode communication (https://docs.ton.org/llms/foundations/network/adnl-udp/content.md)



ADNL over UDP is the protocol used by TON nodes to communicate with each other. It serves as the foundation for higher-level protocols such as the [distributed hash table (DHT)](https://github.com/ton-blockchain/ton/tree/v2026.06/dht) and [Reliable Large Datagram Protocol (RLDP)](https://github.com/ton-blockchain/ton/tree/v2026.06/rldp2).

Unlike [ADNL over TCP](https://docs.ton.org/llms/foundations/network/adnl-tcp/content.md), the UDP implementation uses channels for ongoing communication and establishes the connection when the first data exchange occurs. Initial packets use public-key encryption and identity signatures, while established channels use directional symmetric keys.

## Transport limits [#transport-limits]

TON applies these limits before or during packet construction:

| Limit               |        Size | Purpose                                                         |
| ------------------- | ----------: | --------------------------------------------------------------- |
| ADNL message MTU    | 1,024 bytes | Maximum `adnl.Message` size without fragmentation               |
| UDP transport MTU   | 1,440 bytes | Maximum UDP payload passed to the network manager               |
| Reassembled message | 8,192 bytes | Maximum serialized message accepted through `adnl.message.part` |

ADNL over UDP remains an unreliable datagram protocol — although sequence numbers reject duplicates and stale packets, the missing packages are not retransmitted.

## Peer endpoint discovery [#peer-endpoint-discovery]

A sender needs the peer's full public key, 256-bit ADNL short identifier, and a usable endpoint. Static bootstrap entries are available in the [`dht.nodes` section of the global configuration](https://ton-blockchain.github.io/global.config.json). Nodes can subsequently update peer information through address lists and the DHT.

For `adnl.address.udp`, `ip` is an unsigned 32-bit IPv4 value and `port` is a 16-bit UDP port encoded in a TL `int` field.

For example, a `dht.nodes` entry can contain:

```json
{
  "@type": "dht.node",
  "id": {
    "@type": "pub.ed25519",
    "key": "fZnkoIAxrTd4xeBgVpZFRm5SvVvSx7eN3Vbe8c83YMk="
  },
  "addr_list": {
    "addrs": [
      {
        "@type": "adnl.address.udp",
        "ip": 1091897261,
        "port": 15813
      }
    ]
  }
}
```

To connect to a node, [lite-client](#interacting-with-ton-nodes):

1. Takes its public `key`, `ip` address, and the `port`.
2. Decodes the Ed25519 public key from base64 `key`.
3. Converts the `ip` value to dotted-decimal format. For example, `1091897261` becomes `65.21.7.173`.
4. Combines the IP address with the port. For example, `65.21.7.173:15813`.
5. Establishes a UDP connection with the resulting `IP:PORT` address.

## Packet contents [#packet-contents]

UDP envelopes encrypt a boxed `adnl.packetContents` object:

```tl
adnl.packetContents
  rand1:bytes                                  -- random 7 or 15 bytes
  flags:#                                      -- bit flags for field presence
  from:flags.0?PublicKey                       -- sender's public key
  from_short:flags.1?adnl.id.short             -- sender's ID
  message:flags.2?adnl.Message                 -- single message
  messages:flags.3?(vector adnl.Message)       -- multiple messages
  address:flags.4?adnl.addressList             -- sender's address list
  priority_address:flags.5?adnl.addressList    -- priority address list
  seqno:flags.6?long                           -- packet sequence number
  confirm_seqno:flags.7?long                   -- last received seqno
  recv_addr_list_version:flags.8?int           -- address version
  recv_priority_addr_list_version:flags.9?int  -- priority address version
  reinit_date:flags.10?int                     -- connection reinitialization date
  dst_reinit_date:flags.10?int                 -- peer's reinitialization date
  signature:flags.11?bytes                     -- ed25519 signature
  rand2:bytes                                  -- random 7 or 15 bytes
        = adnl.PacketContents
```

`rand1` and `rand2` are independently generated 7-byte or 15-byte values. A packet can contain either `message` or `messages`, but not both. Both reinitialization dates are present when flag bit 10 is set.

## Envelope before channel establishment [#envelope-before-channel-establishment]

A UDP packet sent before channel establishment has this wire layout:

| Field                        |     Size | Description                                            |
| ---------------------------- | -------: | ------------------------------------------------------ |
| Destination ADNL short ID    | 32 bytes | SHA-256 of the destination identity's boxed public key |
| Ephemeral Ed25519 public key | 32 bytes | Fresh key generated by the public-key encryptor        |
| Plaintext hash               | 32 bytes | SHA-256 of the serialized `adnl.packetContents`        |
| Encrypted packet contents    | Variable | AES-CTR encryption of `adnl.packetContents`            |

The last 3 fields are the output of TON's Ed25519 encryptor. It derives a shared secret from the ephemeral private key and the destination identity public key. The destination uses its identity private key and the transmitted ephemeral public key to derive the same secret.

The sender signs the boxed packet contents with the source identity private key. Signature calculation clears `signature` and flag bit 11, serializes the remaining object, and signs those bytes. The sender then inserts the signature and serializes the object again before encryption.

The receiver performs these checks before delivering messages:

1. Decrypts the envelope with the private key selected by the destination short ID.
2. Verifies the plaintext SHA-256 value.
3. Parses `adnl.packetContents` and validates its flags.
4. Resolves the source public key and verifies the packet signature.
5. Applies re-initialization, sequence-number, acknowledgment, and address-list checks.

<Callout type="note">
  [Ed25519 envelope implementation in the TON monorepo](https://github.com/ton-blockchain/ton/blob/v2026.06/keys/encryptor.cpp#L31-L110) defines the exact key and initialization-vector derivation.
</Callout>

## Channel negotiation [#channel-negotiation]

Peers negotiate channels with these messages:

```tl
adnl.message.createChannel
  key:int256
  date:int
    = adnl.Message;

adnl.message.confirmChannel
  key:int256
  peer_key:int256
  date:int
    = adnl.Message;
```

There, `key` is a freshly generated Ed25519 channel public key and `date` is its Unix creation time. In `confirmChannel`, `peer_key` must equal the public key proposed by the peer — confirmations for another or superseded key are rejected.

A typical first packet contains `createChannel` and an application message such as `adnl.message.query`. The application message is not required to be a query — channel negotiation is appended to whichever messages are queued for the peer.

The numeric constructor ID of `adnl.message.createChannel` is `0xe673c3bb`, and its little-endian wire bytes are `bb c3 73 e6`. Constructor IDs elsewhere on this page follow the same numeric-ID and wire-byte distinction.

Serialized `createChannel` example:

```text
bbc373e6                                                         -- createChannel constructor wire bytes
d59d8e3991be20b54dde8b78b3af18b379a62fa30e64af361c75452f6af019d7 -- key
555c8763                                                         -- date
```

An accompanying query uses this schema:

```tl
adnl.message.query query_id:int256 query:bytes = adnl.Message;
```

The actual request, such as `dht.getSignedAddressList`, is serialized inside `query`. Its sample encoding is:

```text
7af98bb4                                                         -- adnl.message.query constructor wire bytes
d7be82afbc80516ebca39784b8e2209886a69601251571444514b7f17fcd8875 -- random query_id
04 ed4879a9 000000                                               -- query: 4-byte payload and 3-byte padding
```

For this exchange, the peer responds with `adnl.message.confirmChannel`, which provides its channel public key, and `adnl.message.answer`, which carries the query response.

## Channel key derivation [#channel-key-derivation]

Each peer derives a 32-byte shared secret from its channel private key and the other peer's channel public key. Two AES keys are then defined:

```text
shared_key = shared secret
reversed_key = shared secret with its 32 bytes reversed
```

Direction is selected by comparing the peers' ADNL short identity IDs as unsigned 256-bit values. The comparison does not use channel-key IDs.

| Condition                     | Outgoing encryption key | Incoming decryption key |
| ----------------------------- | ----------------------- | ----------------------- |
| Local ADNL ID \< peer ADNL ID | `reversed_key`          | `shared_key`            |
| Local ADNL ID > peer ADNL ID  | `shared_key`            | `reversed_key`          |

The incoming and outgoing channel IDs are the ADNL short IDs of their respective AES keys.

<Callout type="note">
  [Channel implementation in the TON monorepo](https://github.com/ton-blockchain/ton/blob/v2026.06/adnl/adnl-channel.cpp#L30-L64) defines the derivation and comparison.
</Callout>

## Channel packet envelope [#channel-packet-envelope]

An established channel packet has this wire layout:

| Field                     |     Size | Description                                     |
| ------------------------- | -------: | ----------------------------------------------- |
| Outgoing channel ID       | 32 bytes | ADNL short ID of the directional AES key        |
| Plaintext hash            | 32 bytes | SHA-256 of the serialized `adnl.packetContents` |
| Encrypted packet contents | Variable | AES-CTR encryption of `adnl.packetContents`     |

Channel packets omit the source identity and signature because possession of the directional key authenticates the sender. They normally omit reinitialization dates. They can still contain a single message or message vector, sequence fields, address lists, priority address lists, and received-version fields.

## Sequence and session state [#sequence-and-session-state]

* `seqno` identifies a packet within the current reinitialization epoch. TON tracks the highest received number plus a 64-packet mask. It rejects duplicates and packets more than 63 positions behind the highest accepted number.

* `confirm_seqno` acknowledges the highest packet number accepted from the peer. A value greater than the highest locally sent sequence number is invalid. Acknowledgment updates state but does not cause ADNL to retransmit missing packets.

* `reinit_date` identifies the sender's epoch, and `dst_reinit_date` states which destination epoch the sender has observed. A newer valid peer epoch resets channel, sequence, acknowledgment, address-version, and partial-message state. Reinitialization metadata is carried in signed packets sent outside a channel.

## Address-list propagation [#address-list-propagation]

Packets can advertise ordinary and priority address lists. The `recv_addr_list_version` fields tell the peer which versions have already been received, allowing it to omit unchanged lists.

```tl
adnl.addressList
  addrs:(vector adnl.Address)
  version:int
  reinit_date:int
  priority:int
  expire_at:int
    = adnl.AddressList;
```

Supported address variants include IPv4 UDP, IPv6 UDP, ADNL tunnels, reverse connectivity, and IPv4 QUIC endpoints. Nodes reject address lists whose serialized representation exceeds the implementation limit.

## Message types [#message-types]

### Query and answer, `adnl.message.query` [#query-and-answer-adnlmessagequery]

```tl
adnl.message.query query_id:int256 query:bytes = adnl.Message;
adnl.message.answer query_id:int256 answer:bytes = adnl.Message;
```

`query_id` correlates one answer with one outstanding query. Higher-level protocol data is serialized inside `query` or `answer`.

### Part, `adnl.message.part` [#part-adnlmessagepart]

```tl
adnl.message.part
  hash:int256
  total_size:int
  offset:int
  data:bytes
    = adnl.Message;
```

The blockchain fragments the complete boxed serialization of an `adnl.Message`, not only its application payload. Reassembly has these constraints:

* The first accepted part has offset `0`.
* Parts for a message arrive contiguously and in order.
* Only one hash is assembled per peer at a time.
* `total_size` does not exceed 8,192 bytes.
* The final SHA-256 value equals `hash`.
* The reassembled bytes parse as one boxed `adnl.Message`.

### Custom, `adnl.message.custom` [#custom-adnlmessagecustom]

```tl
adnl.message.custom data:bytes = adnl.Message;
```

`custom` delegates the payload to a subscribed higher-level protocol. RLDP uses custom messages because its transfer protocol does not follow a single-request, single-response exchange.

### No-operation, `adnl.message.nop` [#no-operation-adnlmessagenop]

```tl
adnl.message.nop = adnl.Message;
```

`nop` carries no application data.

### Reinitialization, `adnl.message.reinit` [#reinitialization-adnlmessagereinit]

```tl
adnl.message.reinit date:int = adnl.Message;
```

`reinit` asks the peer to adopt a newer session epoch — stale dates have no effect.

## See also [#see-also]

* [ADNL packet schema in the TON monorepo](https://github.com/ton-blockchain/ton/blob/v2026.06/tl/generate/scheme/ton_api.tl#L77-L119)
* [UDP peer state machine in the TON monorepo](https://github.com/ton-blockchain/ton/blob/v2026.06/adnl/adnl-peer.cpp)
* [ADNL overview](https://docs.ton.org/llms/foundations/network/adnl/content.md)
* [ADNL over TCP](https://docs.ton.org/llms/foundations/network/adnl-tcp/content.md)
