Each encrypted packet has the following header:
@verbatim
- +------------ HEADER --------------------+ +------------- ENCRYPTED -------------+
- / \ / \
-+--------------------------------------------+------+---------+----------...---+------+
-| MAGIC | NICE | SENDER | RCPT | EPUB | SIGN | SIZE | BLOCK 0 | BLOCK 1 ... | JUNK |
-+-------------------------------------/------\------+---------+----------...---+------+
+ +------------ HEADER --------------------+ +------ ENCRYPTED -----+
+ / \ / \
++--------------------------------------------+---------+----------...---+-----...--+
+| MAGIC | NICE | SENDER | RCPT | EPUB | SIGN | BLOCK 0 | BLOCK 1 ... | OPAD |
++-------------------------------------/------\---------+----------...---+-----...--+
/ \
+-------------------------------------+
| MAGIC | NICE | SENDER | RCPT | EPUB |
@headitem @tab XDR type @tab Value
@item Magic number @tab
8-byte, fixed length opaque data @tab
- @verb{|N N C P E 0x00 0x00 0x05|}
+ @verb{|N N C P E 0x00 0x00 0x06|}
@item Niceness @tab
unsigned integer @tab
1-255, packet @ref{Niceness, niceness} level
ed25519 signature for that packet's header over all previous fields.
@end multitable
-All following encryption is done in AEAD mode using
+Each @code{BLOCK} is AEAD-encrypted 128 KiB data. Last block can have
+smaller size. They are encrypted in AEAD mode using
@url{https://cr.yp.to/chacha.html, ChaCha20}-@url{https://en.wikipedia.org/wiki/Poly1305, Poly1305}
algorithms. Authenticated data is BLAKE3-256 hash of the unsigned
-portion of the header (the same data used in the signature). Size is
-XDR-encoded unsigned hyper integer, carrying the payload size, encrypted
-as a single AEAD-block (with the tag) independently from the following
-blocks. It is encoded with the zero nonce.
+portion of the header (the same data used in the signature). Nonce is
+block's sequence number (64-bit integer starting at 0).
-Payload with possible padding is divided on 128 KiB blocks blocks. They
-are encrypted with the same authenticated data and increasing big-endian
-64-bit nonce, starting at 1.
+Concatenated plaintext of those blocks hold the following stream of data:
+
+@verbatim
++-----------+--------+---------------------+--------+
+| PAYLOAD | SIZE | REST (OF PAYLOAD) | IPAD |
++-----------+--------+---------------------+--------+
+ ^
+ |
+ +-- always aligned to the beginning of block
+@end verbatim
+
+Where @code{SIZE} is following XDR structure:
+
+@multitable @columnfractions 0.2 0.3 0.5
+@headitem @tab XDR type @tab Value
+@item Payload @tab
+ unsigned hyper integer @tab
+ Full payload size. @code{len(PAYLOAD) + len(REST)}
+@item Pad @tab
+ unsigned hyper integer @tab
+ Full padding size. @code{len(IPAD) + len(OPAD)}
+@end multitable
+
+@code{SIZE} is always at the beginning of the block. So payload and rest
+of it have variable length. Block containing @code{SIZE} is encrypted
+with the different key (@code{key=size}), to distinguish it from the
+"ordinary" ones (@code{key=full}).
+
+@code{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 @code{PAYLOAD} or @code{REST} of it of
+zero length. For example:
+
+@verbatim
++------+-------------+
+| 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
+@end verbatim
+
+@code{OPAD} is appended if @code{IPAD} (inside the block) has not enough
+length. @code{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 @strong{exchange} and @strong{signature} keypairs.
When node A want to send encrypted packet to node B, it:
@item takes remote node's exchange public key and performs
Diffie-Hellman computation on this remote static public key and
private ephemeral one
-@item derives 32-bytes AEAD encryption key with BLAKE3 derivation
- function. Source key is the derived ephemeral key. Context is
- @verb{|N N C P E 0x00 0x00 0x05|} magic number
+@item derives three keys using BLAKE3 derivation function from the
+ curve25519-derived ephemeral source key:
+ @itemize
+ @item @code{key=full} with the context of:
+ @verb{|N N C P E 0x00 0x00 0x06 <SP> F U L L|}
+ @item @code{key=size} with the context of:
+ @verb{|N N C P E 0x00 0x00 0x06 <SP> S I Z E|}
+ @item @code{key=pad} with the context of:
+ @verb{|N N C P E 0x00 0x00 0x06 <SP> P A D|}
+ @end itemize
@item calculates authenticated data: it is BLAKE3-256 hash of the
unsigned header (same used for signing)
-@item encrypts size, appends its authenticated ciphertext to the header
- (with authenticated data, nonce=0)
-@item encrypts each payload block, appending its authenticated ciphertext
- (with authenticated data, nonce starting at 1, increasing with each block)
-@item possibly appends any kind of "junk" noise data to hide real
- payload's size from the adversary (generated using BLAKE3 XOF, with
- the key derived from the ephemeral one and context string of
- @verb{|N N C P E 0x00 0x00 0x05 <SP> P A D|})
+@item reads the payload by 128 KiB chunks. If it is enough data to fill
+ the entire 128 KiB block, then encrypt the chunk with
+ @code{key=full} key
+@item if there is not enough data, then payload is reaching the end.
+ @itemize
+ @item prepend @code{SIZE} structure to the finishing chunk of data.
+ All sizes at that time are known
+ @item produce block with @code{SIZE} even if there is no payload
+ data left
+ @item append remaining payload to the @code{SIZE}, if it is left
+ @item if there is padding, then fill current block to the end with
+ zeros (@code{IPAD})
+ @item encrypt the block with @code{key=size} key
+ @end itemize
+@item if there is more padding left (@code{OPAD}), then generate it with
+ BLAKE3 XOF function using the @code{key=pad} key
@end enumerate