Peuple is a peer-to-peer social app.
Posts, direct messages and contacts are stored encrypted on your own device and sync directly with the people you choose — there's no company server in the middle. This page documents what's actually built: every feature below, and the architecture underneath it.
The real PostCard layout — Recent/Hot are both formulas you can read, not a hidden ranking.
Everything Peuple does, organized by area.
Eight areas cover the shipped feature set. Each card lists what's there today; open "Technical detail" for the implementation underneath.
Identity & contacts
Because an account is something a company can suspend, sell, or lose — a keypair isn't.
- Ed25519 identity keypair, generated on-device from a 128-bit master seed
- 12-word BIP39 recovery phrase — the only backup, and the only thing that restores you
- Profile (name, about, avatar) stored locally, shared with contacts you add
- Contacts added by scanning a one-time QR invite; pairing is mutual
- Removing a contact rotates your feed's encryption key going forward
Technical detail
expo-secure-store). Every other key —
identity, corestore, account, feed epochs, DM channels — is derived from that one seed via
blake2b(seed, purpose), so the recovery phrase alone is enough; there's no
separate backup file. An invite is z32(version | inviterPk | oneTimeSecret),
exchanged over a swarm topic derived from the secret and MAC'd + signed by the inviter's
identity key.
Feed & posts
Because you deserve to see what people actually posted — not what an algorithm decided to show you.
- Text, photo and video posts; media stored as Hyperblobs (64KB blocks)
- Threaded replies (comments) on any post
- Two sort modes — Recent, or Hot (interactions per hour, cooling over time)
- Multi-photo posts
Technical detail
corestore.replicate) — torrent-style, so
distribution scales with the audience rather than your device's own upload bandwidth.
Direct messages
Because a private conversation should stay between the two people having it — not sit on a company's servers.
- Pairwise end-to-end encryption (X25519 ECDH between identities)
- Reply/quote, edit (marked "edited"), delete for me or for everyone
- Emoji reactions, star/pin messages, in-chat search
- Delivery ticks (sent/delivered) — deliberately no read receipts, typing indicators or online status
- Photos and video in DMs
Technical detail
core/lib/dms.js, so one side
can never rewrite or erase the other's message. The DM key is static per pair — derived once
from X25519(a,b) — so there's no per-message forward secrecy yet; see Known
limitations below.
Spaces
Because a community you built shouldn't disappear the day a company decides to pull the plug.
- Built on Autobase: every member writes to their own core, linearized into one shared view
- Public or private — private Spaces are encrypted, invites carry the key
- Threaded posts and replies
- Invite existing contacts, or share an invite
- Seven-level role ladder: banned → guest → member → trusted → moderator → admin → owner
- Moderators can remove individual posts
Technical detail
spaces.js's apply() —
the client never trusts a role value it didn't get back from the core. A Space keeps two
views: the posts log (with remove-tombstones) and a separate role-change audit log.
Subscriptions
Because you should be able to support a page you like without being tracked for it.
- Subscribe to a company's public feed — no handshake required
- Sponsored posts appear only from accounts you subscribed to, labeled "Subscribed"
- Publish your own public feed
- Unsubscribing just stops replication — there's no subscriber list to delete, because there's no server holding one
Technical detail
.well-known/peuple.json file on their domain — no
central registry.
Linked devices
Because working from a laptop shouldn't mean trusting it with your entire identity.
- Authorize a browser/desktop session by scanning a QR code from your phone
- Each session is scoped (e.g. read-feed, send-dm) and time-limited
- Revoke any session instantly from the phone
- The desktop companion runs locally and never transmits your keys anywhere, including to Peuple
Technical detail
Moderation
Because keeping a community safe shouldn't require handing one company the power to silence anyone in it.
- Report a user straight from the app — a signed, encrypted, one-way report
- An opt-out default blocklist; blocked identities simply stop rendering locally
- No content is deleted off anyone's device — moderation removes reach, not history
- Moderators can remove individual Space posts (tombstoned for that Space)
Technical detail
Offline delivery
Because your messages should still arrive when you're offline — without anyone in between being able to read them.
- Blind peers: always-on relays that store-and-forward ciphertext for offline contacts
- They hold no read capability — encrypted blocks only
- Also mirror your encrypted account log, which is what makes phrase-only recovery able to restore your contact list
Technical detail
infra/blind-peer in the repo has instructions to
run one.
Architecture & tech specs
One UI shell, one P2P core, connected by a typed RPC seam. No server ever sees plaintext or the social graph. This is the full technical picture: architecture, cryptography, data model, threat model, stack, and current limitations.
Architecture
The React Native app renders UI only. All networking, storage and crypto run in a Bare worklet, off the UI thread, talking to the UI over bare-rpc.
Key material
Everything derives from one 128-bit master seed, backed up as a 12-word BIP39 phrase.
| Key | Derivation | Purpose |
|---|---|---|
| Identity keypair | blake2b(seed, "peuple/identity") → ed25519 | Swarm auth, invite signatures |
| Corestore primary key | blake2b(seed, "peuple/corestore") | Deterministic core keys → recovery |
| Account key | blake2b(seed, "peuple/account") | Encrypts the account log |
| Feed epoch key | blake2b(seed, "peuple/feed-enc/<epoch>") | Encrypts feed + media cores |
| DM key | blake2b(X25519(a,b), "peuple/dm", sort(pkA,pkB)) | Encrypts both DM cores of a pair |
Data model
All Hypercores, all encrypted at rest.
| Core | Contents |
|---|---|
| account | Local append-only log: profile, contacts, feed epochs, read state. Mirrored as ciphertext to blind peers for recovery. |
| feed / media | Posts (JSON) and media blobs, one epoch per key generation. |
| dm | One core per side of a conversation, merged by timestamp client-side. Key rotations ride the same channel as system messages. |
| Space (Autobase) | One writer core per member, linearized into a shared posts view plus a separate roles audit view. |
Threat model
| Adversary | Mitigation |
|---|---|
| Server/relay operator | There is no server holding plaintext; blind peers see ciphertext + discovery keys only |
| Network observer | Noise-encrypted transport (Hyperswarm); the DHT reveals discovery keys, not content |
| Non-contact who obtains blocks | Blocks are encrypted with feed/DM keys they don't hold |
| Removed contact | Feed key rotation on removal |
| Stolen recovery phrase | Full compromise — communicated prominently in onboarding/settings |
| Stolen, unlocked phone | Out of scope for this build; the keystore protects the seed at rest, the OS lock screen is the rest |
Known limitations (current build)
- Invites are exchanged live — both phones need to be online during pairing
- DMs use a static per-pair key — no per-message forward secrecy yet (a Double Ratchet is a candidate future addition)
- Network metadata — who's talking to the DHT — is not hidden
- Live-pushed Feed/Space events aren't blocklist-filtered yet; only reads are (next refresh corrects it)
Stack
| Layer | Technology |
|---|---|
| UI shell | Expo / React Native 0.86, React 19 — renders UI only, never touches the network directly |
| Core runtime | Bare worklet (core/app.js), runs off the UI thread |
| Storage | Corestore 7 / Hypercore 11 — encrypted-at-rest, append-only logs |
| Networking | Hyperswarm 4 — Noise-encrypted transport, hole-punched P2P connections |
| Multi-writer forums | Autobase 7, used for Spaces |
| Media | Hyperblobs 2 — chunked blob storage for photos/video |
| RPC seam | bare-rpc, typed command surface in packages/rpc (~50 commands) |
| Desktop companion | Node.js — joins via the same device-delegation model as any other linked device |
Repository layout
| Path | What it is |
|---|---|
| apps/mobile | Expo (React Native) app — UI only, no P2P logic |
| core | The P2P core (Bare worklet): identity, contacts, feeds, DMs, Spaces |
| apps/desktop | Desktop companion, authenticated by device delegation |
| packages/rpc | Typed RPC seam between UI and core |
| infra/blind-peer | How to run a blind-peer relay |
| docs | Architecture, threat model, phase-2 design notes |
apps/mobile, apps/desktop and packages/rpc are public, MIT-licensed, on
github.com/hottens/peuple.
core, infra and docs stay private — that's the P2P protocol
implementation itself.
Run it yourself
Core logic is plain JS, tested with brittle against a local hyperdht testnet — no network needed to run the test suite.
Two ways to run it.
The phone app is the whole product — identity, feed, DMs, everything. The desktop companion lets you read and reply from a laptop, authenticated by your phone, without your keys ever leaving it.
Android
Signed release build — identity, feed, DMs, Spaces, everything. Not on the Play Store yet, so Android will ask you to allow installs from this source the first time.
Desktop companion
A local app for your laptop. It runs on your own machine, pairs with your phone over an encrypted device delegation you can revoke any time, and never sends your keys anywhere — including to us.