Concord vs. Nostr Relay Infrastructure
- 1. What Concord asks of a relay
- 2. Expiry and pruning
- 3. Deletion by any member
- 4. Invites: who can invite whom
- 5. Other relevant properties
Concord runs on Nostr relays, in this article we will explore what Concord asks of a relay, what happens when a relay prunes or expires events, invites and other interesting attributes of the protocol.
1. What Concord asks of a relay
Conflicts with NIP17 relays
Concord does not co-exist with a typical NIP17 inbox relay, and the specs actually conflict for the following reasons:
Concord reverses NIP-59. A normal giftwrap (kind 1059) has a random one-time author and the real recipient in the p tag. Concord flips both: the author is the shared stream key, and the p tag is a per-message random key that is not the recipient’s and that no reader needs to hold (CORD-01; the sender’s client may optionally retain it to delete its own wrap by p tag). Members read a channel by author, {"kinds":[1059],"authors":["<stream pubkey>"]}, never by p tag. That is what lets a whole group share one address.
This means a relay that follows NIP-17’s read rule cannot serve a Concord community. NIP-17 says relays SHOULD only serve a kind 1059 to the pubkey p-tagged on it, enforced via NIP-42 AUTH. Under that rule nobody can read a Concord stream: the p tag is a fresh random value per message, so there is no stable recipient to AUTH as, and a reader would need to AUTH across thousands of distinct p tags to follow one stream. DM-privacy “inbox” relays therefore do not work, which is what the spec’s “communities must choose relays accordingly” refers to.
Relay Requirements
What works is ‘wide-open’ public relay, one which can fend only fend for itself by expiring or TBD when under certain flood attacks on the relay or group itself: one that answers an authors filter for 1059s and returns matches to whoever subscribed. Since such a relay is open and does not gate reads, anyone can pull the 1059s from it, including in bulk by kind alone ({"kinds":[1059]}) with no stream address needed; the events can also be clustered by author, since a pubkey signing many 1059s is a stream rather than a one-off DM. What the key-derived address (CORD-02 §4) provides is narrower: the mapping from a named community to its stream addresses is not publicly computable, so an outsider cannot single out one known community’s traffic by address. It does not keep the ciphertext from being collected. What anyone pulls is ciphertext, and decryption needs the key. Because there is no forward secrecy within an epoch (§5), a key that leaks later decrypts whatever ciphertext was collected; and since relay-side deletion is best-effort (§3) and members keep offline copies, removal from a relay is not an erasure guarantee. Confidentiality here is a property of the key, not of the relay.
The other expectation is that a community’s relays retain durable 1059s rather than pruning them aggressively, since roles, bans, channel definitions and the guestbook all ride on them. Ephemeral events (typing, voice presence) are kind 21059 and are meant to be dropped.
2. Expiry and pruning
Concord stream events cannot opt into NIP-40 expiry: the no-outer-tags rule (CORD-02 App. B) leaves no room for an expiration tag, so expiry only happens as a relay’s blanket policy, not per event. (A Direct Invite is normal NIP-59 and may carry expiry, which suits a one-shot handoff.)
What expiry costs depends on the event type:
- Chat (9/1111/7/5/3302): degrades gracefully. A message need only survive on one of the community’s relays, and devices keep events offline, so a pruning relay loses its copy rather than the history. The exception is cold backfill for a brand-new device: if a message is gone from every relay and no online member rebroadcasts it, it is lost.
- Guestbook (3306/3309/3312): off-consensus, heals by observation. A dropped Join returns when that member next publishes.
- Genesis / founding (metadata
vsk 0plus#general, epoch 0): resilient. Ownership does not need the genesis event, since a joiner recomputescommunity_id = sha256(owner || salt)from the invite bundle. Refounding compaction (CORD-06) rewrites current Control state forward each epoch, carrying the owner’s signatures via plaintext seals, so old editions aging out is expected. The residual case is a community that never refounds, on pruning relays, where epoch-0 editions age out before a device re-seeds them. - Authority editions (roles/grants/channels/banlist): folded and versioned, and the banlist is a single replaceable entity, so only the latest edition must survive. A fresh device rebuilds from the
currentsnapshot in its Community List rather than replaying history. - Rekey blobs (3303): delivered once to advance an epoch. If an offline member’s blob expires before they fetch it, they cannot advance to the new epoch until the rotator re-issues it (Refoundings are idempotent). If the rotator is gone and the blob is pruned, that member stays on the old key until re-invited.
- Dissolution tombstone (
vsk 10): its presence is the “community is dead” state. If it is pruned from the relays a client talks to, that client can treat a dissolved community as live. This is the case where expiry changes correctness rather than just dropping history. - Bare replaceable events (invite bundle 33301, Community/Invite Lists 13302/13303): self-repairing. Relays keep the latest replaceable anyway; an expired bundle makes a link go dead, and the Lists are the user’s own data.
In short, chat and authority state tolerate expiry because durability rests on relay redundancy, offline caches and rebroadcast, and compaction makes old editions expected to age out. The two that depend most on relays retaining them are an unfetched rekey blob and the dissolution tombstone.
3. Deletion by any member
Classic behavior not only used by NIP17 relays, but also almost all nostr relays circa NIP-09 will honor deletes.
Every member holds the shared stream key can publish to a relay that honors NIP-09 deletion by author. Any member can delete any message by signing a deletion as the stream author.
Preventing Deletion
A relay that rejects sender-side giftwrap deletion prevents it. CORD-01 asks clients to use at least one relay configured this way. Members still delete their own content the normal way, a giftwrapped kind-5 published into the stream that honest clients honor.
4. Invites: who can invite whom
Any keyholder can invite anyone, because membership is key possession (CORD-02 §7: “possession of the keys is membership; the invite is simply how they’re handed over”). There is no admission event and no gatekeeper, so an invite is just a way to hand over keys.
Keys change hands three ways:
- Public link (CORD-05 §1-5). Keys sit in an encrypted bundle on relays; the URL fragment carries a 16-byte unlock token that never reaches a server. Revocable (overwrite the bundle with a tombstone), refreshable (re-post fresh keys behind the same URL after a rekey), and registered so members can see a door exists. This is the only path gated by a permission,
CREATE_INVITE(CORD-04, bit1<<6). - Direct Invite (CORD-05 §6). The same bundle giftwrapped straight to an npub. Not revocable once it lands, appears in no registry, and never flips the community public, so it is how a private community grows one npub at a time. No permission gates it.
- The whisper. Keys are 32-byte strings, so any keyholder can pass them out of band. Unobservable and ungateable. The spec: “no permission gates it, because none could.”
So CREATE_INVITE gates only the official, revocable, listed link, not who can let people in. A community can restrict who mints branded links but cannot stop a member cloning their own keys; a leak is a social matter that a Refounding resolves by rotating out whoever was not re-authorized.
Two related points. An invite cannot fake the community: community_id is a self-certifying hash of the owner’s key (CORD-02 §1), and clients reject any bundle whose owner/owner_salt do not reproduce it, so the worst a hostile inviter can do is join. And attribution is only a courtesy: a joiner may echo who invited them, but that tag is joiner-signed and can be omitted or faked, so there is no reliable invite log.
5. Other relevant properties
Spam flood vectors and rate limits (client and relay)
- Per-member rate limiting is not possible at the relay. Every event at a channel shares one author key, so a relay cannot tell one member’s 1,000 messages from 1,000 members’ one. That leaves IP throttles, AUTH plus per-npub quotas (which would need it’s own AUTH key, single use for this relay/community pair, or be risking linking nostr identity to the stream), payment, or PoW on the ephemeral
ptag. - Ban-to-refounding gap. A Ban is a client-side display filter, and the relay cannot pick out a banned member’s events, so members keep downloading and decrypting them until a Refounding rotates the address.
- Old-epoch addresses stay writable. A removed member keeps prior keys. The client-side bound (not yet mandated) is to cap old-epoch history queries with
until: <rotation time>.
Additional considerations
- Non-repudiation, not deniability. Every seal is signed by the author’s real key, so authorship is provable among keyholders and to anyone if keys leak.
- No forward secrecy within an epoch (traded against MLS). One compromised device leaks that epoch’s history, and since the earliest epoch is kept for backfill, a compromise tends to leak most of it.
- No owner succession. A lost owner nsec is terminal; the clean exit is Dissolution.
- Scale ceilings from the 64KB NIP-44 envelope: roughly a 500-npub banlist, 50-community list, 400-member snapshot chunks, 120-recipient rekey blobs. Banlist sharding is future work.
- Traffic and metadata analysis. Stable per-epoch addresses, untweaked timestamps and subscription grouping give relays a rich but ‘anonymous’ activity graph. The Concord design is attempting confidentiality and membership, not masking observability. Eg. They cannot just ‘co-exist’ with other gift-wrap traffic and are easily distinguishable as Concord traffic.
Write a comment