This document provides comprehensive data flow diagrams and architectural overviews for the Marmot Protocol, illustrating how data moves between clients, groups, relays, and storage systems, with detailed cryptographic protections applied at each layer.
- Actor Definitions
- Key Package Distribution Flow
- Group Creation Flow
- Member Invitation Flow
- Group Messaging Flow
- Group Administration Flow
- Cryptographic Protection Layers
- Identity: Nostr keypair (secp256k1)
- MLS Identity: Credential containing Nostr pubkey
- Capabilities: Create groups, send/receive messages, send/receive files, publish KeyPackages, Welcomes, and other events
- Local State: MLS group state, message history, KeyPackages, local media storage
- Role: Store and distribute Nostr events
- Trust Model: Untrusted for confidentiality, relied upon for availability
- Capabilities: Accept, store, query, and distribute events
- Observable Data: Event metadata (kind, timestamps, tags), encrypted content
- Role: Content-addressed storage for encrypted media
- Trust Model: Untrusted - cannot decrypt stored content, relied upon for availability
- Capabilities: Store and retrieve blobs by SHA256 hash
- Observable Data: Encrypted blob hashes, access patterns
sequenceDiagram
participant C as Client
participant R1 as Relay 1
participant R2 as Relay 2
participant R3 as Relay 3
Note over C: Generate MLS Signing Key<br/>Create Credential<br/>Build KeyPackage
C->>C: Create kind: 443 event<br/>Sign with Nostr key
C->>R1: Publish KeyPackage (443)
C->>R2: Publish KeyPackage (443)
C->>R3: Publish KeyPackage (443)
C->>C: Create kind: 10051 event<br/>List relay URLs
C->>R1: Publish Relay List (10051)
C->>R2: Publish Relay List (10051)
C->>R3: Publish Relay List (10051)
Note over C,R3: KeyPackages now discoverable<br/>on user's advertised relays
rect rgb(100, 100, 100)
Note over C: Later: KeyPackage consumed<br/>for group join
C->>R1: DELETE KeyPackage (kind: 5)
C->>R2: DELETE KeyPackage (kind: 5)
C->>R3: DELETE KeyPackage (kind: 5)
end
Data Flow:
- Client generates MLS KeyPackage locally
- KeyPackage published to multiple relays (redundancy)
- Relay list published (helps others find KeyPackages)
- After use, KeyPackage deleted (unless last_resort)
Security Notes:
- KeyPackage content is public (needed for invitations)
- Nostr signature prevents impersonation
- Credential links Nostr identity to MLS signing key
- Last resort KeyPackages can be reused (with quick rotation)
sequenceDiagram
participant A as Admin Client
participant MLS as MLS Library
participant GS as Local Group State
Note over A: User initiates<br/>group creation
A->>MLS: Create new group<br/>with random 32-byte ID
MLS->>MLS: Generate initial<br/>cryptographic state
MLS->>MLS: Create ratchet tree<br/>with admin as founder
A->>A: Build Marmot Group<br/>Data Extension
Note over A: Extension fields:<br/>- version: 2<br/>- nostr_group_id (random 32 bytes)<br/>- name, description<br/>- admin_pubkeys (raw 32-byte keys)<br/>- relays (length-prefixed URLs)<br/>- image fields (optional)
A->>MLS: Add extension to<br/>GroupContext
MLS->>GS: Initialize epoch 0<br/>group state
GS-->>A: Group ready<br/>for invitations
Note over A,GS: Group created locally<br/>Not yet published anywhere
Data Flow:
- Admin generates random MLS group ID (private, never published)
- Generates random
nostr_group_idfor relay routing - Creates Marmot Group Data Extension with metadata
- MLS library initializes group state (epoch 0)
- Group state stored locally only
Security Notes:
- MLS group ID is private (cryptographic boundary)
nostr_group_idis public-ish (observable by relays)- Extension is cryptographically authenticated
- No data leaves client during creation
- Initial creation Commit is applied locally only (never sent to relays)
- This intentionally departs from MLS spec to preserve privacy—no coordination needed when creator is the only member
sequenceDiagram
participant A as Admin Client
participant R as Relays
participant M as Member Client
participant MLS as MLS Library
Note over M: Published KeyPackage<br/>available on relays
A->>R: Query KeyPackages<br/>for target member
R-->>A: KeyPackage event (443)
A->>A: Verify credential matches<br/>Nostr pubkey in event
A->>MLS: Create Add Proposal<br/>with KeyPackage
A->>MLS: Create Commit<br/>(includes Add proposal)
MLS->>MLS: Advance to epoch N+1<br/>Update ratchet tree
A->>MLS: Create Welcome object<br/>for new member
Note over A: Commit references<br/>group state at epoch N+1
A->>R: Publish Commit (kind: 445)
R-->>A: OK confirmation
Note over A: WAIT for relay<br/>confirmation before<br/>sending Welcome
A->>A: Gift-wrap Welcome<br/>using NIP-59
A->>R: Send Welcome (kind: 1059)
R->>M: Deliver Welcome<br/>(subscribed to p tag)
M->>M: Unwrap NIP-59<br/>Decrypt to kind: 444
M->>MLS: Process Welcome<br/>Join group
MLS->>M: Group state at epoch N+1<br/>Ratchet tree, secrets
M->>R: DELETE consumed<br/>KeyPackage
R->>M: Subscribe to Group Events<br/>h tag = nostr_group_id
Note over A,M: Member now in group<br/>at same epoch
Data Flow:
- Admin fetches member's KeyPackage from relays
- Admin creates MLS Add Proposal and Commit
- Critical: Commit published and confirmed BEFORE Welcome sent
- Welcome gift-wrapped and sent privately (NIP-59)
- Member decrypts Welcome and joins group
- Member deletes consumed KeyPackage
Security Notes:
- ✅ Credential validation prevents impersonation
- ✅ Commit/Welcome ordering prevents race conditions
- ✅ Gift-wrapping hides invitation from observers
- ✅ Welcome unsigned (cannot be republished)
⚠️ Relay can observe timing correlation
Why Commit confirmation before Welcome is CRITICAL:
The Welcome message contains the group state at epoch N+1 (the epoch created by the Commit). If the Welcome reaches the new member before the Commit reaches existing members:
- New member activates at epoch N+1
- Existing members are still at epoch N
- State fork occurs: the new member and existing members have incompatible group states
- Messages fail to decrypt across the fork boundary
- Manual recovery (re-invitation or new group creation) is required
Distributed relay networks can and do deliver messages out of order. The admin MUST wait for at least one relay to confirm receipt of the Commit before sending the Welcome. This is the only reliable way to prevent state forks.
Note: This requirement applies to all member additions to existing groups. Initial group creation (where there are no existing members) does not have this constraint.
sequenceDiagram
participant S as Sender Client
participant MLS as MLS Library
participant R as Relays
participant Rec as Recipient Client
Note over S: User types message
S->>S: Create inner event<br/>kind: 9 (text)<br/>pubkey: sender's Nostr key<br/>NO SIGNATURE
S->>MLS: Create ApplicationMessage<br/>with inner event
MLS->>MLS: Encrypt with group<br/>symmetric keys
S->>S: Extract exporter_secret<br/>for current epoch
S->>S: Derive NIP-44 keys<br/>from exporter_secret
S->>S: Encrypt MLSMessage<br/>with NIP-44
S->>S: Generate fresh<br/>ephemeral keypair
S->>S: Create kind: 445 event<br/>ephemeral pubkey<br/>encrypted content<br/>h tag: nostr_group_id
S->>R: Publish Group Event (445)
R->>Rec: Deliver to subscribers<br/>(subscribed to h tag)
Rec->>Rec: Derive NIP-44 keys<br/>from own exporter_secret
Rec->>Rec: Decrypt to MLSMessage
Rec->>MLS: Process MLSMessage
MLS->>MLS: Verify sender in group<br/>Decrypt with group keys
MLS->>Rec: Inner Nostr event
Rec->>Rec: Verify inner pubkey<br/>matches MLS sender
Rec->>Rec: Display message
Note over S,Rec: Message encrypted twice:<br/>MLS + NIP-44
Data Flow:
- Sender creates unsigned inner event
- MLS encrypts with group keys
- NIP-44 encrypts MLS message (exporter_secret)
- Published with ephemeral keypair
- Recipients decrypt NIP-44 layer
- MLS decrypts and authenticates inner content
Security Notes:
- ✅ Double encryption (MLS + NIP-44)
- ✅ Ephemeral key per message (sender privacy)
- ✅ MLS authentication (sender identity)
- ✅ Inner event unsigned (leak protection)
⚠️ Relay sees timing and size patterns
sequenceDiagram
participant M as Member Client
participant MLS as MLS Library
participant R as Relays
participant A as Admin Client
Note over M: Member wants to<br/>propose a change
M->>MLS: Create Proposal<br/>(Add/Remove/Update)
MLS->>MLS: Sign with MLS<br/>signing key
M->>M: Wrap in MLSMessage<br/>Encrypt (MLS + NIP-44)
M->>M: Create kind: 445 event<br/>with ephemeral key
M->>R: Publish Proposal (445)
R->>A: Deliver to admin<br/>(subscribed to h tag)
A->>A: Decrypt and process<br/>Proposal
A->>MLS: Validate Proposal<br/>Check member signature
Note over A: Admin decides whether<br/>to commit this proposal
Note over A: See Commit flow<br/>for next steps
Data Flow:
- Member creates MLS Proposal
- Wrapped in encrypted Group Event
- Published to relays
- Admins receive and review
- Admin may include in future Commit
Security Notes:
- ✅ MLS signature authenticates proposer
- ✅ Encrypted like application messages
⚠️ Any member can create proposals- ✅ Only admins can commit proposals (except self-updates)
Admins can create Commits that include any proposals. Non-admin members can only create self-update Commits (see Key Rotation Flow).
sequenceDiagram
participant A as Admin Client
participant MLS as MLS Library
participant R as Relays
participant M1 as Member 1
participant M2 as Member 2
Note over A: Admin decides to<br/>commit proposals
A->>MLS: Create Commit<br/>(includes proposals)
MLS->>MLS: Advance epoch N → N+1<br/>Update ratchet tree<br/>Generate new secrets
Note over A: DO NOT apply locally yet
A->>A: Wrap Commit in<br/>MLSMessage + NIP-44
A->>A: Create kind: 445 event<br/>ephemeral key
A->>R: Publish Commit (445)
R-->>A: OK confirmation
Note over A: NOW apply locally
A->>MLS: Apply own Commit<br/>Update to epoch N+1
R->>M1: Deliver Commit
R->>M2: Deliver Commit
M1->>M1: Verify sender in<br/>admin_pubkeys array
M1->>MLS: Process Commit
MLS->>MLS: Advance to epoch N+1<br/>Update state
M2->>M2: Verify admin status
M2->>MLS: Process Commit
Note over A,M2: All members now<br/>at epoch N+1
Data Flow:
- Admin creates MLS Commit
- Critical: Published to relays BEFORE applying locally
- Wait for relay confirmation
- Then apply locally
- Members verify admin status
- Members process and advance epoch
- Race conditions handled by timestamp/ID priority
Exception - Initial Group Creation: The very first Commit that creates a group MUST NOT be sent to relays. This initial Commit is applied locally only, establishing epoch 0. It exists purely on the creating admin's device until the first member invitation. This preserves privacy by not exposing group creation metadata to relays. The first Commit published to relays is the one adding the first member. This intentionally departs from MLS spec recommendations—since no other members exist yet, there's no risk of state divergence, and privacy benefits outweigh coordination guarantees.
Security Notes:
- ✅ Admin verification REQUIRED for non-self-update Commits
- ✅ Epoch advancement provides PCS
- ✅ Timestamp ordering prevents forks
⚠️ Multiple admins need coordination- ✅ Only authenticated admins can make structural changes (membership, settings)
sequenceDiagram
participant A as Admin Client
participant MLS as MLS Library
participant R as Relays
participant M as Members
Note over A: Admin wants to update<br/>group name/relays/etc.
A->>A: Create updated<br/>Marmot Group Data Extension
Note over A: Updated fields:<br/>- name or description<br/>- relays list<br/>- nostr_group_id<br/>- admin_pubkeys
A->>MLS: Create GroupContextExtensions<br/>Proposal
A->>MLS: Create Commit<br/>(includes extension update)
MLS->>MLS: Advance epoch<br/>New extension active
A->>R: Publish Commit (445)
A->>MLS: Apply locally
R->>M: Deliver Commit
M->>M: Verify admin status<br/>of sender
M->>MLS: Process Commit
MLS->>M: Updated extension<br/>in GroupContext
Note over M: Update relay subscriptions<br/>if relays changed
M->>R: Subscribe to new relays<br/>(if relay list updated)
Note over A,M: All members now have<br/>updated metadata
Data Flow:
- Admin creates updated extension
- Wraps in Proposal and Commit
- Publishes and waits for confirmation
- Members verify admin and apply
- Update relay subscriptions if needed
Security Notes:
- ✅ Extension changes cryptographically authenticated
- ✅ Admin verification REQUIRED
- ✅ TLS serialization ensures consistency
- ✅ Version field enables future upgrades
⚠️ Oldnostr_group_idmay leak correlation
sequenceDiagram
participant A as Admin Client
participant MLS as MLS Library
participant R as Relays
participant M as Existing Members
participant N as New/Removed Member
alt Add Member
Note over A: See Member Invitation Flow<br/>for complete add sequence
A->>MLS: Add Proposal + Commit
A->>R: Publish Commit
A->>N: Send Welcome (NIP-59)
else Remove Member
A->>MLS: Remove Proposal<br/>for target member
A->>MLS: Create Commit
MLS->>MLS: Advance epoch<br/>Removed member cannot<br/>decrypt new messages
A->>R: Publish Commit (445)
R->>M: Deliver to remaining members
R->>N: Deliver to removed member
N->>N: Process Commit<br/>Realize removed<br/>Delete group state
M->>M: Process Commit<br/>Update member list
end
Note over A,N: Forward secrecy:<br/>Removed member cannot<br/>read future messages
Data Flow:
- Add: Proposal → Commit → Welcome (see detailed flow above)
- Remove: Proposal → Commit → Epoch advance → Keys invalidated
Security Notes:
- ✅ Epoch change invalidates removed member's keys
- ✅ Forward secrecy for future messages
⚠️ Removed member retains historical messages⚠️ Removed member knows group metadata
Members can rotate their signing keys via self-update Commits (preferred) or by creating an Update Proposal for an admin to commit.
Any member can directly commit their own key update without admin involvement:
sequenceDiagram
participant M as Member Client
participant MLS as MLS Library
participant R as Relays
participant Others as Other Members
Note over M: Weekly rotation<br/>or after compromise
M->>MLS: Create self-update Commit<br/>with new signing key
Note over M: Any member can<br/>self-update their own key
M->>R: Publish Commit (445)
R-->>M: OK confirmation
M->>MLS: Apply own Commit
R->>Others: Deliver Commit
Others->>Others: Verify Commit contains<br/>only sender's Update
Others->>MLS: Process Commit
Note over M,Others: Member now using<br/>new signing key<br/>Old key invalidated
Data Flow:
- Member creates self-update Commit (Update for own LeafNode only)
- Published to relays and confirmed
- Other members verify it's a valid self-update
- Epoch advances, new signing key active
Security Notes:
- ✅ Regular rotation limits compromise impact
- ✅ RECOMMENDED: Weekly rotation
- ✅ CRITICAL: Rotate after last_resort KeyPackage use
- ✅ PCS achieved after epoch advance
| Event Kind | TLS | Nostr Sig | Ephemeral Key | NIP-44 | MLS Encrypt | MLS Auth | Inner Unsigned |
|---|---|---|---|---|---|---|---|
| 443 (KeyPackage) | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ Credential | N/A |
| 10051 (Relay List) | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | N/A |
| 444 (Welcome) via NIP-59 | ✅ | ✅ Ephemeral | ✅ | ✅ | ❌ Content is MLS | ❌ | ✅ |
| 445 (Group Event) | ✅ | ✅ Ephemeral | ✅ | ✅ | ✅ | ✅ | ✅ |