Custom proposals
OpenMLS allows the creation and use of application-defined proposals. To create such a proposal, the application needs to define a Proposal Type in such a way that its value doesn't collide with any Proposal Types defined in Section 17.4. of RFC 9420. If the proposal is meant to be used only inside of a particular application, the value of the Proposal Type is recommended to be in the range between 0xF000
and 0xFFFF
, as that range is reserved for private use.
Custom proposals can contain arbitrary octet-strings as defined by the application. Any policy decisions based on custom proposals will have to be made by the application, such as the decision to include a given custom proposal in a commit, or whether to accept a commit that includes one or more custom proposals. To decide the latter, applications can inspect the queued proposals in a ProcessedMessageContent::StagedCommitMesage(staged_commit)
.
Example on how to use custom proposals:
// Define a custom proposal type
let custom_proposal_type = 0xFFFF;
// Define capabilities supporting the custom proposal type
let capabilities = Capabilities::new(
None,
None,
None,
Some(&[ProposalType::Custom(custom_proposal_type)]),
None,
);
// Generate KeyPackage that signals support for the custom proposal type
let bob_key_package = KeyPackageBuilder::new()
.leaf_node_capabilities(capabilities.clone())
.build(ciphersuite, provider, &bob_signer, bob_credential_with_key)
.unwrap();
// Create a group that supports the custom proposal type
let mut alice_group = MlsGroup::builder()
.with_capabilities(capabilities.clone())
.ciphersuite(ciphersuite)
.build(provider, &alice_signer, alice_credential_with_key)
.unwrap();
// Create a custom proposal based on an example payload and the custom
// proposal type defined above
let custom_proposal_payload = vec![0, 1, 2, 3];
let custom_proposal =
CustomProposal::new(custom_proposal_type, custom_proposal_payload.clone());
let (custom_proposal_message, _proposal_ref) = alice_group
.propose_custom_proposal_by_reference(provider, &alice_signer, custom_proposal.clone())
.unwrap();
// Have bob process the custom proposal.
let processed_message = bob_group
.process_message(
provider,
custom_proposal_message.into_protocol_message().unwrap(),
)
.unwrap();
let ProcessedMessageContent::ProposalMessage(proposal) = processed_message.into_content()
else {
panic!("Unexpected message type");
};
bob_group
.store_pending_proposal(provider.storage(), *proposal)
.unwrap();
// Commit to the proposal
let (commit, _, _) = alice_group
.commit_to_pending_proposals(provider, &alice_signer)
.unwrap();
let processed_message = bob_group
.process_message(provider, commit.into_protocol_message().unwrap())
.unwrap();
let staged_commit = match processed_message.into_content() {
ProcessedMessageContent::StagedCommitMessage(staged_commit) => staged_commit,
_ => panic!("Unexpected message type"),
};
// Check that the proposal is present in the staged commit
assert!(staged_commit.queued_proposals().any(|qp| {
let Proposal::Custom(custom_proposal) = qp.proposal() else {
return false;
};
custom_proposal.proposal_type() == custom_proposal_type
&& custom_proposal.payload() == custom_proposal_payload
}));