feat(firmware): MeshCore text TX — send encrypted DMs to contacts
- sendMeshCoreText: plaintext (timestamp + flags + text) -> encryptThenMAC (AES-128-ECB zero-padded + 2-byte HMAC-SHA256 prefix), wrapped as [header route=flood|type=TXT_MSG][path_len 0][dest_hash][src_hash][cipher] - serial "C:msg" now DMs the text to every heard MeshCore contact - verified end-to-end: bridge DM decrypts to plaintext on a real node Completes bidirectional MeshCore messaging (advert TX + DM RX + DM TX). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
38c146c477
commit
8939b9524f
@ -384,6 +384,48 @@ static void sendMeshCoreAdvert() {
|
|||||||
radio.startReceive();
|
radio.startReceive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send an encrypted MeshCore direct message to a contact (Ed25519 pubkey).
|
||||||
|
// Mirror of the RX path: plaintext = timestamp(4) + flags(1) + text, then
|
||||||
|
// encryptThenMAC = HMAC-SHA256(2) prefix + AES-128-ECB(zero-padded plaintext),
|
||||||
|
// wrapped as [header][path_len 0][dest_hash][src_hash][cipher].
|
||||||
|
static void sendMeshCoreText(const uint8_t* dest_pub, const char* text) {
|
||||||
|
uint32_t ts = mcNow();
|
||||||
|
uint8_t plain[176]; size_t pl = 0;
|
||||||
|
memcpy(plain + pl, &ts, 4); pl += 4;
|
||||||
|
plain[pl++] = 0x00; // flags: attempt 0, TXT_TYPE_PLAIN
|
||||||
|
size_t tl = strlen(text); if (tl > 140) tl = 140;
|
||||||
|
memcpy(plain + pl, text, tl); pl += tl;
|
||||||
|
|
||||||
|
uint8_t secret[32];
|
||||||
|
ed25519_key_exchange(secret, dest_pub, mcPrv); // ECDH shared secret
|
||||||
|
|
||||||
|
// AES-128-ECB over the plaintext, zero-padded to a 16-byte boundary.
|
||||||
|
uint8_t cipher[2 + 176 + 16]; uint8_t* enc = cipher + 2; size_t el = 0;
|
||||||
|
AES128 aes; aes.setKey(secret, 16);
|
||||||
|
size_t off = 0;
|
||||||
|
while (pl - off >= 16) { aes.encryptBlock(enc + el, plain + off); el += 16; off += 16; }
|
||||||
|
if (pl - off > 0) {
|
||||||
|
uint8_t tmp[16]; memset(tmp, 0, 16); memcpy(tmp, plain + off, pl - off);
|
||||||
|
aes.encryptBlock(enc + el, tmp); el += 16;
|
||||||
|
}
|
||||||
|
// 2-byte truncated HMAC-SHA256 over the ciphertext (key = 32-byte secret).
|
||||||
|
SHA256 sha; sha.resetHMAC(secret, 32); sha.update(enc, el);
|
||||||
|
sha.finalizeHMAC(secret, 32, cipher, 2);
|
||||||
|
size_t clen = 2 + el;
|
||||||
|
|
||||||
|
uint8_t pkt[4 + sizeof(cipher)]; size_t p = 0;
|
||||||
|
pkt[p++] = 0x0A; // route=flood(2) | type=TXT_MSG(2)
|
||||||
|
pkt[p++] = 0x00; // path_len = 0
|
||||||
|
pkt[p++] = dest_pub[0]; // dest_hash
|
||||||
|
pkt[p++] = mcPub[0]; // src_hash
|
||||||
|
memcpy(pkt + p, cipher, clen); p += clen;
|
||||||
|
|
||||||
|
radio.standby();
|
||||||
|
int st = radio.transmit(pkt, p);
|
||||||
|
Serial.printf(" [C] TX dm -> tx=%d (%u bytes) : \"%s\"\n", st, (unsigned)p, text);
|
||||||
|
radio.startReceive();
|
||||||
|
}
|
||||||
|
|
||||||
// --- MeshCore contacts (heard adverts) + DM receive ------------------------
|
// --- MeshCore contacts (heard adverts) + DM receive ------------------------
|
||||||
// To decrypt a direct message we need the SENDER's full public key. MeshCore
|
// To decrypt a direct message we need the SENDER's full public key. MeshCore
|
||||||
// packets only carry a 1-byte src_hash (pubkey prefix), so we remember pubkeys
|
// packets only carry a 1-byte src_hash (pubkey prefix), so we remember pubkeys
|
||||||
@ -818,8 +860,14 @@ static void flushTxOnWindow() {
|
|||||||
|
|
||||||
if (L == 'T') {
|
if (L == 'T') {
|
||||||
sendMeshtasticText(txText);
|
sendMeshtasticText(txText);
|
||||||
|
} else if (L == 'C') {
|
||||||
|
// DM the text to every MeshCore contact we've heard an advert from.
|
||||||
|
int sent = 0;
|
||||||
|
for (int c = 0; c < MC_MAX_CONTACTS; c++)
|
||||||
|
if (mcContacts[c].used) { sendMeshCoreText(mcContacts[c].pub, txText); sent++; }
|
||||||
|
if (sent == 0) Serial.printf(" [C] no known contacts yet — nobody to DM\n");
|
||||||
} else {
|
} else {
|
||||||
// MeshCore / Reticulum TX framing not built yet — acknowledge, don't fake it.
|
// Reticulum TX not built yet — acknowledge, don't fake it.
|
||||||
Serial.printf(" [%c] TX not implemented yet (msg: \"%s\")\n", L, txText);
|
Serial.printf(" [%c] TX not implemented yet (msg: \"%s\")\n", L, txText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user