Add a "Multipart ACK Usage Constraints" subsection making explicit
what upstream enforces in code but the spec previously left implicit:
- MULTIPART-wrapped ACKs MUST only be emitted on direct-routed return
paths. When no direct path is known, the sender MUST fall back to a
plain PAYLOAD_TYPE_ACK via flood. Matches sendAckTo() and
routeDirectRecvAcks() in upstream.
- All MULTIPART and plain copies of the same ACK carry an identical
4-byte ack_crc; receivers MUST dedup by CRC at the application
layer (Section 16 packet-hash dedup does not catch them because the
wrapper byte makes each copy hash-distinct).
- Typical inter-copy delay ~300 ms plus the direct retransmit delay;
exact count via getExtraAckTransmitCount() (reference default 0).
Validated against upstream meshcore-dev/MeshCore @ 1a7b361
(Mesh::routeDirectRecvAcks, BaseChatMesh::sendAckTo).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two additions to the TXT_MSG section:
1. Attempt Counter Semantics. Spells out that bits 0-1 of
txt_type_attempt carry attempt & 3, the input the ACK CRC depends
on. For attempt > 3, upstream appends a [NUL][attempt_full] tail
after the text (BaseChatMesh.cpp:410-412), recovering the full
attempt value at the cost of 2 bytes of text capacity. The tail is
NOT part of the ACK CRC input, so attempt=4 and attempt=0 collide
on CRC and senders relying on >3 must verify the trailing byte.
2. Reliable DM Delivery. Documents the sender-side ACK tracking
contract: maintain a pending-ACK table keyed by expected CRC,
retry with attempt incremented, consume MULTIPART-wrapped or plain
ACKs identically, and the receive-side option to cache replies per
peer for duplicate-DM loss recovery.
Validated against upstream meshcore-dev/MeshCore @ 1a7b361
(BaseChatMesh::composeMsgPacket, onPeerDataRecv TXT_MSG handling).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spell out the SHA-256-truncation algorithm previously described in
prose:
ack_crc = SHA-256(plaintext_prefix || sender_pub_key)[0..3]
where plaintext_prefix = timestamp(4) || txt_type_attempt(1) || text
(excluding any trailing NUL terminator). Add the SIGNED_PLAIN variant
(includes 4-byte signature prefix, hashes against receiver's pub_key).
Reference the upstream functions that implement each side.
Resolves the ACK CRC vagueness called out in the 2026-04-01 audit.
Paired with the new corpus/crypto/sha256/ack-crc.json vectors.
Validated against upstream meshcore-dev/MeshCore @ 1a7b361
(BaseChatMesh.cpp:222, 249, 407; Utils::sha256 two-fragment form).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add four SHA-256-truncation vectors covering ACK CRC computation with
incrementing attempt values (0..3) over a stable (timestamp, text,
sender_pub_key) triple. Demonstrates that each retry produces a
distinct expected CRC, fixing the algorithmic ambiguity noted in the
2026-04-01 audit.
Algorithm: SHA-256(timestamp(4) || txt_type_attempt(1) || text ||
sender_pub_key(32))[0..3] as little-endian uint32.
Validated against upstream meshcore-dev/MeshCore @ 1a7b361
(BaseChatMesh::composeMsgPacket, src/helpers/BaseChatMesh.cpp:407).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>