all writing

build log / networking / privacy

why i made Rivet.

private messaging that keeps moving even when the internet doesn't.

i did not start Rivet because i thought the world needed another messaging app. it has enough of those.

the question that kept bothering me was smaller: what happens to a conversation when the infrastructure disappears?

no mobile data.no Wi-Fi.no server to ask where somebody is.no account system.no convenient cloud sitting in the middle.

the two phones are still physically there, a few metres apart. so why should they suddenly have nothing to say to each other?

that question became Rivet.

conceptual visualization

sender. seals the message for one recipient, then offers the envelope to whoever is nearby.

not real Bluetooth in the browser. the envelope only travels as far as you scroll.

a message leaves the sender, hops through two phones that cannot read it, and reaches the recipient. hover or tab through the phones to see what each one is allowed to do.

the annoying question.

almost every messenger i use works the same way underneath, and for good reasons. you have an account. a server knows how to find you. your phone talks to the internet, the internet talks to the server, the server talks to the other phone.

that design is fine. it is fast, it scales, and most of the time the internet is just there. Rivet is not a protest against it. it is what happens if you take one assumption away and look at what is left.

so the starting constraint was: no server and no internet. the only thing you can count on is that phones exist and some of them are near each other. what if the phones are the network?

no account was part of the point.

if there is no server, there is nothing to sign up to. no phone number, no email, no username sitting in a database somewhere. that ended up being one of my favourite parts of the design rather than a limitation.

the thing i had to untangle was the difference between an identity and an account. an account is something a service gives you and can take away. an identity, in Rivet, is a set of keys your phone generates for itself. nobody issues it, and nobody keeps a list of them.

which means contacts have to be exchanged on purpose. you show a QR code or paste a code, in person or through a channel you already trust. then you can compare a 60-digit safety number with the other person, so you both know the key you saved really belongs to them and not to someone in the middle.

Bluetooth sounded simple.

at first Bluetooth felt like the easy part.there are two phones.they are nearby.Bluetooth exists.problem solved.

it was not problem solved.

Bluetooth Low Energy has two roles. a peripheral advertises that it exists and waits to be found. a central scans and connects. a mesh needs every phone to find others and be found, so every Rivet phone does both at once: it runs a GATT server while it scans for everyone else.

then the platforms get involved. Android lets Rivet keep relaying in the background through Relay Mode, a foreground service you start yourself, with a notification that says it is running and a button to stop it. iOS is stricter. in the background, scanning slows down and gets filtered, the advertisement carries less, callbacks arrive late, and the system can end the app when it wants the resources back. there is no real iOS equivalent of Relay Mode.

and nothing holds still. people walk in and out of range, connections drop halfway through a transfer, and every message has to be split into frames small enough for the radio and put back together on the other side. a surprising amount of Rivet is just staying calm about things not working.

the phones became the network.

this is the part that made it interesting. if the recipient is not nearby right now, the message does not fail. it waits, and other phones can help.

every phone running Rivet can:

  • receive an encrypted envelope it cannot read,
  • keep it for a while,
  • offer it to the next phone it meets,
  • throw it away when it expires,
  • and, if it happens to be the recipient, open it.

this is store-and-forward (or carry-and-forward, which is more honest, because the message is literally carried around in people's pockets until it gets close enough to where it is going).

nothing stays forever. an envelope lives on any one device for at most six hours, whatever its sender asked for, and it can pass through at most six hops. after that it is gone.

conceptual visualizationloading the 3d view
too far apartsenderrelay arelay brecipienttoo far apartsenderrelay arelay brecipient

not real Bluetooth in the browser. the range ring is a drawing aid, not a measured distance.

a conceptual simulation, not real Bluetooth. move phones in and out of range and watch one envelope wait, spread, and either arrive or expire.
conceptual simulationready

the friend is out of range. press start, then hand a sealed copy to any phone that is close enough. keep going until the friend is close enough too.

hand a sealed copy to

range 26 · 0 handovers · 26 ticks left

not real Bluetooth behaviour. positions, range and ticks are drawing units. what it does show is the real idea: a phone you hand a copy to keeps it, so the message can wait for the next one to drift close.

a small game about the same idea, also a conceptual simulation and not real Bluetooth behaviour. carry a message to someone out of range before every copy expires.

why there is no routing table.

the obvious way to make this efficient is routing: keep track of which phones see which other phones, build up a map, and send each message along the best path.

i did not want that map to exist. a routing table that is good at delivering messages is also a pretty good record of who is near whom, and how often. that is exactly the kind of information i would rather a messenger never collected.

so Rivet uses a bounded epidemic instead. when two phones meet, each offers a shuffled, capped list of the envelope ids it is carrying, the other asks for the ones it does not have, and they hand them over. no phone builds a picture of the network. every unexpired envelope just keeps moving, inside the six-hour and six-hop limits.

the cost is real. spreading copies spends more bandwidth and more battery than a clever route would. and it is not invisibility either: a phone you connect to can still see which envelope ids you offered. it is a trade, made on purpose.

what actually travels.

every envelope starts with a small plaintext header that relays need in order to do their job. everything else is sealed.

conceptual visualization · fields from the protocol notes

seal

seal: the letter is encrypted for the recipient and folded inside. the ephemeral key and nonce ride outside, the header goes on the front, and the seal closes it.

34 B relay-readable header32 B ephemeral key24 B nonce128 B sealed sender + signature6 hops max6 h max on any one device

plaintext header (relays read this)

visible, but useless on its own

encrypted for the recipient

envelope id. random. relays use it to avoid storing or sending the same envelope twice. it is not derived from who sent it.

the parts of a sealed envelope, as defined in Rivet's protocol notes. select a part to see what it is for.

the header holds a random id, a creation time rounded down to the minute, a requested lifetime, a hop count and a hop limit, plus a version and type so it can be parsed at all. the sender's keys and signature are inside the encrypted part, not next to it. payloads are padded up to fixed bucket sizes so a message's length gives less away.

encryption was not the end of the threat model.

the cryptography is what people ask about first, so: identities are Ed25519 keys, key agreement uses X25519, keys are derived with HKDF-SHA256, and messages are sealed with XChaCha20-Poly1305. none of it is homemade; it comes from established libraries, not from anything i invented.

but encryption only protects what is inside the envelope. writing the threat model was mostly a long list of things it does not fix.

protects against

does not solve

protected message contents. sealed with XChaCha20-Poly1305 for one recipient. relays carry ciphertext they cannot open.

what Rivet is designed to protect, next to what it explicitly does not. select an item for the detail.

a phone with Bluetooth on is announcing that a device is physically present, whatever it is sending. Rivet rotates the random tag it advertises every fifteen minutes, but a radio is still a radio. if someone has your phone unlocked, they have your messages. emergency reset deletes data and keys, but it cannot promise a forensic tool will never find a trace.

i would rather write that down than let anyone assume otherwise.

the feature list got weird.

once envelopes could travel through strangers' phones, other things fell out of the same machinery almost by accident.

circles came from wanting to message a small group without a server keeping a member list, so a circle is just a list on your phone, and each person gets their own sealed copy. channels came next: a shared passphrase, no owner, no admin, and anyone who knows the words can read and write. nearby broadcast is the deliberately public one, readable by anyone around you, because sometimes that is what you want and the app should say so plainly.

the delivery states had to be honest too. Rivet shows waiting, in mesh and delivered. in mesh means another phone told yours it took a copy. it does not mean the recipient has it, and the app never pretends it does.

and then the ones i did not see coming: an emergency reset, six languages (English, Hindi, Bengali, Marathi, Telugu and Tamil), and light, dark and system themes. none of them were part of the original idea. all of them seemed obviously necessary the moment i imagined someone actually using it.

things Rivet deliberately does not do.

version 1 does not do internet messaging, cloud sync, attachments, voice, video or synchronized group chat.

every one of those is reasonable to want. every one of them would also pull in a server, a much bigger attack surface, or a protocol problem i am not ready to solve properly. keeping the scope small is the only way i can make honest statements about what is left.

where it is right now.

  • version 1 is pre-release.
  • the automated suite is green and the protocol is frozen for version 1.
  • physical-device field testing has not been done. the field-test plan exists; none of its scenarios have been run yet.
  • there has been no independent security review.

those last two matter. a messenger that has not been tested on real phones in real places, and has not been reviewed by someone whose job is to break it, should not be trusted with anything important yet. it is not released because it is not ready, and i would rather say that clearly than blur it.

  1. 2022ideacan two nearby phones talk privately without the usual infrastructure?
  2. afterearly developmentthe question slowly turned into Bluetooth discovery, encrypted envelopes and store-and-forward.
  3. 2025protocol + relayprotocol design, relay behaviour, native radio work.
  4. 2025security modelthreat modelling, and writing down what it cannot do.
  5. 2026v1 freezeprotocol frozen for version 1, automated suite green.
  6. nextphone testingfield tests on real devices. planned, not run yet.
  7. nextindependent reviewsomeone whose job is breaking things. not done yet.
roughly how the project moved. years only, because the early parts never had release dates.

what building it changed.

Rivet started as one small question and turned into Bluetooth, cryptography, protocol design, state machines, unreliable transport, threat modelling, native Swift and Kotlin modules, and a long education in how differently two phone platforms behave when asked to do the same thing.

it is also the project that taught me to write down what something does not do with the same care as what it does.

Rivet still has a lot to prove. that is probably why i am still interested in it.

it stopped being "a messenger without the internet" a while ago. now it is mostly a long-running answer to the question that started it:

how much infrastructure does a conversation actually need?