chat: length buckets and plaintext padding #184
Loading…
Reference in a new issue
No description provided.
Delete branch "wt/t_3c0798f2"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Chat plaintext is now padded to a fixed length ladder inside the ratchet
AEAD, so the framed
uint32 lengthreveals a bucket instead of the exactmessage size. The padding is authenticated by ChaCha20-Poly1305 and by the
Noise session around it.
Ladder (padded-plaintext bytes): 256, 512, 1024, 2048, 4096, 8192, 16384, 32768.
Plaintext encoding:
The total is the smallest ladder value at least
2 + body_len, sopad_len = bucket - 2 - body_len; empty and short bodies both collapse to 256,and an exact fit has
pad_len = 0. A body over 32766 bytes fails closed beforethe outbox.
Receiver: only
pad_lenis authoritative. No trailing-zero trimming and nocontent-based boundary inference, so a body ending in the same byte as the fill
round-trips. A plaintext shorter than 2 bytes or with
pad_lenoverrunninglen - 2fails closed before any row is appended or the ratchet state is saved.Total length is not required to be a ladder value, for interop; unpadded frames
do not decode as an empty bucket, they fail the
pad_lenbound.Real minimum observable frame
ratframe overhead is 73 bytes (prefix 4 + version 1 + header 40 + nonce 12 +tag 16); with the 256-byte bucket that is a 329-byte ratchet frame. Noise adds
its 16-byte tag and framing adds 5 bytes, so the smallest chat frame on the
wire is 350 bytes. Largest is 32862 bytes, under the 65535 cap.
Card premise correction: the ML-KEM-768 ciphertext does not ride in the
ratheader. It is the one-shot handshake prelude (version = 2, 1095 byteson the wire, once per session), and ratchet keys in a chat frame are still
X25519 (32 bytes). So the hybrid transport does not widen this floor. Extending
the 40-byte header to a PQ ratchet key would be a separate wire change.
filchunks are deliberately not padded: padding would either cut theusable chunk size or spend the 65535 frame cap on fill. Their size still leaks.
What still leaks
Timing, retry cadence, the one-shot-per-message circuits and handshake timing
are unchanged; no cover traffic or jitter. Non-chat frames (
loc,prf,inv,rcp,tip,fil) are still exact-sized.Tests
the_ladder_is_the_documented_one,buckets_follow_the_body_plus_the_two_byte_header(tests/padding.rs) — ladder and boundary table, inclusive of 254/255 and 32766/32767.chat_frames_are_padded_to_the_normative_length_ladder(src/node.rs) — frame length is 329 for empty/1/254-byte bodies and 585 for 255 bytes.a_one_byte_chat_round_trips_in_the_smallest_bucket,an_oversize_chat_send_fails_closed_before_the_outbox,a_hybrid_rat_frame_round_trips_inside_the_smallest_bucket(src/node.rs).a_chat_body_ending_in_the_pad_fill_byte_round_trips(src/node.rs + tests/padding.rs) — body boundary comes frompad_len, never from fill contents.a_pad_len_that_overruns_the_plaintext_writes_no_chat_row(src/node.rs) — a validly authenticated frame with an overrunningpad_lenis rejected and writes nothing.a_pad_len_that_overruns_the_plaintext_is_rejected,a_truncated_padding_header_is_rejected,a_receiver_accepts_a_padded_plaintext_of_any_total_length(tests/padding.rs).a_snapshot_heals_only_once_the_ratchet_key_it_held_has_been_replaced(tests/ratchet_adversarial.rs) now unpads the rawRatchetStatedecrypt it inspects; the property it proves is unchanged.Verification:
cargo test --lockedexit 0 (38 targets, all green),cargo clippy --locked --all-targets -- -D warningsclean.Docs
docs/PROTOCOL.mdnew normative Length buckets section plus framing and ratchet cross-references;docs/THREAT_MODEL.md's no-padding bullet replaced with bucketed-length / still-observable-timing wording;README.mdone sentence in Chat ratchet.