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 = ( -
-