NONCE = 64bit(ZEROS) || 64bit(MAC(MAC_KEY, SERIAL)) PAYLOAD = DATA || PAD [|| ZEROS] CIPHERTEXT = ENCRYPT(KEY, NONCE, PAYLOAD) TAG = AUTH(AUTH_KEY, CIPHERTEXT || NONCE) MESSAGE = TAG || CIPHERTEXT || NONCE
SERIAL is message’s serial number. Odds are reserved for
client (to server) messages, evens for server (to client) messages.
MAC is BLAKE2b-MAC used to obfuscate
SERIAL. MAC’s key
MAC_KEY is the first 256-bit of ChaCha20’s output with established
common key and zero nonce (message nonces start from 1).
MAC_KEY = 256bit(ENCRYPT(KEY, 0))
ENCRYPT is ChaCha20 stream cipher, with established session
KEY and obfuscated
SERIAL used as a nonce. 512 bit of
ChaCha20’s output is ignored and only remaining is XORed with ther data,
DATA is padded using ISO/IEC 7816-4 format (
byte) with optional
ZEROS following), to fill up packet to
conceal payload packet length.
AUTH is Poly1305 authentication function. First 256 bits of
ChaCha20’s output are used as a one-time key for
AUTH_KEY = 256bit(ENCRYPT(KEY, NONCE))
To prevent replay attacks we must remember received
drop when receiving duplicate ones.
In encryptionless mode this scheme is slightly different:
NONCE = MAC(MAC_KEY, SERIAL) ENCODED = ENCLESS(DATA || PAD || ZEROS) PACKET = ENCODED || NONCE
ENCLESS is AONT and chaffing function. There is no need in
explicit separate authentication.