DEV Community

autoAbode
autoAbode

Posted on

Building an Encrypted LoRa Mesh Network with ESP32-S3: Lessons from MeshVani

When we set out to build a mesh communicator that could handle text, voice, photos, and GPS sharing over distances of 10 to 40 kilometres without any cellular infrastructure, we knew we were signing up for a hard problem. Not just a radio problem or a firmware problem, but a systems engineering problem where cryptography, RF performance, power management, and user experience all had to work together.

This article walks through the core technical decisions we made while building MeshVani, our LoRa mesh communicator. Whether you are building your own mesh project or just curious about encrypted radio networks, there should be something useful here.

The Hardware Stack

At the heart of MeshVani is the ESP32-S3 paired with the Semtech SX1262 LoRa transceiver. Here is why we chose this combination over alternatives.

ESP32-S3 over ESP32 or nRF52:

The S3 variant gives us a dual-core Xtensa LX7 running at 240 MHz, 512 KB SRAM, and hardware-accelerated AES. That last point is critical. When you are encrypting and decrypting every single packet on a battery-powered device, offloading AES to hardware cuts power consumption by roughly 40% compared to software AES on the same chip. The S3 also has native USB-OTG, which simplifies firmware updates and debugging without needing an FTDI adapter.

SX1262 over SX1276:

The SX1262 is Semtech's current-generation LoRa transceiver. Compared to the older SX1276, it offers lower receive current (4.6 mA vs 10.3 mA), a wider frequency range, and better blocking immunity. For a device that spends most of its life in receive mode listening for incoming packets, that 56% reduction in RX current translates directly into battery life.

The SPI Bridge:

The ESP32-S3 communicates with the SX1262 over SPI at up to 16 MHz. We use DMA transfers to avoid blocking the CPU during packet send and receive operations.

(Technical implementation details omitted)

The Encryption Layer: AES-256-GCM Done Right

Most hobbyist LoRa projects either skip encryption entirely or bolt on AES-128-ECB as an afterthought. We went with AES-256-GCM for a reason: it provides both confidentiality and authenticity in a single pass. GCM mode gives you a 128-bit authentication tag alongside the ciphertext, which means any tampering with the packet, including bit flips from RF interference, gets caught before the payload reaches the application layer.

Key Derivation with PBKDF2-SHA256

Asking users to manage raw 256-bit keys is a non-starter. Instead, we derive the encryption key from a user-chosen passphrase using PBKDF2-SHA256. Here is the flow:

(Technical implementation details omitted)

Why 100,000 iterations? On the ESP32-S3 with hardware SHA acceleration, this takes approximately 1.8 seconds, which is acceptable during initial setup but makes brute-force attacks on captured packets impractical. An attacker would need roughly 5 years on a modern GPU cluster to exhaust a 6-word passphrase space.

The mesh ID is mixed into the salt so that identical passphrases on different mesh networks produce different keys. This prevents cross-mesh key reuse attacks.

Per-Packet Nonce Construction

GCM requires a unique nonce for every encryption operation. Nonce reuse is catastrophic, as it completely breaks GCM's security guarantees. We construct the 96-bit nonce from three components:

(Technical implementation details omitted)

The sender ID ensures that two nodes incrementing their counters independently never collide on the same nonce. The 64-bit counter gives us 2^64 packets before rollover, which at one packet per second would take 584 billion years.

Mesh Routing: Flooding with Intelligence

Pure flooding, where every node rebroadcasts every packet, works for tiny networks but collapses at scale. Pure routing protocols like AODV add complexity and latency for route discovery. We use a hybrid approach.

The Routing Table

Each node maintains a neighbour table with signal quality metrics:

(Technical implementation details omitted)

When a node needs to forward a packet, it checks whether the destination is in its neighbour table. If so, it forwards directly. If not, it uses controlled flooding with a TTL (time-to-live) field that decrements at each hop.

Duplicate Suppression

Every node maintains a circular buffer of the recent packet hashes. Before rebroadcasting, the node checks this buffer. If the hash already exists, the packet is dropped. This simple mechanism eliminates broadcast storms in dense networks.

(Technical implementation details omitted)

Delivery Receipts

We implemented WhatsApp-style delivery receipts (sent, delivered, read) as lightweight control packets. When node B receives a message from node A, it immediately sends back a 12-byte ACK containing the original packet hash and a status byte. This ACK follows the same mesh routing path back.

(Technical implementation details omitted)

The UI shows single tick (sent), double tick (delivered), and blue double tick (read), exactly the pattern billions of people already understand from messaging apps. We saw no reason to invent a new visual language.

Power Management

MeshVani targets 72 hours of operation on a single charge. Achieving this required aggressive duty cycling:

  1. Listen-Before-Talk (LBT): The SX1262 stays in RX mode with a configurable CAD (Channel Activity Detection) cycle. The radio wakes every 500 ms to check for preamble energy. If none is detected, it goes back to sleep in under 1 ms.

  2. Transmission Batching: Non-urgent packets (like GPS position updates) are queued and transmitted in bursts rather than individually. This amortises the TX power-up cost across multiple packets.

  3. Dynamic SF Adjustment: When RSSI indicates a strong link, we drop from SF10 to SF7 for shorter airtime and lower energy per bit. This alone improved average battery life by 18% in field testing.

What We Learned

Building MeshVani taught us several things that no datasheet or application note mentioned:

Clock drift matters more than you think. The ESP32-S3's internal RC oscillator drifts enough at temperature extremes (we test from -30 to +70 degrees Celsius) that time-synchronised protocols break. We switched to asynchronous designs wherever possible and added a 32.768 kHz crystal for the RTC.

GCM tag verification failures are your friend. In early field tests, we saw a 2-3% GCM tag failure rate that we initially attributed to RF noise. It turned out to be a race condition in our SPI DMA driver that occasionally corrupted the last few bytes of received packets. Without authenticated encryption, this bug would have been a silent data corruption issue.

Users do not read manuals. We originally required a 16-character passphrase. Support tickets piled up. We switched to a word-list approach (four random words, like "tiger-monsoon-bridge-seven") that is both easier to remember and has higher entropy than most user-chosen passwords.

Looking Ahead

Mesh networking on sub-GHz bands is still in its early days. We are exploring time-division multiplexing for voice relay across multiple hops, adaptive bitrate for mixed voice and data traffic, and network-coded cooperation where multiple nodes collaborate to decode weak signals.

If you are building something similar and want to compare notes, we are always happy to talk radio over chai. You can find more about our mesh communication products at autoabode.com/meshvani.


Shubham Garg is the Founder and Managing Director of AutoAbode, an India-based deep-tech company building industrial 3D printers, mesh communication systems, and autonomous aerial platforms. AutoAbode has been designing and manufacturing hardware in New Delhi since 2015.

Top comments (0)