Peers

For the curious

Technicalities

What Peers is made of, how a message gets from one phone to another, and what each part of the way can and cannot see. Written from the code.

No server in the middle.

Phones talk to each other directly when their networks allow it. When they don’t, a relay passes the sealed packets along. A sealed copy also waits in a mailbox for phones that are off. Peers has no server of its own that holds anything.

The parts

The relay, the mailbox and the call relay (STUN/TURN) are one ird p2p app, reached over wss://. Phones find each other there by a room id derived from the chat’s secret, then open WebRTC data channels where they can.

The mailbox keeps every sealed packet for 72 hours, read or not. Messages are trimmed to about 40 KB before sealing, so photos and files never rest there: a phone that comes back later asks an online member for the file and checks it against its SHA-256.

The relay is a setting in the app. The defaults point at ird’s network.

Three ways a message travels Your phone on the left, your friend's on the right. On top, a direct line between them. In the middle, a line through the relay. Below, a line into a mailbox that holds a sealed copy for three days. you friend direct, when both networks allow relay otherwise: passed along, still sealed mailbox: a sealed copy for 3 days
Every path carries the same sealed packet.

Signed once. Sealed twice.

Everything a chat is made of, messages, reactions, edits, polls and calls, is signed by whoever wrote it and sealed twice before it leaves the phone. The relay and the mailbox only ever hold the outer seal, and inside it is another one.

The inner seal, in detail

Sender keys. Each device keeps a hash ratchet per chat (HMAC-SHA256) and seals each packet under a one-time AES-256-GCM key, bound to the chat, the sender and the index. Used keys are deleted.

A chain reaches each member’s device sealed to that device alone: X25519, ephemeral-static plus static-static, and hybrid with X-Wing when the device has one. Chains start over after 2,000 messages, after a week, when someone joins a chat that has history, and when someone leaves or is removed.

Signatures cover a canonical form of each packet, so history replayed from the mailbox is checked against its author too. Files travel under a key of their own, carried inside the sealed message, and are checked against their SHA-256.

A message, signed and sealed twice Nested boxes. Innermost, the message, signed by its author. Around it, the message seal, a new key for every message. Around that, the room seal. Outermost, the connection to the relay or to the other phone. the connection: TLS to the relay, or DTLS phone to phone room sealAES-GCM, a key every member of the chat holds message sealAES-256-GCM, a new key for every message “see you at 7” signed by its author, Ed25519

People are keys.

There is no account to look up. Your identity is a key pair made on your phone, and a contact is someone’s public key with a nickname you gave it. No phone number, no email, no directory.

Four emoji are the fingerprint of a key. Compare them with a friend, or hold two iPhones back to back and let them check over ultra-wideband. Until you do, the first key you see for someone is the one Peers holds them to, and a different key shows up as a different person.

More than one device

A Mac or a second phone gets the account in a one-time room, from a sixteen-character code, sealed to a key the new device made for that transfer. Both screens show four emoji for that key, so a stranger answering the code would show. Each device keeps its own agreement key, which never leaves it.

KeyKindLives
IdentityEd25519 and ML-DSA-65Your devices
DeviceX25519 and X-WingThat device only
ChatRoom secretMembers’ devices
MessageAES-256-GCM, one useGone once used
On diskAES-GCMKeychain, this device

A link opens a door, not the chat.

An invite link never contains a chat’s key. It points at a door. The newcomer knocks, the inviter’s phone answers, and the key travels sealed to the newcomer’s device. Someone else holding the link sees that a note was left, not whose.

From link to chat in three steps Three columns: the inviter's phone, the door, and the newcomer. One, the link goes from the inviter to the newcomer. Two, the newcomer leaves a knock at the door, sealed to the inviter's device. Three, the inviter's phone leaves the chat's key at the door, sealed to the newcomer's device. inviter’s phone the door newcomer 1 the link, signed any way you like 2 knock: who they are sealed to the inviter 3 the chat’s key sealed to the newcomer
Rules at the door

The link carries the inviter’s key and their device’s key, signed with the expiry, so an edited link stops working. A private chat’s link lets in the first person who knocks and then closes; a later knock is told the place was taken, which is how an intercepted link shows. A group’s link can be set to ask first. Every link ends after a week, and a new link closes the old door.

Whoever made a group can remove people and name others who can. A removal moves the group to a new room whose key is sealed to everyone who stays. The person removed keeps what they already had and is left with a room nobody is in.

Waking a locked phone.

A locked iPhone runs no apps, and only a push from Apple wakes it, sent with the key of whoever published the app. So there is one small program in the picture: Peers Helper, a menu-bar app for Mac that turns sealed wake requests into pushes. OPUS runs the one the App Store build uses.

It sees

  • The number Apple uses for a phone
  • A time
  • A call or a message

It never sees

  • Who asked
  • Which chat
  • What was said
How a request is checked

Your phone never hands its push token to the people you chat with. Each chat gets a ticket instead: the token sealed by your phone to the helper, valid 30 days. It is withdrawn when someone leaves or is removed, when you delete the chat, or when you block the person.

A request is signed by a throwaway key and sealed to the helper, with the time sealed inside. The helper ignores copies it has seen, rings older than a minute, messages older than an hour, and withdrawn tickets. It sends at most two rings in ten seconds and one message alert in thirty to a phone, and holds message alerts back by a random 5 to 45 seconds. The alert carries no words of yours; a call’s details are sealed to the phone and checked against the caller’s key before it rings.

It writes down two things: where it stopped reading, and which tickets were withdrawn, until they would have run out. Once a minute it signs a heartbeat that the app checks under Settings › Advanced.

If it is not running, nothing is lost: messages wait in the mailbox until the phone next opens Peers. Calls to a sleeping phone do not ring.

The message and the ring take different paths Your phone on the left, a locked phone on the right. One path runs upward through the helper, which wakes the sleeping phone through Apple. A separate path runs downward through a mailbox, which holds the sealed message until the phone comes back. you asleep the helper: which phone, and when the mailbox: the sealed message, up to three days
The ring and the message travel separately.

Who learns what.

Every party that touches Peers, what it can make out, and what it never sees. Written from the code; if the two ever disagree, the code is right and this is a bug.

PartyLearnsNever
The relayrun on the ird networkA random chat number, when a device connected, its internet address, the size of each sealed packetYour name, who you write to, a word of it
The mailboxpart of the relayThe chat number, a sealed copy of each message of about 40 KB at most, when it was dropped in; kept 3 daysWho dropped it in, who it is for, what it says
The call relayTURN, only when no direct path existsThat an encrypted stream is passing, its size and timingA word or a frame of the call
The helperrun by OPUSThe number Apple uses for a phone, a time, a call or a messageWho asked, which chat, what was said
Apple’s push serviceThat a push went to a phone at a timeWhat it says: the body is sealed for that phone
Your network providerThat your device talks to the relay, and how muchAnything inside
Strangers nearbyThat a device announces a Peers service under a random name, and the sizes of sealed framesWho you are, who you talk to, what crosses
Someone holding an invite linkThat notes were left at that door, when, and how big; whether their own knock was let inWho else knocked, the chat’s key, its members, anything said
A site you share a link fromoff with Settings › Link cards in postsOne visit from your internet address when your phone makes the previewWho you shared it with; their phones never contact it
OPUSOnly what the helper learns, while OPUS runs itWho you are, who you talk to, what you said, that you exist

Only if you turn it on

Apple PasswordsSettings › Account, off by defaultThat a sealed item and a passkey for peers.opus.ro exist in your iCloud Keychain, its size, when it changes; on install, that a Peers build was installed on some deviceWhat is inside, or which identity is yours
Apple IntelligenceA draft you ask the writing tools to work on, on the phone or Apple’s private compute, per your Apple settingsAnything else; Peers sends drafts nowhere itself
Wi-Fi Aware pairingiOS keeps the pairing; phones nearby can tell a Peers service is aroundWho you are; the same signed handshake decides who is on the other end
Local, and nothing more

These run on your devices and change nothing above: the quantum-safe second lock, holding phones together to verify, Siri, Shortcuts and Spotlight, widgets, Live Activities and Control Center (drawn from a small note of counts and titles; no push token is requested), voice-note transcripts, the Mac menu bar, haptics and tips.

The two rules, and what changed when

No feature may hand any party something it does not already receive, unless you turn it on knowingly and this list says exactly what changes. And no feature may ask you to understand keys, pairing codes or network modes to benefit from it.

3 October 2026. Added the site a link is shared from: with link cards on, the phone that posts a link opens the page once, and the friends who see it contact nobody. The helper: the people in your chats no longer receive the number Apple uses for your phone; each chat gets a sealed ticket only the helper can open, withdrawn when someone leaves or is removed. The helper keeps withdrawn tickets until they would have run out and ignores a request it has already seen.

2 October 2026. Groups: removing someone moves the group to a new room, so the relay sees the phones that stay turn up under a new room number, and nothing about who or why. Invitations: a link no longer holds a chat’s key; someone holding one learns only that notes were left at its door. The mailbox keeps each sealed copy for three days, not one, and a copy is about 40 KB before sealing.

11 September 2026. The mailbox keeps a sealed copy of every message, not only for phones that are off, and collecting one does not remove it.

10 September 2026. Added Apple Passwords, Apple Intelligence and Wi-Fi Aware pairing as opt-in rows, and the local-only list; noted the association file fetched on install.

9 September 2026. First version: the relay, the mailbox, the doorbell, Apple’s push service, the Keychain, strangers nearby, your network provider.

At a glance.

SignaturesEd25519 on everything a chat keeps; ML-DSA-65 as well on member cards
Key agreementX25519, hybrid with X-Wing (ML-KEM-768 and X25519) wherever a key is handed over
Room sealAES-GCM, key from the room secret by HKDF-SHA256 (ird p2p SDK)
Message sealSender keys: HMAC-SHA256 chain, AES-256-GCM per message
FilesAES-256-GCM under their own key, checked by SHA-256
TransportWebRTC data channels, or wss:// through the relay
CallsWebRTC, DTLS-SRTP; signalling sealed inside the chat; TURN when no direct path exists
NearbyBonjour, AWDL and Wi-Fi Aware; a signed handshake, frames sealed under the chat’s key
On the deviceEncrypted store, key in the Keychain, never synced; no analytics, no crash reporting
Built withSwift and CryptoKit, for iOS 26 and macOS 26; GPLv3

The rest, folded.

Open what you are curious about.

Nearby, without internet

Bonjour over the local network and Apple’s peer-to-peer Wi-Fi (AWDL), and on iPhone Wi-Fi Aware between phones paired once through Apple’s sheet, published only while a pairing exists. A pairing is transport trust only: the signed handshake with your identity key decides who is on the other end, and every frame is sealed under a key derived from the chat’s secret. When a device is reachable two ways, the better carrier wins.

Calls

WebRTC with DTLS-SRTP, a full mesh in groups, signalling sent as sealed packets inside the chat. On iPhone, LiveCommunicationKit puts calls on the lock screen and in Recents under an opaque handle derived from the chat, never a number; PushKit wakes the phone. The caller’s signature is checked against your contacts before anything rings. On the Mac, calls ring inside the app.

The second lock

Every device has an X-Wing key (ML-KEM-768 with X25519) next to its X25519 key. Chain hand-offs, wake payloads and device transfers are sealed hybrid: HPKE with XWingMLKEM768X25519_SHA256_AES_GCM_256 for anonymous envelopes, and an X-Wing encapsulation combined with the static X25519 secret in one HKDF for pairwise ones, so the sender stays authenticated. Each identity also derives an ML-DSA-65 key; member cards carry both signatures, and a contact’s fingerprint covers both keys once theirs verifies, which is when the shield appears. Packets stay Ed25519-signed, since an ML-DSA signature is 3.3 KB. About 1.1 KB more per envelope.

Siri, widgets and the Dynamic Island

App Intents for send, call, ding, open and nearby, with contacts and chats indexed on the device. Widgets, Live Activities and Control Center controls are drawn from a small snapshot in the app group: counts, titles, whether nearby is on. The extension never opens the encrypted store, no push token is requested for an activity, and nothing runs while the app is locked. Voice notes transcribe on the device with the system’s speech model, and the transcript is never sent.

The vault in Apple Passwords

Off by default. A passkey for peers.opus.ro is made inside the app, with no server: a random challenge and a hash of the identity key as the user handle. Its PRF extension derives a secret inside the authenticator that wraps the account (identity, contacts, chats, room secrets, settings; never messages) with AES-GCM, stored as a synchronizable Keychain item, so it travels through iCloud Keychain end to end encrypted. Restoring is one Face ID. Turning it off deletes the item; the passkey stays until you remove it in Apple Passwords.

Where it stops.

Every design draws a line somewhere. These are Peers’.

  • The relay sees traffic, not words. Your internet address, when you connect, a random room number, and the size of each sealed packet. Peers hides what you say, not that you use it.
  • Friends you connect to directly see your internet address, as on any call.
  • Your phone is your account. Whoever has it unlocked, or a copy of its keys, has your chats. The app lock adds Face ID or a PIN.
  • Disappearing messages disappear from devices, not from memory. A screenshot or a second camera still works, and Peers does not detect either. Sealed copies stay in the mailbox for three days, with no key left on any device that read them.
  • Removing someone does not unsend. What they already received stays with them.
  • An unverified contact is taken on first sight. Comparing emoji or holding phones together is what settles who is who.
  • No independent security audit yet. The source is there to read in the meantime.

Every part OPUS runs can be swapped.

The relay

A setting in the app. Point it at any ird p2p app, including your own.

The helper

Runs on any Mac left switched on. Its keys are a setting too.

The app

Build it under your own Apple account. It still talks to everyone on the App Store build.

One Apple rule

Apple ties pushes to the account that published the app. Waking a phone on the App Store build needs OPUS’s Apple key, so that one piece stays with OPUS. Phones on your own build are woken with your own key.

Questions and answers Read the source