TON DocsTON Docs
OnboardingNodesApplicationsAPIsContractsSmart contractsTolkTolk languageTVMTON Virtual MachineFoundationsBlockchain foundations

ADNL over UDP: internode communication

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) and Reliable Large Datagram Protocol (RLDP).

Unlike ADNL over TCP, 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

TON applies these limits before or during packet construction:

LimitSizePurpose
ADNL message MTU1,024 bytesMaximum adnl.Message size without fragmentation
UDP transport MTU1,440 bytesMaximum UDP payload passed to the network manager
Reassembled message8,192 bytesMaximum 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

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

{
  "@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:

  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

UDP envelopes encrypt a boxed adnl.packetContents object:

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

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

FieldSizeDescription
Destination ADNL short ID32 bytesSHA-256 of the destination identity's boxed public key
Ephemeral Ed25519 public key32 bytesFresh key generated by the public-key encryptor
Plaintext hash32 bytesSHA-256 of the serialized adnl.packetContents
Encrypted packet contentsVariableAES-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.

Ed25519 envelope implementation in the TON monorepo defines the exact key and initialization-vector derivation.

Channel negotiation

Peers negotiate channels with these messages:

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:

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

An accompanying query uses this schema:

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:

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

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:

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.

ConditionOutgoing encryption keyIncoming decryption key
Local ADNL ID < peer ADNL IDreversed_keyshared_key
Local ADNL ID > peer ADNL IDshared_keyreversed_key

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

Channel implementation in the TON monorepo defines the derivation and comparison.

Channel packet envelope

An established channel packet has this wire layout:

FieldSizeDescription
Outgoing channel ID32 bytesADNL short ID of the directional AES key
Plaintext hash32 bytesSHA-256 of the serialized adnl.packetContents
Encrypted packet contentsVariableAES-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

  • 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

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.

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

Query and answer, adnl.message.query

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

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

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

adnl.message.nop = adnl.Message;

nop carries no application data.

Reinitialization, adnl.message.reinit

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

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

See also

On this page