Building an offline, encrypted messaging platform for Burning Man using React Native and LoRa

Joshua Mustill
6 min readOct 30, 2019

Burner disclaimer
This project aimed to facilitate communication between camp members over vast distances: to spread news of events and ensure everyone’s safety. It was not intended to distract from the immediacy aspect or to interfere with anyone else’s experience.


As anyone who has been to Burning Man knows, the unscripted, unscheduled nature of events spreading only by word-of-mouth is one of the greatest and most surprising aspects of the week spent in Nevada’s Black Rock desert. There is no cell service (and that’s fantastic) but, on occasion, you may stumble across something so awesome, so unexpected, that you feel the need to share it with your closest friends so they can participate too. Enter the Burning Man Pager system.

Tech stack overview

The basic premise was to allow camp members to compose messages on their personal smartphones and then use custom hardware to relay these encrypted messages to a centralised gateway over a radio protocol known as LoRa. The gateway maintained a queue for each member of the network and sent any pending messages back to the user’s phone.

  • React Native app composes and encrypts messages using a pre-shared password.
  • Encrypted messages are encoded using Protobuf and forwarded to the PCB over BLE.
  • The PCB broadcasts the message to a central gateway over LoRa and listens for a response. If there are pending messages for that network member, the response is sent back to the PCB and relayed to the phone.
  • A series of acknowledgement messages ensure that nothing is lost over the airwaves.
  • The React Native app is scheduled to poll the gateway every 30 seconds whilst in the foreground and every 5 minutes whilst in the background to check for any messages which have been sent.
  • The end result is a group messaging application which allowed our 30+ camp members to communicate in near real-time across several miles without any cell service.
Tech stack overview

React Native app

I had originally planned to develop an Android-only version natively in Java, building on a previous project I had used interfacing with a Nordic chip over BLE. However, given the popularity of iPhones within our camp and my newfound love of TypeScript, I opted for React Native and used the opportunity to implement many of React 16.8’s newest features, namely hooks.

Coming from a primarily Node.js background, the first obstacle to overcome was that of lack of native Buffer and Crypto libraries which would be so heavily relied upon. Luckily, rn-nodeify quickly overcame that barrier and I moved on to the BLE aspect.

I was pleasantly surprised to observe the similarities in API calls between react-native-ble-manager and the Nordic Android BLE Library. After overcoming a few quirks with regard to the BLE Manager on iPhone (see this hack), a BLE connection with the custom PCB running a Nordic chip was quickly established and data began to flow back and forth using a bespoke BLE protocol developed by Lucas and myself (after ensuring that my Javascript code passed 100% of its unit tests for BLE and protobuf encoding of course!)

The result was an application not dissimilar to a Facebook group chat (credit to GiftedChat) which was able to display timestamped user messages from a variety of members with full name lookup (more on that in the protobuf section below).

Screenshot of the app chat screen
Yes, I realise Burning Man me cannot spell desert…

The Protocol Buffers

Protocol buffers (“protobufs”) are used to serialise (turn into binary data) a complex message type in a highly optimised format. They work across languages so the React Native compiled protobuf built on the phone was also readable by the embedded-C PCB and the Go app running on the gateway.

The main fields of interest above are in the PagerCommsTextMessage message:

  • LatLongLocation location — this was future-proofing. The idea being that the latest location of each member of the network could also be stored to construct a map of nearby camp members (not used in the end).
  • uint64 unixtimems_creation — quality of life addition. Allows for the time-stamping of messages seen in the screenshot above.
  • string message_content — the encrypted message which would be shared in the group. End-to-end encryption from the moment the message is constructed until it is delivered to another member’s phone. This is why the counter and user id are stored separately — so that the gateway does not need to store decrypted messages.
  • uint32 sender_id — for this I wrote a simple algorithm which would encode the user’s personal cell phone number into a 24-bit integer. The most significant byte was reserved as a network token to avoid conflicts (although I doubt there was too much LoRa traffic in Black Rock City). Upon receiving a message, the receiver’s phone would decode the 24-bit integer back into a cell phone number and cross reference it with their phone’s address book to assign a name to the sender.
  • The entire text message was wrapped in a custom LoRa protocol which contained the counter, user ID and HMAC (for verification).

The Gateway (Golang) App

This part of the architecture was entirely designed by my close friend Lucas Glenat — I will do my best to explain the principle logic but my examples will be in JavaScript as my Go knowledge is only very basic.

Each time a new device registered on the network (by sending an initial polling request), a new queue was created for that unique sender ID. Since the architecture was to designed around a group chat application, every new message received would be added to everybody’s queue and would remain there until the receiver requested a download. At this point, messages would be sent to the receiver one at a time through an ack-based polling (think QoS1 in MQTT) until the queue was empty.

The Hardware (Antenna)

To cover an area as large as Black Rock City, we needed a big antenna and we needed it to be high. We also needed power and protection from the dust storms. And the gateway needed to never turn off, never require servicing, or monitoring — no-one would be responsible for keeping a generator running (which would be super wasteful anyway). Again, Lucas was the brains behind most of this — I just fought against the dust, sun and various, more entertaining distractions in the middle of the desert to assemble it.

The final setup was:

  • 12V battery (~$25)
  • Small 20W 12V solar panel (~$30)
  • Solar Charge Controller 20A (~$10)
  • One giant-ass antenna
  • LoRa “off the shelf” gateway (a little pricey — luckily the dust didn’t do too much damage to it)

The whole contraption was assembled inside a Dodge Dakota with the cable for the antenna poking through a small hole in the window. The antenna was strapped firmly to a 50 gallon water barrel on the bed of the trailer giving it a final height of approximately 15ft.

Solar panel and antenna setup
A little precarious but it survived all week

Final thoughts

Was it useful? No, honestly not really and we didn’t expect it to be. The motivator for Lucas and myself was to see if we could build something (in a week) that would survive, cut-off from the outside world for 10 days and add some infrastructure which did not previously exist. This was a quick and dirty hack to see what we could build from scratch and just the fact that it worked; that we were able to receive messages over multiple miles and that all the hardware survived the ordeal is a success in my eyes!

I don’t know if we will take the project back to Burning Man in future years, but I’m very glad we made it work this one time.