ADNL TCP - Liteserver
This is the low level protocol on which all interaction in the TON network is built, it can work on top of any protocol, but is most often used on top of TCP and UDP. UDP is used for communication between nodes, and TCP is used for communication with lite servers.
Now we will analyze ADNL running over TCP and learn how to interact with lite servers directly.
In the TCP version of ADNL, network nodes use public keys ed25519 as addresses and establish a connection using a shared key obtained using the Elliptic Curve Diffie-Hellman procedure - ECDH.
Packet Structure
Each ADNL TCP packet, except for the handshake, has the following structure:
- 4 bytes of packet size in little endian (N)
- 32 bytes nonce (random bytes to protect against checksum attacks)
- (N - 64) payload bytes
- 32 bytes SHA256 checksum from nonce and payload
The entire packet, including the size, is AES-CTR encrypted. After decryption, it is necessary to check whether the checksum matches the data, to check, you just need to calculate the checksum yourself and compare the result with what we have in the packet.
The handshake packet is an exception, it is transmitted in a partially unencrypted form and is described in the next chapter.
Establishing a connection
To establish a connection, we need to know the ip, port and public key of the server, and generate our own private and public key ed25519.
Public server data such as ip, port and key can be obtained from the global config. IP in the config in numerical form, it can be brought to normal form using, for example this tool. The public key in the config in base64 format.
The client generates 160 random bytes, some of which will be used by the parties as the basis for AES encryption.
Of these, 2 permanent AES-CTR ciphers are created, which will be used by the parties to encrypt/decrypt messages after the handshake.
- Cipher A - key 0 - 31 bytes, iv 64 - 79 bytes
- Cipher B - key 32 - 63 bytes, iv 80 - 95 bytes
The ciphers are applied in this order:
- Cipher A is used by the server to encrypt the messages it sends.
- Cipher A is used by the client to decrypt received messages.
- Cipher B is used by the client to encrypt the messages it sends.
- Cipher B is used by the server to decrypt received messages.
To establish a connection, the client must send a handshake packet containing:
- [32 bytes] Server key ID [Details]
- [32 bytes] Our public key is ed25519
- [32 bytes] SHA256 hash from our 160 bytes
- [160 bytes] Our 160 bytes encrypted [Details]
When receiving a handshake packet, the server will do the same actions, receive an ECDH key, decrypt 160 bytes and create 2 permanent keys. If everything works out, the server will respond with an empty ADNL packet, without payload, to decrypt which (as well as subsequent ones) we need to use one of the permanent ciphers.
From this point on, the connection can be considered established.
After we have established a connection, we can start receiving information; the TL language is used to serialize data.
Ping&Pong
It is optimal to send a ping packet once every 5 seconds. This is necessary to maintain the connection while no data is being transmitted, otherwise the server may terminate the connection.
The ping packet, like all the others, is built according to the standard schema described above, and carries the request ID and ping ID as payload data.
Let's find the desired schema for the ping request here and calculate the schema id as
crc32_IEEE("tcp.ping random_id:long = tcp.Pong")
. When converted to little endian bytes, we get 9a2b084d.
Thus, our ADNL ping packet will look like this:
- 4 bytes of packet size in little endian -> 64 + (4+8) = 76
- 32 bytes nonce -> random 32 bytes
- 4 bytes of ID TL schema -> 9a2b084d
- 8 bytes of request id -> random uint64 number
- 32 bytes of SHA256 checksum from nonce and payload
We send our packet and wait for tcp.pong, random_id
will be equal to the one we sent in ping packet.