Scope: Community-scoped, distributed chat rooms with bounded history.
Properties: No global DHT message storage. History remains available as long as at least one peer persists (“pins”) recent segments. Phones can participate as mostly-clients.
- Chat exists within a community (community membership governs discovery and sync surfaces).
- Messages are signed by authors.
- History is bounded by retention rules (count/time/bytes) to prevent unbounded growth.
- Chat history remains available while at least one peer pins recent history.
- Chat replication does not overload the regular DHT work:
- DHT is not used for message storage.
- Optional DHT use is limited to small, infrequently-updated head pointers.
- Community: An SCP2P community scope (as defined by the main SCP2P spec).
- Room: A named chat channel within a community.
- Event: A single chat message (or control event) signed by an author.
- Segment: A content-addressed blob containing a batch of chat events.
- Pinning: Persisting recent segments locally to keep history available.
- Head: Latest known segment index/hash for a room.
Room identity is derived within a community.
RoomId = SHA-256("scp2p:chat:room" || community_id || room_name_utf8)(32 bytes)room_name_utf8must be normalized (see §10.1) and treated case-sensitively unless the community specifies otherwise.
Segments are content-addressed:
SegmentId = BLAKE3(segment_cbor_bytes)(32 bytes)
Chat authors use existing SCP2P Ed25519 identities.
AuthorId = author_pubkey (32 bytes)
Each event is signed by its author.
CBOR structure (deterministic): MUST be encoded as a fixed-order tuple/array, not a map.
Fields (in order):
version: u8=1room_id: bytes32author_pubkey: bytes32author_seq: u64(monotonic per author per room; starts at 1)timestamp_unix_secs: u64kind: u8payload: bytes(CBOR-encoded by kind)signature: bytes64(Ed25519 over tuple fields 1..7)
Kinds
0x01= Text message0x02= Reaction0x03= Edit (tombstone/replace)0x04= Delete (tombstone)0x10= System/room notice (optionally restricted to moderators; policy is community-defined)
Payloads
- Text (
kind=0x01):ChatTextPayloadV1 - Reaction (
kind=0x02):ChatReactionPayloadV1 - Edit (
kind=0x03):ChatEditPayloadV1 - Delete (
kind=0x04):ChatDeletePayloadV1 - System (
kind=0x10):ChatSystemPayloadV1
Fixed-order CBOR tuple:
text: string(max length policy; see §10.2)reply_to: Option<EventRef>(optional)attachments: Option<Vec<AttachmentRef>>(optional)
EventRef tuple:
author_pubkey: bytes32author_seq: u64
AttachmentRef tuple:
content_id: bytes32(SCP2P content id)name: Option<string>mime: Option<string>size: Option<u64>
Tuple:
target: EventRefemoji: string(or short token)add: bool
Tuple:
target: EventRefnew_text: string
Tuple:
target: EventRefreason: Option<string>
Tuple:
text: stringseverity: u8(0=info,1=warn,2=critical)
Segments batch events to reduce overhead and simplify retention.
Segment size targets
events_per_segment: recommended 200–500max_segment_bytes: recommended 256 KiB–2 MiB (implementation choice; must enforce an upper bound)
CBOR structure (deterministic tuple):
version: u8=1room_id: bytes32segment_index: u64(monotonic per room; starts at 1)prev_segment_id: Option<bytes32>created_at_unix_secs: u64events: Vec<ChatEventV1>(each event is independently signed)segment_publisher_pubkey: bytes32(node/publisher identity, not necessarily a moderator)segment_signature: bytes64(Ed25519 over tuple fields 1..7)
Notes
- Events remain authoritative because each has an author signature.
segment_signatureprotects segment integrity and allows peers to reject tampering early, but does not replace event signatures.
Each node applies local retention rules per community/room:
A node MUST support configuration via any of:
max_messages(e.g. 5,000)max_segments(e.g. 50)max_days(e.g. 30)max_bytes(e.g. 50 MB)
Nodes that pin a room store segments up to their retention limits and serve them to others.
Nodes MAY participate without pinning:
- They keep only a short cache (e.g. last 1–3 segments) or none.
- They can still send and receive live events.
Chat replication is community-scoped and uses two layers:
- Head gossip (small, frequent)
- Segment fetch (on-demand)
Each peer maintains:
room_head: { latest_index, latest_segment_id, updated_at }
Peers exchange heads opportunistically:
- on community join
- periodically (e.g. every 10–60 seconds while connected)
- when a new segment is produced
When a peer learns it is behind:
- it requests missing segments by index from peers that advertise availability
- it verifies segment signature and event signatures
- it stores segments subject to retention/pinning policy
Segment availability can be inferred from:
- peers responding to
CHAT_HEADS_OFFER - direct
CHAT_SEGMENTreplies - optional provider hints (see §8)
Chat messages are additional MsgType values in the SCP2P wire registry.
Direction: any peer → any peer
Purpose: advertise latest room heads
Payload tuple:
community_id: bytes32heads: Vec<RoomHead>(bounded)sent_at_unix_secs: u64
RoomHead tuple:
room_id: bytes32latest_index: u64latest_segment_id: bytes32updated_at_unix_secs: u64
Bounds:
max_heads_per_message(e.g. 64)max_payload_bytesenforced by envelope limits
Direction: requester → peer
Purpose: request a range of segments for a room
Payload tuple:
community_id: bytes32room_id: bytes32start_index: u64count: u16(bounded)want_ids_only: bool(optional optimization)
Direction: peer → requester
Purpose: return segments (or segment ids only)
Payload tuple:
community_id: bytes32room_id: bytes32start_index: u64segments: Vec<bytes>ORsegment_ids: Vec<bytes32>depending on request
Each bytes entry is a CBOR-encoded ChatSegmentV1.
Direction: client → connected peers
Purpose: broadcast new events (low latency) prior to segmentation completion
Payload tuple:
community_id: bytes32room_id: bytes32events: Vec<ChatEventV1>(bounded)
Peers may display/store these events immediately, but MUST eventually reconcile with segments (see §9.2).
Direction: peer → sender
Purpose: acknowledge receipt to prevent redundant retries
Payload tuple:
community_id: bytes32room_id: bytes32author_pubkey: bytes32max_author_seq_received: u64
DHT is not used for event storage. A small pointer can help new joiners find current heads quickly.
key = SHA-256("scp2p:chat:head" || community_id || room_id)
ChatHeadPointer tuple:
version: u8= 1room_id: bytes32latest_index: u64latest_segment_id: bytes32providers: Vec<PeerAddr>(bounded, optional)updated_at_unix_secs: u64signature: bytes64(signed by the publisher writing the pointer)
Update policy
- Updated on segment creation or at a bounded cadence (implementation-defined but infrequent).
- Peers MUST validate signature and freshness, and treat this as a hint.
This pointer is optional. Chat must function without it via peer gossip.
Peers MUST reject events that fail:
- signature verification
- room_id mismatch
- author_seq regression for that author (if maintaining per-author seq tracking)
Peers MAY accept out-of-order sequences temporarily and resolve later, but must not let sequences go backwards in the final displayed state.
POST_CHAT_EVENTS exists for responsiveness. Segment storage is authoritative for history.
Rules:
- When a segment arrives containing events already seen via
POST_CHAT_EVENTS, peers de-duplicate by(author_pubkey, author_seq). - If a peer sees an event not present in any segment for a long time, it may:
- keep it in a short-lived pending cache, and/or
- request missing segments from other peers, and/or
- drop it if it fails to become part of history (policy choice).
Edits and deletes are modeled as new signed events referencing a prior event. Clients apply them as a view-layer transformation.
Original content may remain in history; clients hide/replace it in UI.
room_name_utf8should be normalized to a consistent Unicode form (recommended NFC) before hashing.- Clients must enforce maximum lengths on:
- room names
- message text
- attachment names
- emoji/reaction tokens
Peers MUST enforce rate limiting per (community_id, author_pubkey):
- max events per minute
- max bytes per minute
- max rooms joined simultaneously (optional)
Moderation is community-defined and enforced via:
- subscription trust tiers
- blocklists / rules already present in SCP2P
- optional “moderator feeds” (signed lists of muted authors/messages)
This spec does not mandate centralized deletion.
Nodes MUST persist at least:
- room head state per community
- pinned segments within retention limits
- per-author last-seen sequence numbers (to reject obvious replays)
Nodes MAY persist:
- pending (unsegmented) events cache
- UI read markers, reactions, etc.
A conformance pack for chat MUST include:
- event signing/verification vectors
- segment signing/verification vectors
- deterministic CBOR tuple encoding vectors
- merge/de-dup scenarios for:
- out-of-order arrival
- duplicate events via POST + segment
- edit/delete application
- head gossip message encode/decode vectors