rilo

Let’s get technical.

Everything the front page says, written out as the thing it was built from: the real database, how a contact is actually made, and who else touches a call.

You do not need this page to use Rilo, and nothing here contradicts the simpler version — it is the same product, described precisely.

The whole database

Everything our server knows about your family.

Not a summary. These are the seven tables, with the columns they actually have. Four of them are the account; three exist so that a phone which was switched off does not miss anything, and two of those delete themselves.

accounts kept
  • ida hash of the public key — not a name
  • public_keythe device key that is the account
  • typeadult or child
  • type_certthe signature proving it — a child account is signed by a parent, so there is no flag to flip
  • created_at
  • last_active
The username is gone as of issue #75. It was write-only — stored, returned to nobody but yourself, never sent to a peer — so it could only collide, never be read. Dropping the column is what makes this table one row shorter and still true.
guardians kept
  • account_id
  • guardian_keya key allowed to act for this child
  • added_at
  • removed_at
Removal is marked, not erased. Contacts are checked against who was a guardian when the contact was signed, so a parent leaving the account does not silently sever every friendship they ever approved.
push_tokens kept
  • account_id
  • platformapns or fcm
  • tokenan opaque handle from Apple or Google
  • updated_at
One per device, so a call can ring a phone that is asleep. The push carries no name and no content.
used_nonces swept
  • nonce
  • expires_at
A list of one-time numbers already spent, so a captured request cannot be replayed. Deleted as it expires.
pending_grants deleted on delivery
  • edge_hasha hash of the pair, not the two people
  • grantthe permission being decided, and the signatures collected so far
  • waiting_keywhose signature is still missing
  • requested_by
  • originscanned in person, or a parent acting for their child
  • created_at
  • expires_at
  • delivered_towhich sides already have it
This is the one place the server holds anything resembling a social graph, and it holds it only while an introduction is unfinished. Nobody answered in 72 hours: it lapses. Answered but the phone it was meant for never came online: 30 days. Once both phones hold the finished contact, the row is deleted — which is why there is no table of contacts on this page. There is no table of contacts.
messages deleted on delivery
  • id
  • recipient_idrouting needs this one in the clear
  • sender_idso the phone knows whose key opens it
  • edge_hashthe pair, hashed, so this cannot be read as a map of who talks to whom
  • sealedciphertext. There is no text column and there never will be one
  • sent_at
  • expires_at
A phone is switched off most of the day, and a message that quietly vanishes is a broken product. So a sealed box waits here — at most 30 days, and deleted the moment the recipient confirms they have it. We cannot open one.
revocations kept
  • edge_hashthe pair, hashed
  • revoked_at
  • recordthe signed record, so a phone that was offline can verify the cut itself rather than taking our word for it
The one thing we keep indefinitely. It records that a contact was cut without being a readable list of who knew whom.

Columns that do not exist

  • email
  • name
  • age
  • photo
  • message text
  • call content
  • call history
  • contact list
  • location
  • device model

Your email is checked once when you sign up and destroyed in the same breath — there is no column to put it in. Names and photos are set on each phone and handed to the other phone directly; they never reach us, which is also why you can rename Grandpa to “Opa” and we will never know. We could not show any of this to anyone if we wanted to, and we could not be made to.

This section is generated from server/src/identity/accounts.ts — the file that creates the database. If the two ever disagree, the file is right and this page is a bug.

Contacts

How a contact is actually made.

Somebody shows a one-time code on their phone; the other phone scans it, in the same room. The code expires in ten minutes and works once. That scan does two things at once: it starts the introduction, and it records the other phone’s public key, so both phones know exactly which key belongs to which person from then on. That is the part that makes a later call impossible to sit in the middle of, including for us.

Nothing connects yet. A responsible adult has to sign the contact on their own phone, behind a biometric check made at that moment. What they sign is the whole thing — who, with whom, and in which direction — and their signature is what the server checks before it will route anything. The server holds no key that can sign, so it cannot create a contact, and neither can anyone who steals the database.

When both phones hold the finished contact, the row is deleted and the contact exists only on the two devices. Cutting it later writes a signed revocation, which is the one record kept indefinitely — stored as a hash of the pair, so it says that a contact ended without saying who knew whom.

Who else is involved

Three companies touch a call. None of them can hear it.

Three companies are involved in getting a call to ring. Here is what each of them can and cannot see.

  • Apple & Google

    Push notifications, so a call rings a phone that is asleep. Unavoidable on both platforms. They carry an opaque identifier and nothing else — no name, no number, no content.

  • Cloudflare

    Roughly one call in six cannot connect the two phones directly — hotel Wi-Fi, some mobile networks — and is bounced through a relay. The relay forwards encrypted packets it cannot open, but it does see that two anonymous ids were connected, when, for how long, and from which addresses. That is metadata we are proud not to keep, held by somebody else. We would rather write it here than let you discover it in a policy.

  • Us

    The table above. One small server in the Netherlands that introduces two phones and then gets out of the way.