From 739469aa1df2da902fe1ce98adcbb7e1555f8064 Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Thu, 3 Sep 2026 18:02:53 +0700 Subject: [PATCH] Crew chat: frame built-in E2E chat as the default, Cordn as honest opt-in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Decision (per Marmot/Concord/Cordn analysis): a semi-public, socially-verified skate crew fits Concord-style shared-key chat, not MLS. The built-in NIP-17 giftwrap group chat is already the right answer and works with zero setup — so stop implying a coordinator is required and stop over-claiming MLS guarantees the code doesn't yet provide. - CrewCoordinatorCard: default state now reassures that private E2E chat is already ON (no setup) for everyone, instead of prompting founders to "set up a coordinator". Founders get a small, honestly-labeled advanced link to the self-hosted Cordn option. Fixed the "E2E · MLS / forward secrecy" badge — the coordinator path is NIP-44 group encryption routed over ContextVM, not an MLS ratchet. - CrewForm: the coordinator field is now clearly "experimental · self-host", states you don't need it, and links to the Cordn repo. - NIP.md: reworded the crew-chat section + tag/profile descriptions to match the honest crypto seam (NIP-44 group today; MLS a future upgrade), so docs no longer claim forward secrecy that isn't shipped. Co-authored-by: shakespeare.diy --- NIP.md | 51 ++++++++------- src/components/CrewForm.tsx | 24 +++++-- src/components/crew/CrewCoordinatorCard.tsx | 71 ++++++++++++++------- 3 files changed, 91 insertions(+), 55 deletions(-) diff --git a/NIP.md b/NIP.md index f629a20..5ec5dcc 100644 --- a/NIP.md +++ b/NIP.md @@ -14,7 +14,7 @@ other clients, and preserved on edit: | `noffer` | string | CLINK Offer code (`noffer1…`) — self-custodial Bitcoin tip code. | | `bitcoin_silent_payment` | string | Bitcoin Silent Payment address (BIP-352, `sp1…`) — a reusable, private on-chain address. | | `sponsors` | string | Freeform highlight of the skater's sponsors / supporters (brands, shops, people). | -| `cordn` | string | A Cordn (cordn.net) contact URL for private, MLS-encrypted messaging. | +| `cordn` | string | A Cordn (cordn.net) contact URL for private, end-to-end encrypted messaging via a self-hosted coordinator. | `lud16` (Lightning address) is standard NIP-01 but is also editable directly in the SkateSpots profile form for convenience. @@ -2128,7 +2128,7 @@ bare `p` tag on the crew is only a claim until the member self-labels. | `goal` | Optional **crew treasury** — the event id of a NIP-75 Zap Goal (kind `9041`) the crew is raising funds for. See Crew Treasury below. | | `looking-for` | Optional **open call** — a Lab craft the crew is hiring for. Repeatable. Slugs: `filmer`, `photographer`, `editor`, `coach`, `builder`, `artist`, `musician`, `members`. Surfaces the crew's needs so matching Lab providers can reach out. | | `sponsor` | Optional **sponsoring shop** — a NIP-15 stall coordinate (`30017:pubkey:d`) for a local skate shop that backs the crew. Renders a "Sponsored by [Shop]" banner linking to the shop. Only shown when the shop resolves. | -| `coordinator` | Optional **encrypted-chat coordinator** for the crew's private group. Shape: `["coordinator", , , ]`. `` is an `http(s)`/`wss` endpoint of a **Cordn** coordinator (an MLS / RFC 9420 Delivery Service reached over ContextVM). The crew event stays public and discoverable; message content, membership secrets and MLS ratchets live client-side. Nothing crew-specific is published to public relays. `` is the coordinator service pubkey (hex) for ContextVM addressing; `` is the human (hex) who runs it, if delegated. | +| `coordinator` | Optional, **advanced/self-host**: routes the crew's private chat through a founder-run **Cordn** coordinator instead of the built-in NIP-17 chat. Shape: `["coordinator", , , ]`. `` is an `http(s)`/`wss` endpoint of a Cordn coordinator (a Delivery Service reached over ContextVM). The crew event stays public and discoverable; message content and keys live client-side; nothing crew-specific is published to public relays. `` is the coordinator service pubkey (hex) for ContextVM addressing; `` is the human (hex) who runs it, if delegated. Not required — every crew has E2E chat without it. | | `video` | Optional **crew video / montage** ("The Part") — a **YouTube** or **Instagram** URL, rendered as the crew's headline embed. Only these two sources are accepted (skate parts live on YouTube, clips on Instagram); any other URL is ignored. No native/self-hosted video. Clients must parse it to a known embed (YouTube video id / Instagram shortcode) and reject anything else rather than render an untrusted iframe. | | `crew-role` | Optional **skate role** assigned to a roster member. Shape: `["crew-role", , ]`, repeatable (a member may hold several). `` ∈ the full Lab-craft set — `filmer`, `photographer`, `editor`, `coach`, `builder`, `artist`, `graffiti`, `tattoo`, `trainer`, `musician`, `judge` — plus the crew-specific `manager` and `og`. The Lab-craft roles mirror the Lab profession kinds (35001–35012), so a crew's "filmer" speaks the same language as a Lab Filmer profile. These are the *functions* on a skate team (the filmer is as vital as the skaters) and are purely a **flex/label** — they grant **no powers** (only the `admin` marker does). Only honoured for the founder or a listed member; roles on non-roster pubkeys are ignored. | @@ -2469,31 +2469,34 @@ by event id). #### Private Crew Chat (Cordn / MLS) — the `coordinator` tag A crew's **public** identity (roster, spots, events, treasury, sponsor) lives on -Nostr as the kind-`35006` event above. A crew's **private** group chat is designed -to run over **Cordn** — an MLS ([RFC 9420](https://datatracker.ietf.org/doc/rfc9420/)) -Delivery Service coordinator, **one per group**, reached over ContextVM. +Nostr as the kind-`35006` event above. -A crew advertises its coordinator with the optional `coordinator` tag (see the -Optional Tags table). This is intentionally just a **pointer**: +**A crew's private group chat is end-to-end encrypted and on by default** — no +setup. The built-in transport is **NIP-17 giftwrap group DM**: every message is +sealed to each member's key and published as gift wraps, so only the founder and +listed members can read it and relays see only ciphertext. This is the right fit +for a semi-public, socially-verified skate crew (cf. Concord's shared-key model +for "large casual rooms"). -- **On relays:** only the public crew event and a coordinator URL. No - protocol-specific messaging events (no per-message kinds) are ever published to - public relays, so there is nothing crew-specific to fingerprint or cluster. -- **Client-side / under MLS:** message content, the MLS member tree, epoch keys - and ratchets. Forward secrecy and post-compromise security hold; membership - changes are O(log n). +**Optionally**, a technical founder who **self-hosts a +[Cordn](https://github.com/Cordn-msg/cordn) coordinator** (an MLS +[RFC 9420](https://datatracker.ietf.org/doc/rfc9420/) Delivery Service reached +over ContextVM, one per group) can advertise it with the optional `coordinator` +tag (see the Optional Tags table) to route chat through it instead. This is a +**pointer, not a requirement**: + +- **On relays:** only the public crew event and a coordinator URL. With the + coordinator path, no per-message events hit public relays at all, so there is + nothing crew-specific to fingerprint or cluster. - **The coordinator** routes sealed ContextVM traffic and never sees message - content, client IPs, or (for routine sends using ephemeral transport keys) who - is talking. If it is unreachable, chat delivery stalls but the group, its - membership and its history on members' devices survive; groups can migrate - coordinators (per-group cursors preserve ordering). - -Cordn is chosen over Marmot (fully decentralised MLS) and Concord (shared-key, -no forward secrecy) because its **coordinator-per-group** model mirrors a crew's -**leader / coordinator** structure, it is browser/TypeScript-native, and it keeps -the private chat un-fingerprintable next to an already-public, geolocated crew -identity. Until a crew configures a coordinator, the built-in NIP-17 group-DM -chat remains the fallback. + content or client IPs. If it is unreachable, chat delivery stalls but the + group, its membership and its history on members' devices survive; groups can + migrate coordinators (per-group cursors preserve ordering). +- **Crypto status (honest):** the coordinator path currently uses the same + **NIP-44 group encryption** as the built-in chat (see the envelope's crypto + seam below) — real E2E confidentiality, but **not yet** an MLS ratchet, so + forward secrecy / post-compromise security are a future upgrade, not a present + claim. ##### Message envelope (kind `4497`) & delivery diff --git a/src/components/CrewForm.tsx b/src/components/CrewForm.tsx index 003b0cb..1e28a95 100644 --- a/src/components/CrewForm.tsx +++ b/src/components/CrewForm.tsx @@ -41,7 +41,7 @@ import { import { useCrewGoal } from '@/hooks/useCrewGoal'; import { useAppContext } from '@/hooks/useAppContext'; import { cn } from '@/lib/utils'; -import { Users, Upload, X, Loader2, MapPin, Plus, ShieldCheck, AtSign, HardHat, Camera, GraduationCap, Sparkles, Target, Megaphone, Music, Palette, Dumbbell, Eye, Store, Lock, Film, Check, ArrowRight, ArrowLeft as ArrowLeftIcon } from 'lucide-react'; +import { Users, Upload, X, Loader2, MapPin, Plus, ShieldCheck, AtSign, HardHat, Camera, GraduationCap, Sparkles, Target, Megaphone, Music, Palette, Dumbbell, Eye, Store, Lock, Film, Check, ArrowRight, ArrowLeft as ArrowLeftIcon, Server } from 'lucide-react'; const FOCUS_ORDER: CrewFocus[] = ['mixed', 'builder', 'photo', 'coaching', 'art', 'music', 'wellness']; const FOCUS_ICONS: Record = { @@ -1082,14 +1082,24 @@ export function CrewForm({ existing }: CrewFormProps) { ); const coordinatorBlock = ( -
-