Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/openmls/openmls/llms.txt

Use this file to discover all available pages before exploring further.

Proposals are requests to modify the group state. They are collected and applied atomically when a Commit is processed.

Proposal Types

OpenMLS supports multiple proposal types defined by RFC 9420 and extensions.

ProposalType

pub enum ProposalType {
    Add,
    Update,
    Remove,
    PreSharedKey,
    Reinit,
    ExternalInit,
    GroupContextExtensions,
    SelfRemove,
    #[cfg(feature = "extensions-draft-08")]
    AppEphemeral,
    #[cfg(feature = "extensions-draft-08")]
    AppDataUpdate,
    Grease(u16),
    Custom(u16),
}
Add
0x0001
Request to add a new member to the group
Update
0x0002
Request to update the sender’s leaf node (requires path)
Remove
0x0003
Request to remove a member from the group (requires path)
PreSharedKey
0x0004
Request to inject a pre-shared key into the key schedule
Reinit
0x0005
Request to reinitialize the group with new parameters
ExternalInit
0x0006
Used in external commits to join a group (requires path)
GroupContextExtensions
0x0007
Request to update group context extensions (requires path)
SelfRemove
0x000a
Extension: Request to remove oneself from the group (requires path)

Proposal Enum

The Proposal enum contains all proposal variants.
pub enum Proposal {
    Add(Box<AddProposal>),
    Update(Box<UpdateProposal>),
    Remove(Box<RemoveProposal>),
    PreSharedKey(Box<PreSharedKeyProposal>),
    ReInit(Box<ReInitProposal>),
    ExternalInit(Box<ExternalInitProposal>),
    GroupContextExtensions(Box<GroupContextExtensionProposal>),
    SelfRemove,
    Custom(Box<CustomProposal>),
}

Methods

proposal_type()
ProposalType
Returns the type of this proposal
is_path_required()
bool
Returns true if a Commit containing this proposal requires an update path
is_remove()
bool
Returns true if this is a Remove proposal

AddProposal

Request to add a new member with the specified KeyPackage.
// RFC 9420
struct {
    KeyPackage key_package;
} Add;
key_package
KeyPackage
required
The KeyPackage of the new member to add

Example

use openmls::prelude::*;

// Create add proposal
let proposal = group.propose_add_member(
    provider,
    &signer,
    &joiner_key_package
)?;

UpdateProposal

Request to replace the sender’s leaf node with a new one.
// RFC 9420
struct {
    LeafNode leaf_node;
} Update;
leaf_node
LeafNode
required
The new leaf node to replace the sender’s current leaf

Example

// Propose update (generates new leaf node)
let proposal = group.propose_self_update(
    provider,
    &signer,
)?;
Update proposals require the commit to include an update path to refresh encryption keys.

RemoveProposal

Request to remove a member at the specified leaf index.
// RFC 9420
struct {
    uint32 removed;
} Remove;
removed
uint32
required
The leaf index of the member to remove

Example

// Propose to remove a member
let leaf_index = LeafNodeIndex::new(5);
let proposal = group.propose_remove_member(
    provider,
    &signer,
    leaf_index
)?;

PreSharedKeyProposal

Request to inject a pre-shared key into the key schedule.
// RFC 9420
struct {
    PreSharedKeyID psk;
} PreSharedKey;
psk
PreSharedKeyId
required
Identifier for the pre-shared key to inject

Example

use openmls::prelude::*;

// Create PSK proposal
let psk_id = PreSharedKeyId::new(/* ... */);
let proposal = PreSharedKeyProposal::new(psk_id);

ReInitProposal

Request to reinitialize the group with different parameters.
// RFC 9420
struct {
    opaque group_id<V>;
    ProtocolVersion version;
    CipherSuite cipher_suite;
    Extension extensions<V>;
} ReInit;
group_id
GroupId
required
The group ID for the new reinitialized group
version
ProtocolVersion
required
The protocol version for the new group
ciphersuite
Ciphersuite
required
The ciphersuite for the new group
extensions
Extensions<GroupContext>
required
Extensions for the new group context

ExternalInitProposal

Used in external commits to provide KEM output for key agreement.
// RFC 9420
struct {
    opaque kem_output<V>;
} ExternalInit;
kem_output
opaque<V>
required
The KEM output from encapsulating to the group’s external public key

GroupContextExtensionProposal

Request to update the group context extensions.
// RFC 9420
struct {
    Extension extensions<V>;
} GroupContextExtensions;
extensions
Extensions<GroupContext>
required
The new set of group context extensions

Example

// Update group context extensions
let new_extensions = Extensions::default();
let proposal = GroupContextExtensionProposal::new(new_extensions);

AppDataUpdateProposal

Available with extensions-draft-08 feature flag
Update or remove application-specific data associated with a component.
struct {
    ComponentID component_id;
    AppDataUpdateOperation op;
    
    select (AppDataUpdate.op) {
        case update: opaque update<V>;
        case remove: struct{};
    };
} AppDataUpdate;
component_id
ComponentId
required
Identifier of the component being updated
operation
AppDataUpdateOperation
required
Either Update(data) or Remove

Example

use openmls::prelude::*;

// Update component data
let component_id = ComponentId::from(1u32);
let proposal = AppDataUpdateProposal::update(
    component_id,
    b"new data".to_vec()
);

// Remove component data
let proposal = AppDataUpdateProposal::remove(component_id);

AppEphemeralProposal

Available with extensions-draft-08 feature flag
Carry ephemeral application data within a proposal.
struct {
    ComponentID component_id;
    opaque data<V>;
} AppEphemeral;
component_id
ComponentId
required
Component identifier
data
opaque<V>
required
Ephemeral application data

CustomProposal

Application-defined proposal with custom semantics.
pub struct CustomProposal {
    proposal_type: u16,
    payload: Vec<u8>,
}
proposal_type
uint16
required
Application-defined proposal type (must not conflict with standard types)
payload
Vec<u8>
required
Application-defined payload data

Example

let custom_proposal = CustomProposal::new(
    0xF001, // Custom type in private use range
    vec![1, 2, 3, 4]
);

ProposalOrRef

Proposals can be included in commits by value or by reference.
// RFC 9420
enum {
    proposal(1),
    reference(2),
} ProposalOrRefType;

struct {
    ProposalOrRefType type;
    select (ProposalOrRef.type) {
        case proposal: Proposal proposal;
        case reference: opaque hash<0..255>;
    }
} ProposalOrRef;
Proposal
Box<Proposal>
Proposal included by value (full proposal in commit)
Reference
Box<ProposalRef>
Proposal included by reference (hash of authenticated content)

Computing References

use openmls::prelude::*;

// Proposal reference is hash of AuthenticatedContent
let proposal_ref = ProposalRef::from_authenticated_content_by_ref(
    crypto,
    ciphersuite,
    &authenticated_content
)?;

Proposal Validation

Proposals undergo validation when processed.

Validation Rules

  • Add: KeyPackage must be valid and not already in group
  • Update: LeafNode must be valid and signed by sender
  • Remove: Target index must exist and be occupied
  • Path Required: Certain proposals require the commit to include an update path

Path Requirements

These proposal types require the commit to include an update path:
  • Update
  • Remove
  • ExternalInit
  • GroupContextExtensions
  • SelfRemove
if proposal.is_path_required() {
    // Commit must include update path
}

Proposal Priority

When multiple proposals target the same leaf, priority determines which applies:
  1. SelfRemove (highest priority)
  2. Remove (later removes override earlier)
  3. Update (lowest priority)

Serialization

Proposals are serialized using TLS encoding.
use tls_codec::{Serialize, Deserialize};

// Serialize
let bytes = proposal.tls_serialize_detached()?;

// Deserialize (requires ProposalIn type for validation)
let proposal_in = ProposalIn::tls_deserialize(&mut bytes.as_slice())?;

Examples

Creating Proposals

use openmls::prelude::*;

// Add member
let add_proposal = group.propose_add_member(
    provider,
    &signer,
    &key_package
)?;

// Update self
let update_proposal = group.propose_self_update(
    provider,
    &signer,
)?;

// Remove member
let remove_proposal = group.propose_remove_member(
    provider,
    &signer,
    leaf_index
)?;

Processing Proposals

// Receive and process proposal
let processed = group.process_message(provider, protocol_message)?;

if let ProcessedMessageContent::ProposalMessage(proposal) => {
    // Proposal is stored in proposal queue
    // Will be applied when a commit is processed
}