CORD-07: Audio/Video
Voice and video calls in any Channel, with no host and no roster. No server can check membership before handing out media tokens, so clients prove possession of the Channel’s key instead: anyone holding it can derive a signing key, fetch a short-lived token from a blind broker, and connect to a WebRTC SFU (e.g. LiveKit). Media is end-to-end encrypted under keys only members can derive, so the broker and the SFU only ever forward ciphertext.
1. Voice Keys
Section titled “1. Voice Keys”Every Channel is callable — there is no separate “voice Channel” type. A call is simply started in a Channel, and any member holding the Channel’s key can derive its call coordinates and join. Two sub-keys derive from the Channel’s secret through the frozen shapes of CORD-02 Appendix A, fed by the same secret and epoch that address its Chat Plane (CORD-03 §1):
voice_key = group_key("concord/voice-signer", channel_secret, channel_id, epoch)voice_media_key = hkdf(channel_secret, "concord/voice-media", channel_id, epoch)where (channel_secret, epoch) is (community_root, root_epoch) for a Public Channel and (channel_key, channel_epoch) for a Private one, exactly as in CORD-03.
voice_key.pk(x-only, 64-char lowercase hex) is the SFU room name, andvoice_key.sksigns token grants (§2). It is never a stream address; thegroup_keyshape is reused only for its deterministic keypair (CORD-02 A.2–A.3).voice_media_keyis the raw 32-byte root of media encryption; every publisher’s per-sender frame key derives from it (§3).
Both ride the Channel’s epoch, so they rotate exactly when the Channel’s key does: a Rekey or Refounding (CORD-06) rolls the room name and the media key, clients rejoin the new room, and the same rotation that severs a removed member from chat severs them from calls. Until the rotation lands, a removed member can still derive both keys and join — the same window chat itself accepts.
2. The Broker
Section titled “2. The Broker”A broker is a simple service that mints SFU tokens. It holds no Community secrets and keeps no state beyond a transient anti-replay set.
GET /.well-known/concord/av → 204 No Content (capability probe)GET /.well-known/concord/av/<voice_room> → { "token": "<jwt>", "url": "<sfu ws url>", "identity": "<assigned>" }Both endpoints MUST be served over TLS: the grant below is a bearer credential for its whole freshness window, and only the minting broker’s replay set defends it against reuse.
To request a token, the client signs a NIP-98-style event (kind 27235) with voice_key.sk, so event.pubkey equals the room name, and sends it base64-encoded in the header Authorization: Concord <base64(event)>. The grant lives only in that header; it never touches a relay:
{ "kind": 27235, "pubkey": "<voice_room>", "content": "", "tags": [ ["u", "https://broker.example/.well-known/concord/av/<voice_room>"], ["method", "GET"] ], "created_at": 1686840217, "sig": "<signature by voice_key.sk>"}The broker accepts if:
- The signature is valid.
event.pubkeyequals<voice_room>from the path.- The
uandmethodtags match this exact request. created_atis within ±60s.- The event id has not been seen before (ids are retained for at least 240 seconds, twice the ±60s freshness window).
It then mints a short-lived token scoped to joining that one room, under a random SFU identity it assigns to the participant, carried inside the token and echoed as the response’s identity so the client can announce it in presence (§4). The identity MUST carry at least 128 bits of fresh entropy: it feeds the per-sender key derivation (§3), so two participants colliding on one identity would share one frame key and one IV space — exactly the nonce-domain collision §3 exists to prevent (and an SFU treats identity reuse as the same participant reconnecting, kicking the first). Only a holder of the Channel’s key can derive voice_key.sk, so the broker needs no lookup and no Community knowledge: it cannot tell which Community a room belongs to, and it never learns who is joining.
Blindness has stated costs. The broker and SFU still see IPs and connection timing; members who need those hidden should use an anonymizing transport. The room name, though meaningless, is stable for a whole epoch, and epochs bump only on removals (CORD-02 §3), so one broker serving a long-lived channel can link its calls, participant counts, and durations across months under a single label; a community that finds that intolerable spreads its calls across brokers, or rotates by Rekey (CORD-06). Blindness also makes the broker an open service: anyone can mint a random keypair, call its pubkey a room, and pass every check above, so an operator carries strangers’ media by design. Abuse is bounded without identity — per-IP and per-room rate limits, participant caps, short token TTLs — never by allow-listing rooms or callers, which would end the blindness.
3. Media Encryption
Section titled “3. Media Encryption”Media MUST be end-to-end encrypted, and voice_media_key never feeds a cipher directly: every publisher encrypts under a per-sender key, so members never share one AEAD nonce domain (two senders colliding an IV under one GCM key is catastrophic; distinct keys make a collision harmless):
sender_key = hkdf(voice_media_key, "concord/voice-sender", sha256(utf8(identity)))where identity is that publisher’s broker-assigned SFU identity (§2), the label rides the frozen registry (CORD-02 A.6), and the epoch field is omitted — voice_media_key already carries it. The sender_key is that publisher’s per-sender key material: frames are encrypted with AES-256-GCM keyed from it — under the raw 32 bytes directly, or through the media layer’s own fixed, deterministic KDF when the client rides an established frame-encryption layer (e.g. an SFrame-style insertable-streams implementation). Either way the keying stays strictly per-sender, an IV MUST never repeat under one derived frame key (a per-sender monotonic counter suffices), and every client in a room MUST use the same media layer, or frames simply don’t decode. A frame layer MAY leave the few codec header bytes an SFU needs for routing unencrypted — metadata, never content. A receiver derives each publisher’s key from the identity the SFU presents for the track, so every member computes every key and there is no in-band exchange.
The media layer has no sender authentication among key-holders: any member can derive any sender key; the separation partitions nonces, it never proves authorship. Attribution rests on the SFU identity being broker-assigned and single-use, checked against signed presence and its conflict rule (§4), which is sound unless a member colludes with the broker or SFU.
4. Presence
Section titled “4. Presence”Who is in a call is announced over the Channel itself, so relays and brokers stay blind. Heartbeats are realtime-only, so presence rides the ephemeral wrap (kind 21059, CORD-02 Appendix B) at the Channel’s own address, sealed encrypted (kind 20013) like everything else on the Chat Plane (CORD-02 §5), and the rumor commits the same binding tags as any Chat rumor (CORD-03 §3):
{ "kind": 23313, "pubkey": "<member>", "content": "joined", // or "left" "tags": [ ["channel", "<channel_id>"], ["epoch", "0"], ["identity", "<SFU identity>"], // joined only ["broker", "https://broker.example"], // joined only ["ms", "417"] ] }- On join and every 30 seconds thereafter, publish a
joinedcarrying the broker-assigned SFUidentityand thebrokerorigin. On leave, best-effort publish aleft(identity and broker omitted); a missed one heals by staleness. The verbs are past-tense on purpose:joined/leftdescribe a call, never membership, distinct from the Guestbook’sjoin/leave(CORD-02 §5). - Per author, the latest presence wins, on the millisecond basis of CORD-02 §4. A
joinedolder than 90 seconds (three missed heartbeats) is stale and counts as absent. - A client renders an SFU participant as a member only if exactly one author’s fresh signed presence claims that identity. Identities are member-visible, so a malicious member can copy a victim’s into their own
joined: a contested claim proves nothing about either author, so all claimants of one identity render as unverified until the stale claims age out. Anything unclaimed is likewise shown as unverified.
5. Rendezvous
Section titled “5. Rendezvous”The broker tag on live presence lets members converge on one room with no configuration:
- If anyone is present, join their broker.
- On a tie, pick the origin with the smallest
sha256(voice_room[32] || utf8(origin)), whereoriginis the RFC 6454 ASCII serialization (lowercase scheme and host, default port omitted, no path and no trailing slash) — one canonical byte-form, or two clients hash different strings for one broker and the tie never settles. - If the room is empty, use your own preferred broker. Presence is ephemeral and never stored, so a joiner SHOULD listen for one full heartbeat interval (30 seconds, §4) before concluding a room is empty; joining sooner is safe, it just risks the split below.
- Probe the capability endpoint first and fall back if unreachable.
A split (two subsets of one call on two brokers, e.g. simultaneous joins into an empty room) heals by the same tie-break: clients seeing presence for an occupied origin that beats their own migrate to the winner.
The hint is untrusted input from a fellow member, not an authorization. A malicious member can steer the call to a broker/SFU of their choosing, which then sees IPs and timing, but E2EE means it can never decode the media. The tie-break is likewise grindable — an origin can be mined to always win — and that buys nothing more than the hint already grants.
6. Video and Screenshare
Section titled “6. Video and Screenshare”Video and screenshare are the same call: a member additionally publishes camera or screen tracks to the room, under the same per-sender keying and the same presence. No additional keys or events are defined.
7. Moderation
Section titled “7. Moderation”No server checks permissions, so a call carries no enforceable mute: the SFU is blind and CORD-04’s bits never reach it. Client-side discipline covers the polite cases — a client can locally silence any participant, and clients SHOULD refuse to render tracks whose identity fails presence verification (§4) — but a member determined to publish cannot be stopped mid-call by any signed edict. The enforceable lever is the one chat already has: Kick, Ban, and Rekey (CORD-06) rotate the room name and the media key out from under the target. Removal from the Channel is removal from its calls.