Compression and Ciphertext Size
August 25, 20265 min readbeginner
Ciphertexts travel on the wire, so their size is a real cost paid on every connection. ML-KEM throws away low-order bits deliberately, and the error budget from…
Ciphertexts travel on the wire, so their size is a real cost paid on every connection. ML-KEM throws away low-order bits deliberately, and the error budget from Why Decryption Works is what says how many it can afford.
01.The operation
The half-square brackets denote rounding to the nearest integer.
Read it plainly. Compression rescales a value from the range down to and rounds. Decompression rescales back. The round trip does not return the original, because information was discarded, but it returns something close.
Concretely with and , the whole range of possible coefficient values is squeezed into buckets, each about wide. Decompressing returns the centre of the bucket, so the error is at most about .
02.Where the widths come from
The two parts of the ciphertext are compressed differently: bits per coefficient for , and for . That is a factor of sixty-four difference in how much is thrown away, and the reason is in the error equation.
Recall the two extra terms compression added:
The noise on arrives multiplied by the secret . That multiplication is a full ring product, summing terms, so even though is small the product accumulates. Compression noise on is therefore amplified before it reaches the budget, and has to be treated carefully.
The noise on arrives alone. It is added once, at whatever size it is, and nothing multiplies it. So can be compressed far harder for the same cost to the budget.
Hence and . It is a nice illustration of how a scheme's constants are not arbitrary: each one is the answer to an inequality.
03.The saving
Uncompressed, every coefficient needs 12 bits, since needs 12. For ML-KEM-768, an uncompressed ciphertext would be
Compressed, it is
A saving of bytes, just under thirty percent, on every ciphertext ever sent.
Across all three parameter sets:
| uncompressed | compressed | saved | |
|---|---|---|---|
| ML-KEM-512 | 1152 B | 768 B | 384 B |
| ML-KEM-768 | 1536 B | 1088 B | 448 B |
| ML-KEM-1024 | 1920 B | 1568 B | 352 B |
ML-KEM-1024 saves less proportionally because it uses gentler compression, and . At the highest security level the error budget is tighter relative to the noise, so less can be discarded.
04.Why the public key is not compressed
The public key is bytes for ML-KEM-768 and is sent uncompressed. Given that ciphertexts save thirty percent, the obvious question is why keys do not.
Two reasons, and the second is the real one.
The public key is stored in NTT domain, as Key Generation explained. Transformed coefficients are uniform across the full range with no structure to exploit, and compressing them would discard bits that the arithmetic genuinely needs.
More fundamentally, compressing the public key would inject rounding error into , which appears in the correctness equation on the key generation side. Bob's secret cannot compensate for it, because Bob does not know which way each coefficient was rounded and cannot recover that from . Ciphertext compression works precisely because its error lands in where the budget absorbs it. Public key compression has no such landing place.
There is also an asymmetry of usage that makes the trade less attractive anyway. A public key is fetched once and cached, often inside a certificate that is itself several kilobytes. A ciphertext is sent on every single connection. Optimising the thing that travels constantly is worth more than optimising the thing that travels once.
05.The encoding, briefly
One implementation detail that causes trouble in practice. Compressed coefficients are bits wide and is not a multiple of , so the serialised ciphertext is a densely packed bitstream rather than a byte array. Ten-bit values pack four to every five bytes.
FIPS 203 specifies the packing exactly, down to bit order, because two implementations that pack differently produce different ciphertexts from identical inputs and fail to interoperate. This is the kind of detail that has no mathematical content and consumes real engineering time, and it is worth knowing it is there before meeting it in a test vector mismatch.