### 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 type | Value
--- | --- | ---
Magic number | 8-byte, fixed length opaque data | `N N C P E 0x00 0x00 0x06`
Niceness | unsigned integer | 1-255, packet niceness level
Sender | 32-byte, fixed length opaque data | Sender node’s id
Recipient | 32-byte, fixed length opaque data | Recipient node’s id
Exchange public key | 32-byte, fixed length opaque data | Ephemeral curve25519 public key
Signature | 64-byte, fixed length opaque data | ed25519 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 type | Value
--- | --- | ---
Payload | unsigned hyper integer | Full payload size. `len(PAYLOAD) + len(REST)`
Pad | unsigned hyper integer | Full 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