Communities
A Community is a collection of channels sharing one membership and authority model. Normative text: CORD-02.
Founding
Section titled “Founding”Creating a community mints three things:
- a random 32-byte owner salt, which combines with the owner’s pubkey to form
the
community_id; - the
community_root, the access key every member will hold; - the
control_root, the write key only the owner and staff will hold.
Genesis is exactly two owner-signed editions: the community’s metadata and
one public channel named #general. There are no default roles; the creator
sets up everything else.
Identity is a commitment to the owner
Section titled “Identity is a commitment to the owner”community_id = sha256("concord/community" || owner_pubkey || owner_salt)Because the id commits to the owner’s key, ownership is unforgeable: putting a different owner on an existing id would require a second-preimage attack on SHA-256. The salt is not secret and rides inside invites, so any member can recompute the id and confirm the founder.
The downside is that ownership can’t be transferred. A lost owner key can’t be replaced, and whoever steals it has full control. A voluntary, owner-signed transfer is listed as possible future work.
Access is separate from identity
Section titled “Access is separate from identity”The community_root is not derived from the community_id. That way access
can rotate while identity stays fixed, so a community can cut off a removed
member without becoming a different community.
Holding the current community_root is membership. There is no list to check.
Why staff hold a second key
Section titled “Why staff hold a second key”The Control Plane carries state every member must keep complete. If any member could publish there, any member could flood it — valid-looking wraps by the mile, each of which the whole community must fetch and decrypt, burying moderator actions behind spam.
So the plane’s write capability is split off into the control_root. Every
member holds the derived pubkey, which is all reading takes; only staff hold
the secret.
This only controls who can publish. A valid wrap shows that some
control_root holder posted it, not who, and not whether they were allowed to.
A demoted staff member who kept the secret can flood the plane and do nothing
else, until the next rotation takes the key away.
The three planes
Section titled “The three planes”| Plane | Count | Who writes | Carries |
|---|---|---|---|
| Control | one per community | staff only | metadata, roles, grants, banlist, channel definitions, pins |
| Chat | one per channel | any keyholder | messages and everything about them |
| Guestbook | one per community | any member | joins, leaves, kicks, snapshots |
The Guestbook is off-consensus: nothing in Control or Chat depends on it, so it loads last and can lag without harm. Clients fold it flat — one final state per member, latest wins — merge it with everyone they have observed publishing, and subtract the banlist to get the member list.
Observation only counts forward: a member re-enters the list on activity newer than their latest leave, kick, or ban, so old history can never resurrect a departed member.
Metadata
Section titled “Metadata”One Control Plane entity holds the community’s name (max 64 bytes), optional
description (max 10,000 bytes), relay list, optional icon and banner, the
disappearing-messages timer, and a client-extensible custom object.
Images never touch a media server in plaintext. Each is encrypted under a fresh random key and uploaded as an ordinary blob; the entity carries only a pointer and a hash, so the server learns nothing and a swapped blob fails closed.
Up to 5 relays is the recommendation, not a rule. Past that, extra relays cost more than they buy: every publish fans out N times and every fetch waits on the slowest.
Ordering
Section titled “Ordering”Nostr’s created_at has second granularity, which is not enough to order two
messages in the same second. Concord adds an ["ms", 0..999] tag to rumors, and
every comparison in the protocol — message order, guestbook recency, community
list tiebreaks — uses created_at * 1000 + ms.
Multi-device membership
Section titled “Multi-device membership”A member’s memberships sync as the Community List: one self-encrypted, replaceable event holding every community they are in and every one they have left.
It keeps two snapshots per community. seed holds the earliest epoch you ever held, anchoring full-history backfill; current holds the latest, so a fresh device reconstructs everything instantly with no epoch-by-epoch walk.
Leaving writes a permanent tombstone, so a long-offline device can never resurrect a community you left — while a genuine re-join still wins, because the newest of the two timestamps decides.
Dissolution
Section titled “Dissolution”A community ends by an owner-signed tombstone published at a coordinate derived
from the community_id alone — no key and no epoch involved, so every member
past or present resolves the same address, and a refounding can never strand the
grave.
A tombstone always wins. A refounding can’t undo it, and it can’t be reversed. One thing still works afterwards: members can delete their own past messages, since a deletion can’t add content and members should be able to remove what they wrote.