Movatterモバイル変換


[0]ホーム

URL:


bald man icon

ADNL on UDP

This is an internal doc, not official at all, describing the ADNL over UDP protocol in a more formal way. Given that the official documentation to be honest lacks formality. The description has a good approach, that is explaining by example, but still there are so many gaps that should be included. Even that the official documentaion lacks formality, is still way better than the description on the TON whitepaper. In the whitepaper of TON there’s a quite simplistic description of the protocol without any details, which gives the impression that the implementation of the protocol was only discussed internally between the developers of the TON blockchain. I understand that the whitepaper is not intended for that, but a document dedicated to the protocol that constitute the core of the networking I think is needed. In general the networking documentation of TON is quite bad, so I’ll try to do my best at least with ADNL over UDP.

Requirements

Before you start diving in, there are some previous knowledge that you should have.

  1. Basics about UDP, a formal description of it can be found inRFC 768, but any programming language has an implementation of it. Even the formal description is quite short, UDP is basic.
  2. TL serializer and parser. ADNL usesTL(Type Language) to serialize the data that will be transmitted. This is quite particular way of serialization, so not sure if you will find a great variety of libraries that implement it. Here I list you the ones I found:
    1. (Go) xssnick/tonutils-go/tl.
    2. (Go) in this repository you can also find one I wrote, still with a lot of cases missing.
    3. (C++) ton-blockchain/ton/tl. This would be your source of truth I think.
    4. (Rust) ston-fi/tonlib-rs/src/tl.
  3. Get familiar withECDH which stands for Elliptic Curve Diffie-Helman, not in depth but at least try to understand a basic example of it. I think the best resources I found for it at the moment are these:
    1. cryptobook.nakov blog, you can find an example written in Python there.
    2. ECDH encryption using ed25519 keys. With an example written in Golang.

Note

  1. All the TL definition used here can be found intl/generate/scheme.
  2. This article might have incorrect or incomplete information, so feel free to suggest a correction of it.

Description

QuotingTON whitepaper:

The cornerstone in building the TON networking protocols is the (TON) Abstract (Datagram) Network Layer. It enables all nodes to assume certain “network identities”, represented by 256-bit “abstract network addresses”, and communicate (send datagrams to each other, as a first step) using only these 256-bit network addresses to identify the sender and the receiver.

The description of the ADNL protocol in this document will be in the context of its usage on the TON blockchain.

Packet Format

Packet data follows a TL-object calledadnl.packetContents. Here is a description of it.

adnl.packetContents

Communication on ADNL protocol is carried out through the share of datagrams that followedadnl.packetContents format. The definition of this TL-object is described as follows:

adnl.packetContents   rand1:bytes   flags:#   from:flags.0?PublicKey   from_short:flags.1?adnl.id.short  message:flags.2?adnl.Message   messages:flags.3?(vector adnl.Message)  address:flags.4?adnl.addressList   priority_address:flags.5?adnl.addressList  seqno:flags.6?long   confirm_seqno:flags.7?long   recv_addr_list_version:flags.8?int  recv_priority_addr_list_version:flags.9?int  reinit_date:flags.10?int   dst_reinit_date:flags.10?int  signature:flags.11?bytes   rand2:bytes         = adnl.PacketContents;

This is the TL definition, I’ll try to explain better the components of that TL definition in the following table.

adnl.packetContents

Notes: Fields which doesn’t affect flags byte position are maked inflag byte position column with value_. Types that are referenced in the table likePublicKey can be found defined inton_api.tl. Some of these types areexplicitly defined and some are not. The ones that are not, likePublicKey, it just means that it accepts one of the availablePublicKey types. For example,from field takesPublicKey type which is not explicitely define, meaning that accepts one of the available types ofPublicKey defined inton_api.tl,pub.unenc, pub.ed25519, pub.aes, or pub.overlay.

Field nameflag byte positiontypedescription
rand1_bytesrandom 7 or 15 bytes, why 7 or 15? No idea
flags_intflags marks which fields of the TL-object are present
from0PublicKeypacket sender public key
from_short1adnl.id.shortnode identifier
message2adnl.Messagea single unit ofadnl.Message checkout differents types on TL definition
messages3(vector adnl.Message)several units of adnl.Message
address4adnl.addressLista list ofadnl.Address
priority_address5adnl.addressLista list ofadnl.Address
seqno6longsequence number of the packet, in case the receiver already proccessed a packet with this seqno, the packet will be dropped
confirm_seqno7longlatest confirmed sequence number, if confirm_seqno > out_seqno in the receiver node the packet will be dropped
recv_addr_list_version8intno idea why is this needed yet
recv_priority_addr_list_version9intno idea why is this needed yet
reinit_date10intcurrent timestamp of sender noded, needs to be bigger than last reinit_date sent, otherwise packet will be dropped
dst_reinit_date10inttimestamp
signature11bytespacket signature signed with sender private key
rand2_bytesrandom 7 or 15 bytes

Details on some fields

dst_reinit_date

This field when received by a node has a particular requirement in the code implementation that I don’t know if it’s a bug, or just a useless field included. There are two checks on this field that basically will restrictdst_reinit_date to bedst_reinit_date <=0 ordst_reinit_date = d. The first checked performed on the received field, on methodAdnlPeerPairImpl::receive_packet_checked, drop messages packets whichdst_reinit_date > d:

auto d = Adnl::adnl_start_time();if (packet.dst_reinit_date() > d) {  VLOG(ADNL_WARNING) << this << ": dropping IN message: too new our reinit date " << packet.dst_reinit_date();  return;}// ... later in same methodif (packet.dst_reinit_date() > 0 && packet.dst_reinit_date() < d) {    if (!packet.addr_list().empty()) {      auto addr_list = packet.addr_list();      if (packet.remote_addr().is_valid() && addr_list.size() == 0) {        VLOG(ADNL_DEBUG) << "adding implicit address " << packet.remote_addr();        addr_list.add_udp_address(packet.remote_addr());      }      update_addr_list(std::move(addr_list));    }    if (!packet.priority_addr_list().empty()) {      update_addr_list(packet.priority_addr_list());    }    VLOG(ADNL_NOTICE) << this << ": dropping IN message old our reinit date " << packet.dst_reinit_date()                      << " date=" << d;    auto M = OutboundAdnlMessage{adnlmessage::AdnlMessageNop{}, 0};    send_message(std::move(M));    return;  }

These two checks are basically dropping all values ofdst_reinit_date, at least they are0 ord. No comments on the code, or in any part about the rational of this. Keep this in mind when implementing a client, to avoid your packages been dropped for now reason.

Comunication mechanisms

First packet format

The communication between two peers through ADNL, doesn’t consists only on sharing serializedadnl.packetContents, given that these packets need to be encrypted in some way before sharing with the other peer. The approach for this encryption is using ECDH, checkoutRequirements section to look for practical examples of how ECDH is performed. Apart from the encryption peers also will need to check the autenticity of the packet sent. Given this, makes sense that the format of the first packet exchanged follow this structure:

0                                         31+-----------------------------------------+| SERVER KEY ID                           |+-----------------------------------------+| SENDER PUB KEY                          |+-----------------------------------------+| SHA256 CONTENT HASH (BEFORE ENCRYPTION) |                         ENCRYPTED_PACKET_DATA_SIZE+-------------------------------------------------------------------+| ENCRYPTED CONTENT OF THE PACKET                                   |+-------------------------------------------------------------------+

Server Key ID

Is the sha256 of the serialized TL-objectPublicKey of the server. This helps the server to identify which one of its public key were used in the generation of theshared key through ECDH. Let’s see this in a more details example:

Assume that the sender knows the public keys of receiver. Suppose the sender is using the server public keyPub1, which is a ed25519 Public Key. Given that is a ed25519 Public Key, this need to be serialized with the following TL definition:

pub.ed25519 key:int256 = PublicKey;

The serialization should include theCRC32 of the TL definition, this kind of serialization are calledboxed. In pseudo code this server key should be generated in this way:

SHA256(BOXED_SERIALIZER(PUBLIC_KEY_TL_OBJECT))

Sender Public Key

Given that the receiver of the packet also needs to generate theshared secret in order to decrypt the data, the sender must include its own public key.

Checksum of data before encryption

The receiver will need to perform a validation of the data integrity it just decrypted, for this purpose the sender should include:

SHA256(UNENCRYPTED_DATA)

Encrypted content

The last part is the encrypted content, which as previously specified should be a TL serialization ofadnl.packetContent. A full example will be provided later.

Channel

When a sender will be exchanging several messages with the receiver, keeping the previously described format for all the messages doesn’t makes sense given that contains redundant information like thesender public key which was known from the first message. In order to simplify this, ADNL use achannel mechanism, which will allow peers to exchange subsequents messages after the initial one to be simpler. The flow and idea is simple:

  1. For the creation of the channel, sender will need to generate a key pair of public and private key. Let’s call it,senderChannelPubKey andsenderChannelPrivKey.

  2. In the first message, on the fieldmessages the sender includes aadnl.createChannel message. Which TL definition is the following:

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

Reading the TL definition you can notice that akey:int256 is included, this will be asenderChannelPubKey that we generated on our previous step. This along with the other messages to be sent in this first exchange, will be serialized and sent to receiver peer in the format we described previously.

  1. As a response from the receiver, we should receive aconfirmChannel message, which TL definition is the following:
adnl.message.confirmChannel key:int256 peer_key:int256 date:int = adnl.Message;

This message confirm that the peer receiver agree on the creation of the channel, and provide us with akey:int256 which isreceiverPubKey andpeer_key:int256 which isourPubKey. These keys are necessary in order to create ashared secret key as the one we use for the encryption of the first packet, but this time with the channel keys. To be more specific, sender creates itsshared secret withsenderPrivKey andreceiverPubKey, and receiver creates itsshared secret withreceiverPrivKey andsenderPubKey. Thisshared secret is used for encryption and decryption.

  1. Having theshared secret, both peers need to make 2 keys from it, one forencrypting outgoing message and another for decryptingincomming messages. This is made by using theshared secret and thereversed shared secret. For example:
Shared secret: ABCDEncryption key of outgoing messages : ABCDDecryption of incomming messages: DCBA

Now we need some kind of coordination between the peers in order to know which of this two keys are been used for encryption and decryption on each peer. The coordination for this is done by making a numerical comparison between peers ids. For example:

SHARED SECRET: ABCDPEERA_ID < PEERB_ID:  FOR PEERA WE HAVE:    ENCRYPTION KEY: ABCD    DECRYPTION KEY: DCBA  FOR PEERB WE HAVE:    ENCRYPTION KEY: DCBA    DECRYPTION KEY: ABCDPEERA_ID > PEERB_ID:  FOR PEERA WE HAVE:    ENCRYPTION KEY: DCBA    DECRYPTION KEY: ABCD  FOR PEERB WE HAVE:    ENCRYPTION KEY: ABCD    DECRYPTION KEY: DCBAPEERA_ID == PEERB_ID:  FOR BOTH PEERS:    ENCRYPTION: ABCD    DECRYPTION: ABCD

An example in code can be found inAdnlChannel::create.

  1. From now on sender and receiver had stablished achannel on which they can communicate in a more simplistic format.

Channel data format

The format of the data sent on achannel doesn’t need to include the sender public key, also as a first 32-bytes peers will include anENCRYPTIOIN KEY ID.

0                                         31+-----------------------------------------+| ENCRYPTION KEY ID                       |+-----------------------------------------+| SHA256 CONTENT HASH (BEFORE ENCRYPTION) |                         ENCRYPTED_PACKET_DATA_SIZE+-------------------------------------------------------------------+| ENCRYPTED CONTENT OF THE PACKET                                   |+-------------------------------------------------------------------+

Encryption key id

Sender outgoing encryption key id,SHA256(BOXED_SERIALIZED(AES_PUBLICKEY_TL_OBJECT)).

Content of packet

The content of the packet follows as well aadnl.packetContent, but way more simplified including the following fields:

Field nameflag byte positiontypedescription
rand1_bytesrandom 7 or 15 bytes, why 7 or 15? No idea
flags_intflags marks which fields of the TL-object are present
message2adnl.Messagea single unit ofadnl.Message checkout differents types on TL definition
seqno6longsequence number of the packet, in case the receiver already proccessed a packet with this seqno, the packet will be dropped
confirm_seqno7longlatest confirmed sequence number, if confirm_seqno > out_seqno in the receiver node the packet will be dropped
rand2_bytesrandom 7 or 15 bytes

A more detailed example can be found in theofficial documentaion, and I’ll provide one with code here as well later.


[8]ページ先頭

©2009-2025 Movatter.jp