decentrl.
Specifications

DCTRL-0006: Group Messaging

Group identities, shared symmetric group keys, pairwise control channels, and key rotation.

FieldValue
RFCDCTRL-0006
TitleGroup Messaging
StatusDraft
Created2026-03-14
Version0.1
RequiresDCTRL-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

  1. Introduction
  2. Terminology
  3. Group Identity
  4. Group Key
  5. Group Creation
  6. Member Management
  7. Group Event Envelope
  8. Sending Group Events
  9. Receiving Group Events
  10. Key Rotation
  11. Multi-Mediator Synchronization
  12. Operational Complexity
  13. Security Considerations
  14. 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:decentrl format [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 key
  • GK_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 channel

The 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 FieldGroup FieldChange
recipient_didgroup_didIdentifies the group, not an individual
contract_idkey_versionIdentifies 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

OperationAdmin CostPer-Member Cost
Send/receive messageO(1) — encrypt/decrypt with GK_vN
Add memberO(1) — one contract + one key distributionO(1) — receive key
Remove member / rotate keyO(N) — distribute new key to N remaining membersO(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:1Group
Key sourceX25519 ephemeral key agreementRandom, distributed by admin
Key rotationNew contract with fresh ephemeral keysNew GK distributed via pairwise channels
Rotation costO(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_vN are readable by anyone who possesses GK_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

PropertySignal Sender KeysDecentrl Group
Key typePer-sender symmetric chainShared group key
Rotation triggerMembership changeMembership change or periodic
Key distributionCentral serverAdmin via pairwise contracts
Forward secrecyPer-message (chain ratchet)Per-rotation
Multi-deviceComplex (all devices need all sender keys)Simple (one group key per version)
Message orderingServer-imposedGroup 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:decentrl DID Method".
  • [DCTRL-0002] "Cryptographic Operations".
  • [DCTRL-0003] "Communication Contracts".
  • [DCTRL-0004] "Mediator Protocol".
  • [DCTRL-0005] "Event Encryption and Storage".