Next: , Previous: , Up: Packet format   [Index]


Encrypted packet

Encrypted packets are the only files found in spools, in exchangeable storages and that are synchronized between TCP daemons.

Each encrypted packet has the following header:

  +------------ HEADER --------------------+   +------ ENCRYPTED -----+
 /                                          \ /                        \
+--------------------------------------------+---------+----------...---+-----...--+
| MAGIC | NICE | SENDER | RCPT | EPUB | SIGN | BLOCK 0 | BLOCK 1  ...   |   OPAD   |
+-------------------------------------/------\---------+----------...---+-----...--+
                                     /        \
                      +-------------------------------------+
                      | MAGIC | NICE | SENDER | RCPT | EPUB |
                      +-------------------------------------+
XDR typeValue
Magic number8-byte, fixed length opaque dataN N C P E 0x00 0x00 0x06
Nicenessunsigned integer1-255, packet niceness level
Sender32-byte, fixed length opaque dataSender node’s id
Recipient32-byte, fixed length opaque dataRecipient node’s id
Exchange public key32-byte, fixed length opaque dataEphemeral curve25519 public key
Signature64-byte, fixed length opaque dataed25519 signature for that packet’s header over all previous fields.

Each BLOCK is AEAD-encrypted 128 KiB data. Last block can have smaller size. They are encrypted in AEAD mode using ChaCha20-Poly1305 algorithms. Authenticated data is BLAKE3-256 hash of the unsigned portion of the header (the same data used in the signature). Nonce is block’s sequence number (64-bit integer starting at 0).

Concatenated plaintext of those blocks hold the following stream of data:

+-----------+--------+---------------------+--------+
|  PAYLOAD  |  SIZE  |  REST (OF PAYLOAD)  |  IPAD  |
+-----------+--------+---------------------+--------+
            ^
            |
            +-- always aligned to the beginning of block

Where SIZE is following XDR structure:

XDR typeValue
Payloadunsigned hyper integerFull payload size. len(PAYLOAD) + len(REST)
Padunsigned hyper integerFull padding size. len(IPAD) + len(OPAD)

SIZE is always at the beginning of the block. So payload and rest of it have variable length. Block containing SIZE is encrypted with the different key (key=size), to distinguish it from the "ordinary" ones (key=full).

IPAD contains zeros and is shorter than single block. Padding is fully optional and is used only to hide the payload full size.

It is acceptable to have either PAYLOAD or REST of it of zero length. For example:

+------+-------------+
| SIZE | PAYLOAD ... |
+------+-------------+
 \------ BLOCK -----/
         key=size

+------+-------------+------+
| SIZE | PAYLOAD ... | IPAD |
+------+-------------+------+
 \--------- BLOCK --------/
            key=size

+--------------------------+    +------+-------------------+
|          PAYLOAD         | .. | SIZE | IPAD ...           |
+--------------------------+    +------+-------------------+
 \--------- BLOCK --------/      \--------- BLOCK --------/
            key=full                        key=size

+--------------------------+    +------+-------------------+
|          PAYLOAD         | .. | SIZE | PAYLOAD ...       |
+--------------------------+    +------+-------------------+
 \--------- BLOCK --------/      \--------- BLOCK --------/
            key=full                        key=size

+--------------------------+    +------+-------------+------+
|          PAYLOAD         | .. | SIZE | PAYLOAD ... | IPAD |
+--------------------------+    +------+-------------+------+
 \--------- BLOCK --------/      \--------- BLOCK --------/
            key=full                        key=size

+--------------------------+    +------+-------------------+    +--------------------------+
|          PAYLOAD         | .. | SIZE | PAYLOAD ...       | .. | PAYLOAD ...              |
+--------------------------+    +------+-------------------+    +--------------------------+
 \--------- BLOCK --------/      \--------- BLOCK --------/      \--------- BLOCK --------/
            key=full                        key=size                        key=full

+--------------------------+    +------+-------------------+    +-------------+-------------+
|          PAYLOAD         | .. | SIZE | PAYLOAD ...       | .. | PAYLOAD ... | IPAD ...    |
+--------------------------+    +------+-------------------+    +-------------+------------+
 \--------- BLOCK --------/      \--------- BLOCK --------/      \--------- BLOCK --------/
            key=full                        key=size                        key=full

OPAD is appended if IPAD (inside the block) has not enough length. OPAD is just an output of the XOF function. No encryption and explicit authentication is applied to it. XOF is just faster and can be computed deterministically on both ends – you just have to authenticate its length.

Each node has static exchange and signature keypairs. When node A want to send encrypted packet to node B, it:

  1. generates ephemeral curve25519 keypair
  2. prepares structure for signing
  3. signs that structure using private ed25519 signature key
  4. takes remote node’s exchange public key and performs Diffie-Hellman computation on this remote static public key and private ephemeral one
  5. derives three keys using BLAKE3 derivation function from the curve25519-derived ephemeral source key:
    • key=full with the context of: N N C P E 0x00 0x00 0x06 <SP> F U L L
    • key=size with the context of: N N C P E 0x00 0x00 0x06 <SP> S I Z E
    • key=pad with the context of: N N C P E 0x00 0x00 0x06 <SP> P A D
  6. calculates authenticated data: it is BLAKE3-256 hash of the unsigned header (same used for signing)
  7. reads the payload by 128 KiB chunks. If it is enough data to fill the entire 128 KiB block, then encrypt the chunk with key=full key
  8. if there is not enough data, then payload is reaching the end.
    • prepend SIZE structure to the finishing chunk of data. All sizes at that time are known
    • produce block with SIZE even if there is no payload data left
    • append remaining payload to the SIZE, if it is left
    • if there is padding, then fill current block to the end with zeros (IPAD)
    • encrypt the block with key=size key
  9. if there is more padding left (OPAD), then generate it with BLAKE3 XOF function using the key=pad key

Next: Encrypted area packet, Previous: Plain packet, Up: Packet format   [Index]