DCTRL-0006: Group Messaging
Group identities, shared symmetric group keys, pairwise control channels, and key rotation.
| Field | Value |
|---|---|
| RFC | DCTRL-0006 |
| Title | Group Messaging |
| Status | Draft |
| Created | 2026-03-14 |
| Version | 0.1 |
| Requires | DCTRL-0001, DCTRL-0002, DCTRL-0003, DCTRL-0004, DCTRL-0005 |
Abstract
This document specifies group messaging for the Decentrl Protocol. A group is a first-class did:decentrl identity with its own DID, signing key, and dedicated mediator. Group encryption uses a shared symmetric group key distributed through standard pairwise communication contracts. This specification defines group identity, the group key lifecycle, member management, the group event envelope, key rotation, and multi-mediator synchronization.
Status of This Document
This is a draft specification. Group messaging is designed but not yet implemented.
Table of Contents
- Introduction
- Terminology
- Group Identity
- Group Key
- Group Creation
- Member Management
- Group Event Envelope
- Sending Group Events
- Receiving Group Events
- Key Rotation
- Multi-Mediator Synchronization
- Operational Complexity
- Security Considerations
- References
1. Introduction
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119].
Group messaging in the Decentrl Protocol builds entirely on the 1:1 primitives defined in [DCTRL-0003] and [DCTRL-0005]. There is no special group encryption protocol — a group is a shared symmetric key distributed through normal pairwise contracts.
This design reuses the existing infrastructure (mediators, contracts, event storage) while extending it naturally to multi-party communication. The group's mediator provides canonical event ordering, and the pairwise contracts between the admin and each member provide a secure channel for key distribution.
2. Terminology
Group — A multi-party communication context, represented as a first-class Decentrl identity.
Group Admin — The party that holds the group identity's signing key and manages the group key lifecycle, member additions, and member removals.
Group Key (GK_vN) — A 256-bit AES symmetric key shared by all group members. vN denotes the key version (generation). All group events are encrypted with the current group key.
Control Channel — The standard pairwise communication contract between the group DID and each individual member. Used for distributing the group key and membership notifications. NOT used for group messages.
Group Mediator — The mediator that hosts the group identity. All group events are routed through this mediator, providing canonical ordering.
3. Group Identity
3.1 Structure
A group is a first-class did:decentrl identity. It has:
- A DID following the
did:decentrlformat [DCTRL-0001] - An Ed25519 signing key pair (held by the admin)
- An X25519 pre-key pair (for establishing member control channels)
- A storage key (for the group's own storage needs)
- A dedicated mediator
3.2 Creation
The admin generates the group identity using the same process as any did:decentrl identity [DCTRL-0001 Section 6.1]:
group_identity = {
signing: Ed25519_generate_keypair(),
pre_key: X25519_generate_keypair(),
storage_key: secure_random_bytes(32),
did: construct_did_decentrl(alias, signing.public, pre_key.public, mediator_did)
}The admin MUST register the group identity with its mediator [DCTRL-0003 Section 9].
3.3 Admin Hosting
The admin identity SHOULD be a long-running service (hosted identity) rather than a user's personal device. The admin needs to be online to:
- Establish pairwise contracts with new members
- Distribute rotated keys to all members on removal
- Run periodic key rotation on a schedule
This is a deployment RECOMMENDATION, not a protocol requirement. The protocol works identically regardless of where the admin runs.
4. Group Key
4.1 Properties
The group key (GK_vN) is:
- A 256-bit (32-byte) AES symmetric key
- Generated using a CSPRNG
- Shared by all current group members
- Used directly as the AES-256-GCM key for encrypting group events
- Versioned — each key rotation increments the version number
4.2 Key Versioning
Group keys are identified by their version number, starting at 1:
GK_v1— the initial group keyGK_v2— after the first rotation (member removal, periodic rotation, etc.)GK_vN— the N-th generation
The key_version field in group event envelopes [Section 7] identifies which key was used for encryption.
4.3 Key Distribution
Group keys are distributed through pairwise communication contracts (control channels) between the group DID and each member. Distribution uses standard event publishing [DCTRL-0005]:
function distribute_group_key(admin, member_did, group_key, version):
key_event = {
type: "group.key_distribution",
data: {
group_key: base64_encode(group_key),
key_version: version
}
}
publish_event(key_event, member_did, admin) // via the pairwise control channelThe key distribution event is encrypted with the pairwise contract's root secret, ensuring only the intended member can read it.
5. Group Creation
5.1 Algorithm
function create_group(admin_identity, member_dids, alias, mediator_did):
// 1. Generate group identity
group = create_decentrl_identity(alias, mediator_did)
// 2. Register group with mediator
register_with_mediator(group, mediator_did)
// 3. Generate initial group key
GK_v1 = secure_random_bytes(32)
// 4. For each member: establish control channel and distribute key
for member_did in member_dids:
// Establish pairwise contract between group DID and member
contract = establish_contract(group, member_did)
// Send the group key through the control channel
distribute_group_key(group, member_did, GK_v1, version=1)
// 5. Store group state
store_group_state(group.did, GK_v1, version=1, members=member_dids)6. Member Management
6.1 Adding a Member
function add_member(admin, group, new_member_did, current_GK, current_version):
// 1. Establish pairwise control channel
contract = establish_contract(group, new_member_did)
// 2. Send current group key
distribute_group_key(group, new_member_did, current_GK, current_version)The new member can decrypt events encrypted with GK_vN and later versions, but CANNOT decrypt events from prior key versions (no backward access by default).
6.2 Removing a Member
function remove_member(admin, group, removed_member_did, remaining_members):
// 1. Generate new group key
new_version = current_version + 1
GK_new = secure_random_bytes(32)
// 2. Distribute new key to remaining members ONLY
for member_did in remaining_members:
if member_did != removed_member_did:
distribute_group_key(group, member_did, GK_new, new_version)
// 3. Update stored group state
update_group_state(group.did, GK_new, new_version, remaining_members)The removed member never receives GK_new. They can still read events encrypted with prior key versions (which they already decrypted), but cannot read new events.
6.3 No Explicit Removal Protocol
There is no "remove member" protocol message sent to the removed member. They simply stop receiving new group keys and new group events. Their existing control channel contract will eventually expire.
7. Group Event Envelope
7.1 Wire Format
Group events use a modified envelope that replaces the 1:1 fields:
{
"sender_did": "<DID of the member sending the message>",
"group_did": "<DID of the group>",
"key_version": 3,
"payload": "<AES-GCM encrypted with GK_v3>",
"signature": "<Ed25519 signature by sender>",
"timestamp": 1699123456,
"encrypted_tags": ["<base64 tag1>", "<base64 tag2>"]
}7.2 Field Differences from 1:1
| 1:1 Field | Group Field | Change |
|---|---|---|
recipient_did | group_did | Identifies the group, not an individual |
contract_id | key_version | Identifies which group key generation to use |
7.3 Signature Scope
The signature covers canonical JSON of { group_did, key_version, payload, timestamp }:
signable = { group_did, key_version, payload, timestamp }
signature = sign_json_object(signable, sender_signing_private_key)Note: The sender signs with their OWN signing key (not the group's key). This authenticates the individual sender within the group.
8. Sending Group Events
8.1 Algorithm
function send_group_event(event, group, sender_identity):
// 1. Encrypt with current group key
plaintext = JSON.stringify(event)
payload = AES_GCM_encrypt(plaintext, group.current_GK)
// 2. Build and sign the envelope
envelope = {
sender_did: sender_identity.did,
group_did: group.did,
key_version: group.current_version,
payload: payload,
timestamp: current_time_seconds(),
encrypted_tags: generate_tags(event, sender_identity.signing_private_key)
}
envelope.signature = sign_json_object(
{ group_did: envelope.group_did, key_version: envelope.key_version,
payload: envelope.payload, timestamp: envelope.timestamp },
sender_identity.signing_private_key
)
// 3. Send to group mediator
send_to_mediator(group.mediator, envelope)8.2 Mediator Routing
Group events are sent to the group's mediator (not the sender's or recipient's mediator). The group mediator provides canonical ordering — all members pull from the same source and observe the same event sequence.
9. Receiving Group Events
9.1 Algorithm
function receive_group_events(group, member_identity):
// 1. Pull new events from group mediator
events = query_events(group.mediator, group.did)
for event in events:
// 2. Look up group key by version
GK = group.keys[event.key_version]
if GK is null:
skip // unknown key version
// 3. Decrypt
plaintext = AES_GCM_decrypt(event.payload, GK)
// 4. Verify sender signature
sender_signing_key = resolve_did(event.sender_did).signing_public_key
valid = verify_json_signature(
{ group_did: event.group_did, key_version: event.key_version,
payload: event.payload, timestamp: event.timestamp },
event.signature,
sender_signing_key
)
if not valid:
skip
// 5. Re-encrypt with own storage key and store on own mediator
storage_ciphertext = AES_GCM_encrypt(plaintext, member_identity.storage_key)
save_events(member_identity.mediator, [{
sender_did: event.sender_did,
recipient_did: group.did,
timestamp: event.timestamp,
payload: storage_ciphertext,
encrypted_tags: generate_tags(JSON.parse(plaintext), member_identity.signing_private_key)
}])10. Key Rotation
10.1 Triggers
The group key MUST be rotated when:
- A member is removed from the group
- The admin detects or suspects key compromise
The group key SHOULD be rotated periodically (e.g., every 24 hours) as a security best practice.
10.2 Rotation Process
Rotation follows the member removal process [Section 6.2] without removing anyone:
function rotate_group_key(admin, group, all_members):
new_version = current_version + 1
GK_new = secure_random_bytes(32)
for member_did in all_members:
distribute_group_key(group, member_did, GK_new, new_version)
update_group_state(group.did, GK_new, new_version, all_members)10.3 Key Version Coexistence
After rotation, both old and new key versions MAY be in use temporarily:
- Members who have received the new key start encrypting with it
- Members who haven't yet received the new key continue with the old key
- All members retain old key versions for decryption of older events
Convergence happens naturally as all members receive the new key through their control channels.
10.4 Old Key Retention
Members SHOULD retain old group key versions to decrypt historical events. Unlike 1:1 contract rotation where old keys are eventually deleted, group key versions represent distinct epochs that may need to be decrypted at any time during state reconstruction.
11. Multi-Mediator Synchronization
11.1 Sync Sources
Group members synchronize from multiple sources:
function sync(member_identity, groups):
// 1. Pull pending 1:1 events from own mediator
process_pending_events(member_identity.mediator)
// 2. For each group: pull new events from group mediator
for group in groups:
receive_group_events(group, member_identity)11.2 Canonical Ordering
The group mediator provides canonical ordering for group events. All members pull from the same mediator, so they observe the same event sequence. This eliminates ordering conflicts that would arise if events were routed through individual members' mediators.
11.3 Storage
After receiving group events, each member re-encrypts them with their own storage key and stores them on their own mediator. This ensures:
- Group history is available through the member's own infrastructure
- The member's storage key protects their copy independently
- The member can access group history even if the group mediator goes offline
12. Operational Complexity
12.1 Cost Analysis
| Operation | Admin Cost | Per-Member Cost |
|---|---|---|
| Send/receive message | — | O(1) — encrypt/decrypt with GK_vN |
| Add member | O(1) — one contract + one key distribution | O(1) — receive key |
| Remove member / rotate key | O(N) — distribute new key to N remaining members | O(1) — receive new key |
12.2 Scalability
Key distribution goes through individual pairwise contracts rather than a single group event. The admin sends N small messages (parallelizable), and each member receives only their own. The group mediator is NOT involved in key distribution — it only stores encrypted group events.
There is no protocol-imposed group size limit. The practical limit is the admin's ability to maintain N pairwise contracts and distribute keys to N members.
13. Security Considerations
13.1 Group Key vs Root Secret
Both 1:1 and group use AES-256-GCM with a symmetric key. The difference is key establishment:
| 1:1 | Group | |
|---|---|---|
| Key source | X25519 ephemeral key agreement | Random, distributed by admin |
| Key rotation | New contract with fresh ephemeral keys | New GK distributed via pairwise channels |
| Rotation cost | O(1) | O(N) |
13.2 Admin Trust
The admin holds the group's signing key and controls all key distribution. A compromised or malicious admin can:
- Silently add unauthorized members by distributing the group key to them
- Distribute bad keys, causing decryption failures
- Selectively exclude members from key rotations
The protocol does NOT currently provide transparency mechanisms (e.g., signed member lists, key distribution receipts). For high-trust groups, this SHOULD be addressed in a future version.
13.3 Forward Secrecy
Group key rotation provides the same bounded forward secrecy as 1:1 contract rotation [DCTRL-0005 Section 13]:
- Events encrypted with
GK_vNare readable by anyone who possessesGK_vN - After rotation to
GK_v(N+1), new events are protected by the new key - A removed member retains access to events encrypted with keys they previously received
13.4 No Backward Access
A newly added member receives only the current GK_vN. They cannot decrypt events encrypted with prior key versions unless the admin explicitly distributes old keys (which is NOT recommended by default).
13.5 Member Authentication
Each group event is signed by the individual sender with their own Ed25519 signing key. This ensures:
- The group mediator can verify the sender is a legitimate group participant
- Other members can verify message authenticity
- The admin cannot forge messages from other members (they don't have members' signing keys)
13.6 Comparison with Signal's Sender Keys
| Property | Signal Sender Keys | Decentrl Group |
|---|---|---|
| Key type | Per-sender symmetric chain | Shared group key |
| Rotation trigger | Membership change | Membership change or periodic |
| Key distribution | Central server | Admin via pairwise contracts |
| Forward secrecy | Per-message (chain ratchet) | Per-rotation |
| Multi-device | Complex (all devices need all sender keys) | Simple (one group key per version) |
| Message ordering | Server-imposed | Group mediator-imposed |
14. References
14.1 Normative References
- [RFC 2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.
- [DCTRL-0001] "The
did:decentrlDID Method". - [DCTRL-0002] "Cryptographic Operations".
- [DCTRL-0003] "Communication Contracts".
- [DCTRL-0004] "Mediator Protocol".
- [DCTRL-0005] "Event Encryption and Storage".