Add ChaChaPoly AEAD-4 decryption support (Phase 1) #1677
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
Adds ChaCha20-Poly1305 (AEAD-4) encryption alongside the existing AES-128-ECB + HMAC-2 scheme. Updated nodes send AEAD-4 to peers that advertise support and fall back to ECB for legacy peers. All nodes can decode both formats. Old nodes continue to work unchanged.
Relates to #259.
What This Means in Practical Terms
The current encryption has a few weaknesses that this PR begins to address:
Message tampering is too easy to attempt. The existing 2-byte authentication code means an attacker only needs about 65,000 guesses to forge a valid-looking message. At LoRa speeds that's roughly 9 hours of continuous attempts. The new 4-byte tag raises this to over 4 billion guesses — at LoRa rates, that would take over a century.
Identical messages look identical on the air. The current block cipher (ECB mode) produces the same ciphertext for the same plaintext, which can reveal patterns — for example, you could tell when someone sends the same message twice. The new scheme produces completely different ciphertext every time, even for identical messages.
Addressing fields are now protected. Currently, only the message body is authenticated. With AEAD, the payload type and addressing hashes (which identify sender and recipient) are included in the authentication check, so an attacker cannot swap or modify them without detection. Outer routing fields like TTL and hop path are intentionally left unauthenticated so repeaters can still forward packets through the mesh.
Messages get slightly smaller. ECB pads every message up to a 16-byte boundary, wasting airtime. The new scheme has no padding, so most messages shrink by a few bytes on the wire.
Nothing breaks. Updated nodes send AEAD-4 to peers that advertise support, and fall back to ECB for legacy peers. Old nodes are completely unaffected — they never receive AEAD-4 messages because the sender checks their capability first.
Nodes advertise their capabilities. Updated nodes include a flag in their advertisements saying "I understand the new encryption." When two updated nodes discover each other, they automatically start using AEAD-4 for their communication.
Wire Format
Current ECB:
New AEAD-4 (same position in payload):
Average overhead: ~6 bytes (AEAD) vs ~9.5 bytes (ECB). Most messages get smaller.
Cryptographic Design
Per-message key derivation (eliminates nonce-reuse catastrophe):
Including
dest_hash || src_hashmakes keys direction-dependent — Alice→Bob and Bob→Alice derive different keys even with the same nonce value (for 255/256 peer pairs; the 1/256 where dest_hash == src_hash is a residual limitation of 1-byte hashes).IV construction (12 bytes, from on-wire fields):
Associated data (authenticated but not encrypted):
header || dest_hash || src_hashheader || dest_hashheader || channel_hashNonce management: 16-bit counter per peer, seeded from hardware RNG on boot and on contact load. Not persisted to flash — always fresh on each boot cycle.
Security Comparison
memcmp(timing side-channel)secure_compare(constant-time)Scope
Group Message Considerations
Group channels share a single key among all members. With a 2-byte nonce and multiple senders, cross-sender nonce collisions follow the birthday bound (~300 messages for 50% probability on an active channel). A collision leaks
P1 ⊕ P2for that specific message pair via crib-dragging, but:This is mainly beneficial for public/hashtag channels where the PSK is already widely known and the ECB pattern leakage and weak MAC are a greater concern than the bounded nonce collision risk.
Potential future mitigations explored and deferred:
HMAC(channel_secret, sender_pub_key)) — eliminates cross-sender collisions but requires receivers to know all senders' public keys, changing the group security model from "know the PSK = full access" to "know the PSK + sender discovery = access." Ruled out as a usability regression.Decode Order
Adaptive per-peer: for peers with
CONTACT_FLAG_AEADset, try AEAD-4 first then ECB fallback. For unknown/legacy peers, try ECB first then AEAD-4 fallback. This avoids the 1/65536 ECB false-positive rate on AEAD packets (nonce bytes matching truncated HMAC) for known AEAD peers, while minimizing wasted CPU for legacy peers.Capability Advertisement
feat1bit 0 (FEAT1_AEAD_SUPPORT) is set in adverts for all node types (chat, repeater, room, sensor)ContactInfo.flagsbit 1 (CONTACT_FLAG_AEAD)feat1but ignore the value (forward-compatible via existingAdvertDataParser)Files Changed
src/MeshCore.h— AEAD constants (AEAD_TAG_SIZE,AEAD_NONCE_SIZE,CONTACT_FLAG_AEAD,FEAT1_AEAD_SUPPORT)src/Utils.h/src/Utils.cpp—aeadEncrypt()andaeadDecrypt()using ChaChaPolysrc/Mesh.h—getPeerFlags(),getPeerNextAeadNonce()virtuals;aead_nonceparam oncreateDatagram/createPathReturnsrc/Mesh.cpp— AEAD send path increateDatagram/createPathReturn; adaptive try-both decode order per peersrc/helpers/ContactInfo.h—uint16_t aead_noncefield,nextAeadNonce()helpersrc/helpers/BaseChatMesh.h/BaseChatMesh.cpp— Advertise AEAD, track peer capability, seed nonce, AEAD send for all peer message typessrc/helpers/CommonCLI.cpp— Advertise AEAD for repeaters/rooms/sensorsBuild Verification
Heltec_v3_companion_radio_ble): builds successfullyXiao_nrf52_companion_radio_ble): builds successfullyFuture Work
examples/callers still use ECB)