beacon.h (3478B)
1 #pragma once 2 #include <Arduino.h> 3 #include <string.h> 4 #include <stddef.h> 5 #include "mbedtls/md.h" // mbedTLS ships inside the ESP32 Arduino core 6 #include "config.h" 7 #include "pet.h" // PET_FACE_MAX — beacons carry the sender's pet face 8 9 // ───────────────────────────────────────────────────────────────────────── 10 // Beacon wire format (fixed size, broadcast over ESP-NOW ~1/sec). 11 // 12 // The signed region is everything before `tag`. The receiver recomputes 13 // HMAC-SHA256(PSK, signed_region) and constant-time-compares against `tag`. 14 // Unverified beacons are dropped before they ever touch the peer table. 15 // 16 // `counter` is a freshness hint (replay deterrent). With a symmetric group 17 // key the real guarantee is "this came from a holder of our key" — which is 18 // exactly the friends-vs-the-con threat model. Upgrade path to per-badge 19 // Ed25519 identities is noted in the README. 20 // ───────────────────────────────────────────────────────────────────────── 21 22 #define BEACON_MAGIC 0xB1 23 #define BEACON_VERSION 2 // v2: added pet + pet_face (v1 badges won't verify) 24 #define TAG_LEN 16 // HMAC-SHA256 truncated to 128 bits 25 26 #pragma pack(push, 1) 27 struct Beacon { 28 uint8_t magic; // BEACON_MAGIC — cheap pre-filter 29 uint8_t version; // BEACON_VERSION 30 uint32_t counter; // monotonic-ish freshness hint 31 uint8_t handle_len; // 1..HANDLE_MAX_LEN 32 char handle[HANDLE_MAX_LEN]; // zero-padded; padding is part of the MAC 33 uint8_t pet; // sender's pet index (0..PET_BUILTIN_N; ==N means custom) 34 char pet_face[PET_FACE_MAX]; // sender's pet idle face, zero-padded (renders any pet) 35 uint8_t tag[TAG_LEN]; // signature over the bytes above 36 }; 37 #pragma pack(pop) 38 39 // Bytes covered by the signature = everything up to (not including) `tag`. 40 static inline size_t beacon_signed_len() { return offsetof(Beacon, tag); } 41 42 static inline void beacon_hmac(const Beacon* b, uint8_t out[32]) { 43 const mbedtls_md_info_t* info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); 44 mbedtls_md_hmac(info, GROUP_PSK, GROUP_PSK_LEN, 45 (const uint8_t*)b, beacon_signed_len(), out); 46 } 47 48 // Fill `tag` for an outgoing beacon. Caller must zero-pad `handle` first. 49 static inline void beacon_sign(Beacon* b) { 50 uint8_t full[32]; 51 beacon_hmac(b, full); 52 memcpy(b->tag, full, TAG_LEN); 53 } 54 55 // Constant-time compare — don't leak the tag via early-exit timing. 56 static inline bool ct_equal(const uint8_t* a, const uint8_t* b, size_t n) { 57 uint8_t diff = 0; 58 for (size_t i = 0; i < n; i++) diff |= a[i] ^ b[i]; 59 return diff == 0; 60 } 61 62 // Validate structure + signature. `len` is the received byte count. 63 static inline bool beacon_verify(const Beacon* b, size_t len) { 64 if (len < sizeof(Beacon)) return false; // runt 65 if (b->magic != BEACON_MAGIC) return false; 66 if (b->version != BEACON_VERSION) return false; 67 if (b->handle_len == 0 || b->handle_len > HANDLE_MAX_LEN) return false; 68 uint8_t full[32]; 69 beacon_hmac(b, full); 70 return ct_equal(full, b->tag, TAG_LEN); 71 }