diff --git a/openmls/src/group/public_group/tests.rs b/openmls/src/group/public_group/tests.rs index 6a9544999..cbff923bf 100644 --- a/openmls/src/group/public_group/tests.rs +++ b/openmls/src/group/public_group/tests.rs @@ -21,281 +21,284 @@ use crate::test_utils::*; #[apply(ciphersuites_and_backends)] async fn public_group(ciphersuite: Ciphersuite, backend: &impl OpenMlsCryptoProvider) { - let group_id = GroupId::from_slice(b"Test Group"); - - let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) = - setup_client("Alice", ciphersuite, backend).await; - let (_bob_credential, bob_kpb, bob_signer, _bob_pk) = - setup_client("Bob", ciphersuite, backend).await; - let (_charlie_credential, charlie_kpb, charlie_signer, _charlie_pk) = - setup_client("Charly", ciphersuite, backend).await; - - // Define the MlsGroup configuration - // Set plaintext wire format policy s.t. the public group can track changes. - let mls_group_config = MlsGroupConfigBuilder::new() - .wire_format_policy(PURE_PLAINTEXT_WIRE_FORMAT_POLICY) - .crypto_config(CryptoConfig::with_default_version(ciphersuite)) - .build(); - - // === Alice creates a group === - let mut alice_group = MlsGroup::new_with_group_id( - backend, - &alice_signer, - &mls_group_config, - group_id, - alice_credential_with_key, - ) - .await - .expect("An unexpected error occurred."); - - // === Create a public group that tracks the changes throughout this test === - let verifiable_group_info = alice_group - .export_group_info(backend, &alice_signer, false) - .unwrap() - .into_verifiable_group_info() - .unwrap(); - let ratchet_tree = alice_group.export_ratchet_tree(); - let (mut public_group, _extensions) = PublicGroup::from_external( - backend, - ratchet_tree.into(), - verifiable_group_info, - ProposalStore::new(), - true, - ) - .await - .unwrap(); - - // === Alice adds Bob === - let (message, welcome, _group_info) = alice_group - .add_members( + Box::pin(async { + let group_id = GroupId::from_slice(b"Test Group"); + + let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) = + setup_client("Alice", ciphersuite, backend).await; + let (_bob_credential, bob_kpb, bob_signer, _bob_pk) = + setup_client("Bob", ciphersuite, backend).await; + let (_charlie_credential, charlie_kpb, charlie_signer, _charlie_pk) = + setup_client("Charly", ciphersuite, backend).await; + + // Define the MlsGroup configuration + // Set plaintext wire format policy s.t. the public group can track changes. + let mls_group_config = MlsGroupConfigBuilder::new() + .wire_format_policy(PURE_PLAINTEXT_WIRE_FORMAT_POLICY) + .crypto_config(CryptoConfig::with_default_version(ciphersuite)) + .build(); + + // === Alice creates a group === + let mut alice_group = MlsGroup::new_with_group_id( backend, &alice_signer, - vec![bob_kpb.key_package().clone().into()], + &mls_group_config, + group_id, + alice_credential_with_key, ) .await - .expect("Could not add member to group."); - - alice_group - .merge_pending_commit(backend) - .await - .expect("error merging pending commit"); - - let public_message = match message.into_protocol_message().unwrap() { - ProtocolMessage::PrivateMessage(_) => panic!("Unexpected message type."), - ProtocolMessage::PublicMessage(public_message) => public_message, - }; - let processed_message = public_group - .process_message(backend, public_message) - .await - .unwrap(); - - // Further inspection of the message can take place here ... - match processed_message.into_content() { - ProcessedMessageContent::ApplicationMessage(_) - | ProcessedMessageContent::ProposalMessage(_) - | ProcessedMessageContent::ExternalJoinProposalMessage(_) => { - panic!("Unexpected message type.") - } - ProcessedMessageContent::StagedCommitMessage(staged_commit) => { - // Merge the diff - public_group.merge_commit(*staged_commit) - } - }; - - // In the future, we'll use helper functions to skip the extraction steps above. - - let mut bob_group = MlsGroup::new_from_welcome( - backend, - &mls_group_config, - welcome.into_welcome().expect("Unexpected message type."), - Some(alice_group.export_ratchet_tree().into()), - ) - .await - .expect("Error creating group from Welcome"); - - // === Bob adds Charlie === - let (queued_messages, welcome, _group_info) = bob_group - .add_members( + .expect("An unexpected error occurred."); + + // === Create a public group that tracks the changes throughout this test === + let verifiable_group_info = alice_group + .export_group_info(backend, &alice_signer, false) + .unwrap() + .into_verifiable_group_info() + .unwrap(); + let ratchet_tree = alice_group.export_ratchet_tree(); + let (mut public_group, _extensions) = PublicGroup::from_external( backend, - &bob_signer, - vec![charlie_kpb.key_package().clone().into()], + ratchet_tree.into(), + verifiable_group_info, + ProposalStore::new(), + true, ) .await .unwrap(); - // Alice processes - let alice_processed_message = alice_group - .process_message( - backend, - queued_messages - .clone() - .into_protocol_message() - .expect("Unexpected message type"), - ) - .await - .expect("Could not process messages."); - if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = - alice_processed_message.into_content() - { + // === Alice adds Bob === + let (message, welcome, _group_info) = alice_group + .add_members( + backend, + &alice_signer, + vec![bob_kpb.key_package().clone().into()], + ) + .await + .expect("Could not add member to group."); + alice_group - .merge_staged_commit(backend, *staged_commit) + .merge_pending_commit(backend) .await - .expect("Error merging commit."); - } else { - unreachable!("Expected a StagedCommit."); - } + .expect("error merging pending commit"); + + let public_message = match message.into_protocol_message().unwrap() { + ProtocolMessage::PrivateMessage(_) => panic!("Unexpected message type."), + ProtocolMessage::PublicMessage(public_message) => public_message, + }; + let processed_message = public_group + .process_message(backend, public_message) + .await + .unwrap(); + + // Further inspection of the message can take place here ... + match processed_message.into_content() { + ProcessedMessageContent::ApplicationMessage(_) + | ProcessedMessageContent::ProposalMessage(_) + | ProcessedMessageContent::ExternalJoinProposalMessage(_) => { + panic!("Unexpected message type.") + } + ProcessedMessageContent::StagedCommitMessage(staged_commit) => { + // Merge the diff + public_group.merge_commit(*staged_commit) + } + }; - // The public group processes - let ppm = public_group - .process_message(backend, into_public_message(queued_messages)) - .await - .unwrap(); - public_group.merge_commit(extract_staged_commit(ppm)); + // In the future, we'll use helper functions to skip the extraction steps above. - // Bob merges - bob_group - .merge_pending_commit(backend) + let mut bob_group = MlsGroup::new_from_welcome( + backend, + &mls_group_config, + welcome.into_welcome().expect("Unexpected message type."), + Some(alice_group.export_ratchet_tree().into()), + ) .await - .expect("error merging pending commit"); - - let mut charlie_group = MlsGroup::new_from_welcome( - backend, - &mls_group_config, - welcome.into_welcome().expect("Unexpected message type."), - Some(bob_group.export_ratchet_tree().into()), - ) - .await - .expect("Error creating group from Welcome"); + .expect("Error creating group from Welcome"); + + // === Bob adds Charlie === + let (queued_messages, welcome, _group_info) = bob_group + .add_members( + backend, + &bob_signer, + vec![charlie_kpb.key_package().clone().into()], + ) + .await + .unwrap(); + + // Alice processes + let alice_processed_message = alice_group + .process_message( + backend, + queued_messages + .clone() + .into_protocol_message() + .expect("Unexpected message type"), + ) + .await + .expect("Could not process messages."); + if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = + alice_processed_message.into_content() + { + alice_group + .merge_staged_commit(backend, *staged_commit) + .await + .expect("Error merging commit."); + } else { + unreachable!("Expected a StagedCommit."); + } - // === Alice removes Bob & Charlie commits === + // The public group processes + let ppm = public_group + .process_message(backend, into_public_message(queued_messages)) + .await + .unwrap(); + public_group.merge_commit(extract_staged_commit(ppm)); - let (queued_messages, _) = alice_group - .propose_remove_member(backend, &alice_signer, LeafNodeIndex::new(1)) - .await - .expect("Could not propose removal"); + // Bob merges + bob_group + .merge_pending_commit(backend) + .await + .expect("error merging pending commit"); - let charlie_processed_message = charlie_group - .process_message( + let mut charlie_group = MlsGroup::new_from_welcome( backend, - queued_messages - .clone() - .into_protocol_message() - .expect("Unexpected message type"), + &mls_group_config, + welcome.into_welcome().expect("Unexpected message type."), + Some(bob_group.export_ratchet_tree().into()), ) .await - .expect("Could not process messages."); + .expect("Error creating group from Welcome"); - // The public group processes - let ppm = public_group - .process_message(backend, into_public_message(queued_messages)) - .await - .unwrap(); - // We have to add the proposal to the public group's proposal store. - match ppm.into_content() { - ProcessedMessageContent::ApplicationMessage(_) - | ProcessedMessageContent::ExternalJoinProposalMessage(_) - | ProcessedMessageContent::StagedCommitMessage(_) => panic!("Unexpected message type."), - ProcessedMessageContent::ProposalMessage(p) => { - match p.proposal() { - Proposal::Remove(r) => assert_eq!(r.removed(), LeafNodeIndex::new(1)), - _ => panic!("Unexpected proposal type"), + // === Alice removes Bob & Charlie commits === + + let (queued_messages, _) = alice_group + .propose_remove_member(backend, &alice_signer, LeafNodeIndex::new(1)) + .await + .expect("Could not propose removal"); + + let charlie_processed_message = charlie_group + .process_message( + backend, + queued_messages + .clone() + .into_protocol_message() + .expect("Unexpected message type"), + ) + .await + .expect("Could not process messages."); + + // The public group processes + let ppm = public_group + .process_message(backend, into_public_message(queued_messages)) + .await + .unwrap(); + // We have to add the proposal to the public group's proposal store. + match ppm.into_content() { + ProcessedMessageContent::ApplicationMessage(_) + | ProcessedMessageContent::ExternalJoinProposalMessage(_) + | ProcessedMessageContent::StagedCommitMessage(_) => panic!("Unexpected message type."), + ProcessedMessageContent::ProposalMessage(p) => { + match p.proposal() { + Proposal::Remove(r) => assert_eq!(r.removed(), LeafNodeIndex::new(1)), + _ => panic!("Unexpected proposal type"), + } + public_group.add_proposal(*p); } - public_group.add_proposal(*p); } - } - // Check that we received the correct proposals - if let ProcessedMessageContent::ProposalMessage(staged_proposal) = - charlie_processed_message.into_content() - { - if let Proposal::Remove(ref remove_proposal) = staged_proposal.proposal() { - // Check that Bob was removed - assert_eq!(remove_proposal.removed(), LeafNodeIndex::new(1)); - // Store proposal - charlie_group.store_pending_proposal(*staged_proposal.clone()); + // Check that we received the correct proposals + if let ProcessedMessageContent::ProposalMessage(staged_proposal) = + charlie_processed_message.into_content() + { + if let Proposal::Remove(ref remove_proposal) = staged_proposal.proposal() { + // Check that Bob was removed + assert_eq!(remove_proposal.removed(), LeafNodeIndex::new(1)); + // Store proposal + charlie_group.store_pending_proposal(*staged_proposal.clone()); + } else { + unreachable!("Expected a Proposal."); + } + + // Check that Alice removed Bob + assert!(matches!( + staged_proposal.sender(), + Sender::Member(member) if member.u32() == 0 + )); } else { - unreachable!("Expected a Proposal."); + unreachable!("Expected a QueuedProposal."); } - // Check that Alice removed Bob - assert!(matches!( - staged_proposal.sender(), - Sender::Member(member) if member.u32() == 0 - )); - } else { - unreachable!("Expected a QueuedProposal."); - } - - // Charlie commits - let (queued_messages, _welcome, _group_info) = charlie_group - .commit_to_pending_proposals(backend, &charlie_signer) - .await - .expect("Could not commit proposal"); + // Charlie commits + let (queued_messages, _welcome, _group_info) = charlie_group + .commit_to_pending_proposals(backend, &charlie_signer) + .await + .expect("Could not commit proposal"); - // The public group processes - let ppm = public_group - .process_message(backend, into_public_message(queued_messages.clone())) - .await - .unwrap(); - public_group.merge_commit(extract_staged_commit(ppm)); - - // Check that we receive the correct proposal - if let Some(staged_commit) = charlie_group.pending_commit() { - let remove = staged_commit - .remove_proposals() - .next() - .expect("Expected a proposal."); - // Check that Bob was removed - assert_eq!(remove.remove_proposal().removed().u32(), 1); - // Check that Alice removed Bob - assert!(matches!(remove.sender(), Sender::Member(member) if member.u32() == 0)); - } else { - unreachable!("Expected a StagedCommit."); - }; - - charlie_group - .merge_pending_commit(backend) - .await - .expect("error merging pending commit"); + // The public group processes + let ppm = public_group + .process_message(backend, into_public_message(queued_messages.clone())) + .await + .unwrap(); + public_group.merge_commit(extract_staged_commit(ppm)); + + // Check that we receive the correct proposal + if let Some(staged_commit) = charlie_group.pending_commit() { + let remove = staged_commit + .remove_proposals() + .next() + .expect("Expected a proposal."); + // Check that Bob was removed + assert_eq!(remove.remove_proposal().removed().u32(), 1); + // Check that Alice removed Bob + assert!(matches!(remove.sender(), Sender::Member(member) if member.u32() == 0)); + } else { + unreachable!("Expected a StagedCommit."); + }; - // Alice processes - let alice_processed_message = alice_group - .process_message( - backend, - queued_messages - .into_protocol_message() - .expect("Unexpected message type"), - ) - .await - .expect("Could not process messages."); - if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = - alice_processed_message.into_content() - { - alice_group - .merge_staged_commit(backend, *staged_commit) + charlie_group + .merge_pending_commit(backend) .await - .expect("Error merging commit."); - } else { - unreachable!("Expected a StagedCommit."); - } + .expect("error merging pending commit"); + + // Alice processes + let alice_processed_message = alice_group + .process_message( + backend, + queued_messages + .into_protocol_message() + .expect("Unexpected message type"), + ) + .await + .expect("Could not process messages."); + if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = + alice_processed_message.into_content() + { + alice_group + .merge_staged_commit(backend, *staged_commit) + .await + .expect("Error merging commit."); + } else { + unreachable!("Expected a StagedCommit."); + } - // Check that the public group state matches that of all other participants - assert_eq!( - alice_group.export_group_context(), - public_group.group_context() - ); - assert_eq!( - charlie_group.export_group_context(), - public_group.group_context() - ); - assert_eq!( - alice_group.export_ratchet_tree(), - public_group.export_ratchet_tree() - ); - assert_eq!( - charlie_group.export_ratchet_tree(), - public_group.export_ratchet_tree() - ); + // Check that the public group state matches that of all other participants + assert_eq!( + alice_group.export_group_context(), + public_group.group_context() + ); + assert_eq!( + charlie_group.export_group_context(), + public_group.group_context() + ); + assert_eq!( + alice_group.export_ratchet_tree(), + public_group.export_ratchet_tree() + ); + assert_eq!( + charlie_group.export_ratchet_tree(), + public_group.export_ratchet_tree() + ); + }) + .await; } // A helper function diff --git a/openmls/src/tree/tests_and_kats/kats/kat_message_protection.rs b/openmls/src/tree/tests_and_kats/kats/kat_message_protection.rs index 1886e1426..b292d5968 100644 --- a/openmls/src/tree/tests_and_kats/kats/kat_message_protection.rs +++ b/openmls/src/tree/tests_and_kats/kats/kat_message_protection.rs @@ -759,13 +759,16 @@ pub async fn run_test_vector( #[apply(backends)] async fn read_test_vectors_mp(backend: &impl OpenMlsCryptoProvider) { - let _ = pretty_env_logger::try_init(); - log::info!("Reading test vectors ..."); + Box::pin(async { + let _ = pretty_env_logger::try_init(); + log::info!("Reading test vectors ..."); - let tests: Vec = read("test_vectors/message-protection.json"); + let tests: Vec = read("test_vectors/message-protection.json"); - for test_vector in tests.into_iter() { - run_test_vector(test_vector, backend).await.unwrap(); - } - log::info!("Finished test vector verification"); + for test_vector in tests.into_iter() { + run_test_vector(test_vector, backend).await.unwrap(); + } + log::info!("Finished test vector verification"); + }) + .await; } diff --git a/openmls/tests/book_code.rs b/openmls/tests/book_code.rs index 4ee5854c9..41ffa6dd9 100644 --- a/openmls/tests/book_code.rs +++ b/openmls/tests/book_code.rs @@ -82,1248 +82,1253 @@ async fn generate_key_package( /// - Test saving the group state #[apply(ciphersuites_and_backends)] async fn book_operations(ciphersuite: Ciphersuite, backend: &impl OpenMlsCryptoProvider) { - // Generate credentials with keys - let (alice_credential, alice_signature_keys) = - generate_credential("Alice".into(), ciphersuite.signature_algorithm(), backend).await; - - let (bob_credential, bob_signature_keys) = - generate_credential("Bob".into(), ciphersuite.signature_algorithm(), backend).await; + Box::pin(async { + // Generate credentials with keys + let (alice_credential, alice_signature_keys) = + generate_credential("Alice".into(), ciphersuite.signature_algorithm(), backend).await; - let (charlie_credential, charlie_signature_keys) = - generate_credential("Charlie".into(), ciphersuite.signature_algorithm(), backend).await; + let (bob_credential, bob_signature_keys) = + generate_credential("Bob".into(), ciphersuite.signature_algorithm(), backend).await; - let (dave_credential, dave_signature_keys) = - generate_credential("Dave".into(), ciphersuite.signature_algorithm(), backend).await; + let (charlie_credential, charlie_signature_keys) = + generate_credential("Charlie".into(), ciphersuite.signature_algorithm(), backend).await; - // Generate KeyPackages - let bob_key_package = generate_key_package( - ciphersuite, - bob_credential.clone(), - Extensions::default(), - backend, - &bob_signature_keys, - ) - .await; + let (dave_credential, dave_signature_keys) = + generate_credential("Dave".into(), ciphersuite.signature_algorithm(), backend).await; - // Define the MlsGroup configuration - // delivery service credentials - let (ds_credential_with_key, ds_signature_keys) = generate_credential( - "delivery-service".into(), - ciphersuite.signature_algorithm(), - backend, - ) - .await; - - // ANCHOR: mls_group_config_example - let mls_group_config = MlsGroupConfig::builder() - .padding_size(100) - .sender_ratchet_configuration(SenderRatchetConfiguration::new( - 10, // out_of_order_tolerance - 2000, // maximum_forward_distance - )) - .external_senders(vec![ExternalSender::new( - ds_credential_with_key.signature_key, - ds_credential_with_key.credential, - )]) - .crypto_config(CryptoConfig::with_default_version(ciphersuite)) - .use_ratchet_tree_extension(true) - .build(); - // ANCHOR_END: mls_group_config_example - - // ANCHOR: alice_create_group - let mut alice_group = MlsGroup::new( - backend, - &alice_signature_keys, - &mls_group_config, - alice_credential.clone(), - ) - .await - .expect("An unexpected error occurred."); - // ANCHOR_END: alice_create_group - - { - // ANCHOR: alice_create_group_with_group_id - // Some specific group ID generated by someone else. - let group_id = GroupId::from_slice(b"123e4567e89b"); + // Generate KeyPackages + let bob_key_package = generate_key_package( + ciphersuite, + bob_credential.clone(), + Extensions::default(), + backend, + &bob_signature_keys, + ) + .await; - let mut alice_group = MlsGroup::new_with_group_id( + // Define the MlsGroup configuration + // delivery service credentials + let (ds_credential_with_key, ds_signature_keys) = generate_credential( + "delivery-service".into(), + ciphersuite.signature_algorithm(), + backend, + ) + .await; + + // ANCHOR: mls_group_config_example + let mls_group_config = MlsGroupConfig::builder() + .padding_size(100) + .sender_ratchet_configuration(SenderRatchetConfiguration::new( + 10, // out_of_order_tolerance + 2000, // maximum_forward_distance + )) + .external_senders(vec![ExternalSender::new( + ds_credential_with_key.signature_key, + ds_credential_with_key.credential, + )]) + .crypto_config(CryptoConfig::with_default_version(ciphersuite)) + .use_ratchet_tree_extension(true) + .build(); + // ANCHOR_END: mls_group_config_example + + // ANCHOR: alice_create_group + let mut alice_group = MlsGroup::new( backend, &alice_signature_keys, &mls_group_config, - group_id, alice_credential.clone(), ) .await .expect("An unexpected error occurred."); - // ANCHOR_END: alice_create_group_with_group_id + // ANCHOR_END: alice_create_group - // Silence "unused variable" and "does not need to be mutable" warnings. - let _ignore_mut_warning = &mut alice_group; - } + { + // ANCHOR: alice_create_group_with_group_id + // Some specific group ID generated by someone else. + let group_id = GroupId::from_slice(b"123e4567e89b"); - let group_id = alice_group.group_id().clone(); - - // === Alice adds Bob === - // ANCHOR: alice_adds_bob - let (mls_message_out, welcome, group_info) = alice_group - .add_members(backend, &alice_signature_keys, vec![bob_key_package.into()]) - .await - .expect("Could not add members."); - // ANCHOR_END: alice_adds_bob - - // Suppress warning - let _mls_message_out = mls_message_out; - let _group_info = group_info; - - // Check that we received the correct proposals - if let Some(staged_commit) = alice_group.pending_commit() { - let add = staged_commit - .add_proposals() - .next() - .expect("Expected a proposal."); - // Check that Bob was added - assert_eq!( - add.add_proposal().key_package().leaf_node().credential(), - &bob_credential.credential - ); - // Check that Alice added Bob - assert!(matches!( - add.sender(), - Sender::Member(member) if *member == alice_group.own_leaf_index() - )); - } else { - unreachable!("Expected a StagedCommit."); - } - - alice_group - .merge_pending_commit(backend) - .await - .expect("error merging pending commit"); + let mut alice_group = MlsGroup::new_with_group_id( + backend, + &alice_signature_keys, + &mls_group_config, + group_id, + alice_credential.clone(), + ) + .await + .expect("An unexpected error occurred."); + // ANCHOR_END: alice_create_group_with_group_id - // Check that the group now has two members - assert_eq!(alice_group.members().count(), 2); + // Silence "unused variable" and "does not need to be mutable" warnings. + let _ignore_mut_warning = &mut alice_group; + } - // Check that Alice & Bob are the members of the group - let members = alice_group.members().collect::>(); - assert_eq!(members[0].credential.identity(), b"Alice"); - assert_eq!(members[1].credential.identity(), b"Bob"); + let group_id = alice_group.group_id().clone(); - // ANCHOR: bob_joins_with_welcome - let mut bob_group = MlsGroup::new_from_welcome( - backend, - &mls_group_config, - welcome.into_welcome().expect("Unexpected message type."), - None, // We use the ratchet tree extension, so we don't provide a ratchet tree here - ) - .await - .expect("Error joining group from Welcome"); - // ANCHOR_END: bob_joins_with_welcome - - // ANCHOR: alice_exports_group_info - let verifiable_group_info = alice_group - .export_group_info(backend, &alice_signature_keys, true) - .expect("Cannot export group info") - .into_verifiable_group_info() - .expect("Could not get group info"); - // ANCHOR_END: alice_exports_group_info - - // ANCHOR: charlie_joins_external_commit - let (mut dave_group, _out, _group_info) = MlsGroup::join_by_external_commit( - backend, - &dave_signature_keys, - None, - verifiable_group_info, - &mls_group_config, - &[], - dave_credential, - ) - .await - .expect("Error joining from external commit"); - dave_group - .merge_pending_commit(backend) - .await - .expect("Cannot merge commit"); - // ANCHOR_END: charlie_joins_external_commit - - // Make sure that both groups have the same members - assert!(alice_group.members().eq(bob_group.members())); - - // Make sure that both groups have the same epoch authenticator - assert_eq!( - alice_group.epoch_authenticator().as_slice(), - bob_group.epoch_authenticator().as_slice() - ); - - // === Alice sends a message to Bob === - // ANCHOR: create_application_message - let message_alice = b"Hi, I'm Alice!"; - let mls_message_out = alice_group - .create_message(backend, &alice_signature_keys, message_alice) - .await - .expect("Error creating application message."); - // ANCHOR_END: create_application_message + // === Alice adds Bob === + // ANCHOR: alice_adds_bob + let (mls_message_out, welcome, group_info) = alice_group + .add_members(backend, &alice_signature_keys, vec![bob_key_package.into()]) + .await + .expect("Could not add members."); + // ANCHOR_END: alice_adds_bob + + // Suppress warning + let _mls_message_out = mls_message_out; + let _group_info = group_info; + + // Check that we received the correct proposals + if let Some(staged_commit) = alice_group.pending_commit() { + let add = staged_commit + .add_proposals() + .next() + .expect("Expected a proposal."); + // Check that Bob was added + assert_eq!( + add.add_proposal().key_package().leaf_node().credential(), + &bob_credential.credential + ); + // Check that Alice added Bob + assert!(matches!( + add.sender(), + Sender::Member(member) if *member == alice_group.own_leaf_index() + )); + } else { + unreachable!("Expected a StagedCommit."); + } - // Message serialization + alice_group + .merge_pending_commit(backend) + .await + .expect("error merging pending commit"); - let bytes = mls_message_out - .to_bytes() - .expect("Could not serialize message."); + // Check that the group now has two members + assert_eq!(alice_group.members().count(), 2); - // ANCHOR: mls_message_in_from_bytes - let mls_message = - MlsMessageIn::tls_deserialize_exact(bytes).expect("Could not deserialize message."); - // ANCHOR_END: mls_message_in_from_bytes + // Check that Alice & Bob are the members of the group + let members = alice_group.members().collect::>(); + assert_eq!(members[0].credential.identity(), b"Alice"); + assert_eq!(members[1].credential.identity(), b"Bob"); - // ANCHOR: process_message - let protocol_message: ProtocolMessage = mls_message.into(); - let processed_message = bob_group - .process_message(backend, protocol_message) - .await - .expect("Could not process message."); - // ANCHOR_END: process_message - - // Check that we received the correct message - // ANCHOR: inspect_application_message - if let ProcessedMessageContent::ApplicationMessage(application_message) = - processed_message.into_content() - { - // Check the message - assert_eq!(application_message.into_bytes(), b"Hi, I'm Alice!"); - } - // ANCHOR_END: inspect_application_message - else { - unreachable!("Expected an ApplicationMessage."); - } - - // === Bob updates and commits === - // ANCHOR: self_update - let (mls_message_out, welcome_option, _group_info) = bob_group - .self_update(backend, &bob_signature_keys) + // ANCHOR: bob_joins_with_welcome + let mut bob_group = MlsGroup::new_from_welcome( + backend, + &mls_group_config, + welcome.into_welcome().expect("Unexpected message type."), + None, // We use the ratchet tree extension, so we don't provide a ratchet tree here + ) .await - .expect("Could not update own key package."); - // ANCHOR_END: self_update - - let alice_processed_message = alice_group - .process_message( + .expect("Error joining group from Welcome"); + // ANCHOR_END: bob_joins_with_welcome + + // ANCHOR: alice_exports_group_info + let verifiable_group_info = alice_group + .export_group_info(backend, &alice_signature_keys, true) + .expect("Cannot export group info") + .into_verifiable_group_info() + .expect("Could not get group info"); + // ANCHOR_END: alice_exports_group_info + + // ANCHOR: charlie_joins_external_commit + let (mut dave_group, _out, _group_info) = MlsGroup::join_by_external_commit( backend, - mls_message_out - .into_protocol_message() - .expect("Unexpected message type"), + &dave_signature_keys, + None, + verifiable_group_info, + &mls_group_config, + &[], + dave_credential, ) .await - .expect("Could not process message."); + .expect("Error joining from external commit"); + dave_group + .merge_pending_commit(backend) + .await + .expect("Cannot merge commit"); + // ANCHOR_END: charlie_joins_external_commit - // Check that we received the correct message - if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = - alice_processed_message.into_content() - { - // Merge staged Commit - alice_group - .merge_staged_commit(backend, *staged_commit) + // Make sure that both groups have the same members + assert!(alice_group.members().eq(bob_group.members())); + + // Make sure that both groups have the same epoch authenticator + assert_eq!( + alice_group.epoch_authenticator().as_slice(), + bob_group.epoch_authenticator().as_slice() + ); + + // === Alice sends a message to Bob === + // ANCHOR: create_application_message + let message_alice = b"Hi, I'm Alice!"; + let mls_message_out = alice_group + .create_message(backend, &alice_signature_keys, message_alice) .await - .expect("Error merging staged commit."); - } else { - unreachable!("Expected a StagedCommit."); - } + .expect("Error creating application message."); + // ANCHOR_END: create_application_message - bob_group - .merge_pending_commit(backend) - .await - .expect("error merging pending commit"); - - // Check we didn't receive a Welcome message - assert!(welcome_option.is_none()); - - // Check that both groups have the same state - assert_eq!( - alice_group.export_secret(backend, "", &[], 32), - bob_group.export_secret(backend, "", &[], 32) - ); - - // Make sure that both groups have the same public tree - assert_eq!( - alice_group.export_ratchet_tree(), - bob_group.export_ratchet_tree() - ); - - // === Alice updates and commits === - // ANCHOR: propose_self_update - let (mls_message_out, _proposal_ref) = alice_group - .propose_self_update(backend, &alice_signature_keys) - .await - .expect("Could not create update proposal."); - // ANCHOR_END: propose_self_update + // Message serialization - let bob_processed_message = bob_group - .process_message( - backend, - mls_message_out - .into_protocol_message() - .expect("Unexpected message type"), - ) - .await - .expect("Could not process message."); - - // Check that we received the correct proposals - if let ProcessedMessageContent::ProposalMessage(staged_proposal) = - bob_processed_message.into_content() - { - if let Proposal::Update(ref update_proposal) = staged_proposal.proposal() { - // Check that Alice updated - assert_eq!( - update_proposal.leaf_node().credential(), - &alice_credential.credential - ); - // Store proposal - alice_group.store_pending_proposal(*staged_proposal.clone()); - } else { - unreachable!("Expected a Proposal."); + let bytes = mls_message_out + .to_bytes() + .expect("Could not serialize message."); + + // ANCHOR: mls_message_in_from_bytes + let mls_message = + MlsMessageIn::tls_deserialize_exact(bytes).expect("Could not deserialize message."); + // ANCHOR_END: mls_message_in_from_bytes + + // ANCHOR: process_message + let protocol_message: ProtocolMessage = mls_message.into(); + let processed_message = bob_group + .process_message(backend, protocol_message) + .await + .expect("Could not process message."); + // ANCHOR_END: process_message + + // Check that we received the correct message + // ANCHOR: inspect_application_message + if let ProcessedMessageContent::ApplicationMessage(application_message) = + processed_message.into_content() + { + // Check the message + assert_eq!(application_message.into_bytes(), b"Hi, I'm Alice!"); + } + // ANCHOR_END: inspect_application_message + else { + unreachable!("Expected an ApplicationMessage."); } - // Check that Alice sent the proposal - assert!(matches!( - staged_proposal.sender(), - Sender::Member(member) if *member == alice_group.own_leaf_index() - )); - bob_group.store_pending_proposal(*staged_proposal); - } else { - unreachable!("Expected a QueuedProposal."); - } - - // ANCHOR: commit_to_proposals - let (mls_message_out, welcome_option, _group_info) = alice_group - .commit_to_pending_proposals(backend, &alice_signature_keys) - .await - .expect("Could not commit to pending proposals."); - // ANCHOR_END: commit_to_proposals + // === Bob updates and commits === + // ANCHOR: self_update + let (mls_message_out, welcome_option, _group_info) = bob_group + .self_update(backend, &bob_signature_keys) + .await + .expect("Could not update own key package."); + // ANCHOR_END: self_update - // Suppress warning - let _welcome_option = welcome_option; + let alice_processed_message = alice_group + .process_message( + backend, + mls_message_out + .into_protocol_message() + .expect("Unexpected message type"), + ) + .await + .expect("Could not process message."); - let bob_processed_message = bob_group - .process_message( - backend, - mls_message_out - .into_protocol_message() - .expect("Unexpected message type"), - ) - .await - .expect("Could not process message."); + // Check that we received the correct message + if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = + alice_processed_message.into_content() + { + // Merge staged Commit + alice_group + .merge_staged_commit(backend, *staged_commit) + .await + .expect("Error merging staged commit."); + } else { + unreachable!("Expected a StagedCommit."); + } - // Check that we received the correct message - if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = - bob_processed_message.into_content() - { bob_group - .merge_staged_commit(backend, *staged_commit) + .merge_pending_commit(backend) .await - .expect("Error merging staged commit."); - } else { - unreachable!("Expected a StagedCommit."); - } + .expect("error merging pending commit"); - alice_group - .merge_pending_commit(backend) - .await - .expect("error merging pending commit"); - - // Check that both groups have the same state - assert_eq!( - alice_group.export_secret(backend, "", &[], 32), - bob_group.export_secret(backend, "", &[], 32) - ); - - // Make sure that both groups have the same public tree - assert_eq!( - alice_group.export_ratchet_tree(), - bob_group.export_ratchet_tree() - ); - - // === Bob adds Charlie === - let charlie_key_package = generate_key_package( - ciphersuite, - charlie_credential.clone(), - Extensions::default(), - backend, - &charlie_signature_keys, - ) - .await; + // Check we didn't receive a Welcome message + assert!(welcome_option.is_none()); - let (queued_message, welcome, _group_info) = bob_group - .add_members( - backend, - &bob_signature_keys, - vec![charlie_key_package.into()], - ) - .await - .unwrap(); + // Check that both groups have the same state + assert_eq!( + alice_group.export_secret(backend, "", &[], 32), + bob_group.export_secret(backend, "", &[], 32) + ); - let alice_processed_message = alice_group - .process_message( - backend, - queued_message - .into_protocol_message() - .expect("Unexpected message type"), - ) - .await - .expect("Could not process message."); - bob_group - .merge_pending_commit(backend) - .await - .expect("error merging pending commit"); + // Make sure that both groups have the same public tree + assert_eq!( + alice_group.export_ratchet_tree(), + bob_group.export_ratchet_tree() + ); - // Merge Commit - if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = - alice_processed_message.into_content() - { - alice_group - .merge_staged_commit(backend, *staged_commit) + // === Alice updates and commits === + // ANCHOR: propose_self_update + let (mls_message_out, _proposal_ref) = alice_group + .propose_self_update(backend, &alice_signature_keys) .await - .expect("Error merging staged commit."); - } else { - unreachable!("Expected a StagedCommit."); - } + .expect("Could not create update proposal."); + // ANCHOR_END: propose_self_update - let mut charlie_group = MlsGroup::new_from_welcome( - backend, - &mls_group_config, - welcome.into_welcome().expect("Unexpected message type."), - Some(bob_group.export_ratchet_tree().into()), - ) - .await - .expect("Error creating group from Welcome"); - - // Make sure that all groups have the same public tree - assert_eq!( - alice_group.export_ratchet_tree(), - bob_group.export_ratchet_tree(), - ); - assert_eq!( - alice_group.export_ratchet_tree(), - charlie_group.export_ratchet_tree() - ); - - // Check that Alice, Bob & Charlie are the members of the group - let members = alice_group.members().collect::>(); - assert_eq!(members[0].credential.identity(), b"Alice"); - assert_eq!(members[1].credential.identity(), b"Bob"); - assert_eq!(members[2].credential.identity(), b"Charlie"); - assert_eq!(members.len(), 3); - - // === Charlie sends a message to the group === - let message_charlie = b"Hi, I'm Charlie!"; - let queued_message = charlie_group - .create_message(backend, &charlie_signature_keys, message_charlie) - .await - .expect("Error creating application message"); + let bob_processed_message = bob_group + .process_message( + backend, + mls_message_out + .into_protocol_message() + .expect("Unexpected message type"), + ) + .await + .expect("Could not process message."); + + // Check that we received the correct proposals + if let ProcessedMessageContent::ProposalMessage(staged_proposal) = + bob_processed_message.into_content() + { + if let Proposal::Update(ref update_proposal) = staged_proposal.proposal() { + // Check that Alice updated + assert_eq!( + update_proposal.leaf_node().credential(), + &alice_credential.credential + ); + // Store proposal + alice_group.store_pending_proposal(*staged_proposal.clone()); + } else { + unreachable!("Expected a Proposal."); + } - let _alice_processed_message = alice_group - .process_message( - backend, - queued_message - .clone() - .into_protocol_message() - .expect("Unexpected message type"), - ) - .await - .expect("Could not process message."); - let _bob_processed_message = bob_group - .process_message( - backend, - queued_message - .into_protocol_message() - .expect("Unexpected message type"), - ) - .await - .expect("Could not process message."); + // Check that Alice sent the proposal + assert!(matches!( + staged_proposal.sender(), + Sender::Member(member) if *member == alice_group.own_leaf_index() + )); + bob_group.store_pending_proposal(*staged_proposal); + } else { + unreachable!("Expected a QueuedProposal."); + } - // === Charlie updates and commits === - let (queued_message, welcome_option, _group_info) = charlie_group - .self_update(backend, &charlie_signature_keys) - .await - .unwrap(); + // ANCHOR: commit_to_proposals + let (mls_message_out, welcome_option, _group_info) = alice_group + .commit_to_pending_proposals(backend, &alice_signature_keys) + .await + .expect("Could not commit to pending proposals."); + // ANCHOR_END: commit_to_proposals - let alice_processed_message = alice_group - .process_message( - backend, - queued_message - .clone() - .into_protocol_message() - .expect("Unexpected message type"), - ) - .await - .expect("Could not process message."); - let bob_processed_message = bob_group - .process_message( - backend, - queued_message - .into_protocol_message() - .expect("Unexpected message type"), - ) - .await - .expect("Could not process message."); - charlie_group - .merge_pending_commit(backend) - .await - .expect("error merging pending commit"); + // Suppress warning + let _welcome_option = welcome_option; - // Merge Commit - if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = - alice_processed_message.into_content() - { - alice_group - .merge_staged_commit(backend, *staged_commit) + let bob_processed_message = bob_group + .process_message( + backend, + mls_message_out + .into_protocol_message() + .expect("Unexpected message type"), + ) .await - .expect("Error merging staged commit."); - } else { - unreachable!("Expected a StagedCommit."); - } - - // Merge Commit - if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = - bob_processed_message.into_content() - { - bob_group - .merge_staged_commit(backend, *staged_commit) + .expect("Could not process message."); + + // Check that we received the correct message + if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = + bob_processed_message.into_content() + { + bob_group + .merge_staged_commit(backend, *staged_commit) + .await + .expect("Error merging staged commit."); + } else { + unreachable!("Expected a StagedCommit."); + } + + alice_group + .merge_pending_commit(backend) .await - .expect("Error merging staged commit."); - } else { - unreachable!("Expected a StagedCommit."); - } - - // Check we didn't receive a Welcome message - assert!(welcome_option.is_none()); - - // Check that all groups have the same state - assert_eq!( - alice_group.export_secret(backend, "", &[], 32), - bob_group.export_secret(backend, "", &[], 32) - ); - assert_eq!( - alice_group.export_secret(backend, "", &[], 32), - charlie_group.export_secret(backend, "", &[], 32) - ); - - // Make sure that all groups have the same public tree - assert_eq!( - alice_group.export_ratchet_tree(), - bob_group.export_ratchet_tree(), - ); - assert_eq!( - alice_group.export_ratchet_tree(), - charlie_group.export_ratchet_tree() - ); - - // ANCHOR: retrieve_members - let charlie_members = charlie_group.members().collect::>(); - // ANCHOR_END: retrieve_members - - let bob_member = charlie_members - .iter() - .find( - |Member { - index: _, - credential, - .. - }| credential.identity() == b"Bob", - ) - .expect("Couldn't find Bob in the list of group members."); + .expect("error merging pending commit"); - // Make sure that this is Bob's actual KP reference. - assert_eq!( - bob_member.credential.identity(), - bob_group - .own_identity() - .expect("An unexpected error occurred.") - ); - - // === Charlie removes Bob === - // ANCHOR: charlie_removes_bob - let (mls_message_out, welcome_option, _group_info) = charlie_group - .remove_members(backend, &charlie_signature_keys, &[bob_member.index]) - .await - .expect("Could not remove Bob from group."); - // ANCHOR_END: charlie_removes_bob + // Check that both groups have the same state + assert_eq!( + alice_group.export_secret(backend, "", &[], 32), + bob_group.export_secret(backend, "", &[], 32) + ); - // Check that Bob's group is still active - assert!(bob_group.is_active()); + // Make sure that both groups have the same public tree + assert_eq!( + alice_group.export_ratchet_tree(), + bob_group.export_ratchet_tree() + ); - let alice_processed_message = alice_group - .process_message( + // === Bob adds Charlie === + let charlie_key_package = generate_key_package( + ciphersuite, + charlie_credential.clone(), + Extensions::default(), backend, - mls_message_out - .clone() - .into_protocol_message() - .expect("Unexpected message type"), + &charlie_signature_keys, ) - .await - .expect("Could not process message."); + .await; - // Check that alice can use the member list to check if the message is - // actually from Charlie. - let mut alice_members = alice_group.members(); - let sender_leaf_index = match alice_processed_message.sender() { - Sender::Member(index) => index, - _ => panic!("Sender should have been a member"), - }; - let sender_credential = alice_processed_message.credential(); + let (queued_message, welcome, _group_info) = bob_group + .add_members( + backend, + &bob_signature_keys, + vec![charlie_key_package.into()], + ) + .await + .unwrap(); - assert!(alice_members.any(|Member { index, .. }| index == *sender_leaf_index)); - drop(alice_members); + let alice_processed_message = alice_group + .process_message( + backend, + queued_message + .into_protocol_message() + .expect("Unexpected message type"), + ) + .await + .expect("Could not process message."); + bob_group + .merge_pending_commit(backend) + .await + .expect("error merging pending commit"); - assert_eq!(sender_credential, &charlie_credential); + // Merge Commit + if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = + alice_processed_message.into_content() + { + alice_group + .merge_staged_commit(backend, *staged_commit) + .await + .expect("Error merging staged commit."); + } else { + unreachable!("Expected a StagedCommit."); + } - let bob_processed_message = bob_group - .process_message( + let mut charlie_group = MlsGroup::new_from_welcome( backend, - mls_message_out - .into_protocol_message() - .expect("Unexpected message type"), + &mls_group_config, + welcome.into_welcome().expect("Unexpected message type."), + Some(bob_group.export_ratchet_tree().into()), ) .await - .expect("Could not process message."); - let charlies_leaf_index = charlie_group.own_leaf_index(); - charlie_group - .merge_pending_commit(backend) - .await - .expect("error merging pending commit"); - - // Check that we receive the correct proposal for Alice - // ANCHOR: inspect_staged_commit - if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = - alice_processed_message.into_content() - { - // We expect a remove proposal - let remove = staged_commit - .remove_proposals() - .next() - .expect("Expected a proposal."); - // Check that Bob was removed + .expect("Error creating group from Welcome"); + + // Make sure that all groups have the same public tree assert_eq!( - remove.remove_proposal().removed(), - bob_group.own_leaf_index() + alice_group.export_ratchet_tree(), + bob_group.export_ratchet_tree(), + ); + assert_eq!( + alice_group.export_ratchet_tree(), + charlie_group.export_ratchet_tree() ); - // Check that Charlie removed Bob - assert!(matches!( - remove.sender(), - Sender::Member(member) if *member == charlies_leaf_index - )); - // Merge staged commit - alice_group - .merge_staged_commit(backend, *staged_commit) - .await - .expect("Error merging staged commit."); - } - // ANCHOR_END: inspect_staged_commit - else { - unreachable!("Expected a StagedCommit."); - } - - // Check that we receive the correct proposal for Bob - // ANCHOR: remove_operation - // ANCHOR: getting_removed - if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = - bob_processed_message.into_content() - { - let remove_proposal = staged_commit - .remove_proposals() - .next() - .expect("An unexpected error occurred."); - - // We construct a RemoveOperation enum to help us interpret the remove operation - let remove_operation = RemoveOperation::new(remove_proposal, &bob_group) - .expect("An unexpected Error occurred."); - match remove_operation { - RemoveOperation::WeLeft => unreachable!(), - // We expect this variant, since Bob was removed by Charlie - RemoveOperation::WeWereRemovedBy(member) => { - assert!(matches!(member, Sender::Member(member) if member == charlies_leaf_index)); - } - RemoveOperation::TheyLeft(_) => unreachable!(), - RemoveOperation::TheyWereRemovedBy(_) => unreachable!(), - RemoveOperation::WeRemovedThem(_) => unreachable!(), - } + // Check that Alice, Bob & Charlie are the members of the group + let members = alice_group.members().collect::>(); + assert_eq!(members[0].credential.identity(), b"Alice"); + assert_eq!(members[1].credential.identity(), b"Bob"); + assert_eq!(members[2].credential.identity(), b"Charlie"); + assert_eq!(members.len(), 3); + + // === Charlie sends a message to the group === + let message_charlie = b"Hi, I'm Charlie!"; + let queued_message = charlie_group + .create_message(backend, &charlie_signature_keys, message_charlie) + .await + .expect("Error creating application message"); - // Merge staged Commit - bob_group - .merge_staged_commit(backend, *staged_commit) + let _alice_processed_message = alice_group + .process_message( + backend, + queued_message + .clone() + .into_protocol_message() + .expect("Unexpected message type"), + ) .await - .expect("Error merging staged commit."); - } else { - unreachable!("Expected a StagedCommit."); - } - // ANCHOR_END: remove_operation - - // Check we didn't receive a Welcome message - assert!(welcome_option.is_none()); - - // Check that Bob's group is no longer active - assert!(!bob_group.is_active()); - let members = bob_group.members().collect::>(); - assert_eq!(members.len(), 2); - assert_eq!(members[0].credential.identity(), b"Alice"); - assert_eq!(members[1].credential.identity(), b"Charlie"); - // ANCHOR_END: getting_removed - - // Make sure that all groups have the same public tree - assert_eq!( - alice_group.export_ratchet_tree(), - charlie_group.export_ratchet_tree() - ); - - // Make sure the group only contains two members - assert_eq!(alice_group.members().count(), 2); - - // Check that Alice & Charlie are the members of the group - let members = alice_group.members().collect::>(); - assert_eq!(members[0].credential.identity(), b"Alice"); - assert_eq!(members[1].credential.identity(), b"Charlie"); - - // Check that Bob can no longer send messages - assert!(bob_group - .create_message(backend, &bob_signature_keys, b"Should not go through") - .await - .is_err()); + .expect("Could not process message."); + let _bob_processed_message = bob_group + .process_message( + backend, + queued_message + .into_protocol_message() + .expect("Unexpected message type"), + ) + .await + .expect("Could not process message."); - // === Alice removes Charlie and re-adds Bob === + // === Charlie updates and commits === + let (queued_message, welcome_option, _group_info) = charlie_group + .self_update(backend, &charlie_signature_keys) + .await + .unwrap(); - // Create a new KeyPackageBundle for Bob - let bob_key_package = generate_key_package( - ciphersuite, - bob_credential.clone(), - Extensions::default(), - backend, - &bob_signature_keys, - ) - .await; + let alice_processed_message = alice_group + .process_message( + backend, + queued_message + .clone() + .into_protocol_message() + .expect("Unexpected message type"), + ) + .await + .expect("Could not process message."); + let bob_processed_message = bob_group + .process_message( + backend, + queued_message + .into_protocol_message() + .expect("Unexpected message type"), + ) + .await + .expect("Could not process message."); + charlie_group + .merge_pending_commit(backend) + .await + .expect("error merging pending commit"); - // Create RemoveProposal and process it - // ANCHOR: propose_remove - let (mls_message_out, _proposal_ref) = alice_group - .propose_remove_member( - backend, - &alice_signature_keys, - charlie_group.own_leaf_index(), - ) - .await - .expect("Could not create proposal to remove Charlie."); - // ANCHOR_END: propose_remove + // Merge Commit + if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = + alice_processed_message.into_content() + { + alice_group + .merge_staged_commit(backend, *staged_commit) + .await + .expect("Error merging staged commit."); + } else { + unreachable!("Expected a StagedCommit."); + } - let charlie_processed_message = charlie_group - .process_message( - backend, - mls_message_out - .into_protocol_message() - .expect("Unexpected message type"), - ) - .await - .expect("Could not process message."); - - // Check that we received the correct proposals - if let ProcessedMessageContent::ProposalMessage(staged_proposal) = - charlie_processed_message.into_content() - { - if let Proposal::Remove(ref remove_proposal) = staged_proposal.proposal() { - // Check that Charlie was removed - assert_eq!(remove_proposal.removed(), charlie_group.own_leaf_index()); - // Store proposal - charlie_group.store_pending_proposal(*staged_proposal.clone()); + // Merge Commit + if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = + bob_processed_message.into_content() + { + bob_group + .merge_staged_commit(backend, *staged_commit) + .await + .expect("Error merging staged commit."); } else { - unreachable!("Expected a Proposal."); + unreachable!("Expected a StagedCommit."); } - // Check that Alice removed Charlie - assert!(matches!( - staged_proposal.sender(), - Sender::Member(member) if *member == alice_group.own_leaf_index() - )); - } else { - unreachable!("Expected a QueuedProposal."); - } - - // Create AddProposal and remove it - // ANCHOR: rollback_proposal_by_ref - let (_mls_message_out, proposal_ref) = alice_group - .propose_add_member( - backend, - &alice_signature_keys, - bob_key_package.clone().into(), - ) - .await - .expect("Could not create proposal to add Bob"); - alice_group - .remove_pending_proposal(backend.key_store(), &proposal_ref) - .await - .expect("The proposal was not found"); - // ANCHOR_END: rollback_proposal_by_ref + // Check we didn't receive a Welcome message + assert!(welcome_option.is_none()); - // Create AddProposal and process it - // ANCHOR: propose_add - let (mls_message_out, _proposal_ref) = alice_group - .propose_add_member( - backend, - &alice_signature_keys, - bob_key_package.clone().into(), - ) - .await - .expect("Could not create proposal to add Bob"); - // ANCHOR_END: propose_add + // Check that all groups have the same state + assert_eq!( + alice_group.export_secret(backend, "", &[], 32), + bob_group.export_secret(backend, "", &[], 32) + ); + assert_eq!( + alice_group.export_secret(backend, "", &[], 32), + charlie_group.export_secret(backend, "", &[], 32) + ); - let charlie_processed_message = charlie_group - .process_message( - backend, - mls_message_out - .into_protocol_message() - .expect("Unexpected message type"), - ) - .await - .expect("Could not process message."); - - // Check that we received the correct proposals - // ANCHOR: inspect_add_proposal - if let ProcessedMessageContent::ProposalMessage(staged_proposal) = - charlie_processed_message.into_content() - { - // In the case we received an Add Proposal - if let Proposal::Add(add_proposal) = staged_proposal.proposal() { - // Check that Bob was added + // Make sure that all groups have the same public tree + assert_eq!( + alice_group.export_ratchet_tree(), + bob_group.export_ratchet_tree(), + ); + assert_eq!( + alice_group.export_ratchet_tree(), + charlie_group.export_ratchet_tree() + ); + + // ANCHOR: retrieve_members + let charlie_members = charlie_group.members().collect::>(); + // ANCHOR_END: retrieve_members + + let bob_member = charlie_members + .iter() + .find( + |Member { + index: _, + credential, + .. + }| credential.identity() == b"Bob", + ) + .expect("Couldn't find Bob in the list of group members."); + + // Make sure that this is Bob's actual KP reference. + assert_eq!( + bob_member.credential.identity(), + bob_group + .own_identity() + .expect("An unexpected error occurred.") + ); + + // === Charlie removes Bob === + // ANCHOR: charlie_removes_bob + let (mls_message_out, welcome_option, _group_info) = charlie_group + .remove_members(backend, &charlie_signature_keys, &[bob_member.index]) + .await + .expect("Could not remove Bob from group."); + // ANCHOR_END: charlie_removes_bob + + // Check that Bob's group is still active + assert!(bob_group.is_active()); + + let alice_processed_message = alice_group + .process_message( + backend, + mls_message_out + .clone() + .into_protocol_message() + .expect("Unexpected message type"), + ) + .await + .expect("Could not process message."); + + // Check that alice can use the member list to check if the message is + // actually from Charlie. + let mut alice_members = alice_group.members(); + let sender_leaf_index = match alice_processed_message.sender() { + Sender::Member(index) => index, + _ => panic!("Sender should have been a member"), + }; + let sender_credential = alice_processed_message.credential(); + + assert!(alice_members.any(|Member { index, .. }| index == *sender_leaf_index)); + drop(alice_members); + + assert_eq!(sender_credential, &charlie_credential); + + let bob_processed_message = bob_group + .process_message( + backend, + mls_message_out + .into_protocol_message() + .expect("Unexpected message type"), + ) + .await + .expect("Could not process message."); + let charlies_leaf_index = charlie_group.own_leaf_index(); + charlie_group + .merge_pending_commit(backend) + .await + .expect("error merging pending commit"); + + // Check that we receive the correct proposal for Alice + // ANCHOR: inspect_staged_commit + if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = + alice_processed_message.into_content() + { + // We expect a remove proposal + let remove = staged_commit + .remove_proposals() + .next() + .expect("Expected a proposal."); + // Check that Bob was removed assert_eq!( - add_proposal.key_package().leaf_node().credential(), - &bob_credential.credential + remove.remove_proposal().removed(), + bob_group.own_leaf_index() ); + // Check that Charlie removed Bob + assert!(matches!( + remove.sender(), + Sender::Member(member) if *member == charlies_leaf_index + )); + // Merge staged commit + alice_group + .merge_staged_commit(backend, *staged_commit) + .await + .expect("Error merging staged commit."); + } + // ANCHOR_END: inspect_staged_commit + else { + unreachable!("Expected a StagedCommit."); + } + + // Check that we receive the correct proposal for Bob + // ANCHOR: remove_operation + // ANCHOR: getting_removed + if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = + bob_processed_message.into_content() + { + let remove_proposal = staged_commit + .remove_proposals() + .next() + .expect("An unexpected error occurred."); + + // We construct a RemoveOperation enum to help us interpret the remove operation + let remove_operation = RemoveOperation::new(remove_proposal, &bob_group) + .expect("An unexpected Error occurred."); + + match remove_operation { + RemoveOperation::WeLeft => unreachable!(), + // We expect this variant, since Bob was removed by Charlie + RemoveOperation::WeWereRemovedBy(member) => { + assert!( + matches!(member, Sender::Member(member) if member == charlies_leaf_index) + ); + } + RemoveOperation::TheyLeft(_) => unreachable!(), + RemoveOperation::TheyWereRemovedBy(_) => unreachable!(), + RemoveOperation::WeRemovedThem(_) => unreachable!(), + } + + // Merge staged Commit + bob_group + .merge_staged_commit(backend, *staged_commit) + .await + .expect("Error merging staged commit."); } else { - panic!("Expected an AddProposal."); + unreachable!("Expected a StagedCommit."); } + // ANCHOR_END: remove_operation - // Check that Alice added Bob - assert!(matches!( - staged_proposal.sender(), - Sender::Member(member) if *member == alice_group.own_leaf_index() - )); - // Store proposal - charlie_group.store_pending_proposal(*staged_proposal); - } - // ANCHOR_END: inspect_add_proposal - else { - unreachable!("Expected a QueuedProposal."); - } - - // Commit to the proposals and process it - let (queued_message, welcome_option, _group_info) = alice_group - .commit_to_pending_proposals(backend, &alice_signature_keys) - .await - .expect("Could not flush proposals"); + // Check we didn't receive a Welcome message + assert!(welcome_option.is_none()); - let charlie_processed_message = charlie_group - .process_message( + // Check that Bob's group is no longer active + assert!(!bob_group.is_active()); + let members = bob_group.members().collect::>(); + assert_eq!(members.len(), 2); + assert_eq!(members[0].credential.identity(), b"Alice"); + assert_eq!(members[1].credential.identity(), b"Charlie"); + // ANCHOR_END: getting_removed + + // Make sure that all groups have the same public tree + assert_eq!( + alice_group.export_ratchet_tree(), + charlie_group.export_ratchet_tree() + ); + + // Make sure the group only contains two members + assert_eq!(alice_group.members().count(), 2); + + // Check that Alice & Charlie are the members of the group + let members = alice_group.members().collect::>(); + assert_eq!(members[0].credential.identity(), b"Alice"); + assert_eq!(members[1].credential.identity(), b"Charlie"); + + // Check that Bob can no longer send messages + assert!(bob_group + .create_message(backend, &bob_signature_keys, b"Should not go through") + .await + .is_err()); + + // === Alice removes Charlie and re-adds Bob === + + // Create a new KeyPackageBundle for Bob + let bob_key_package = generate_key_package( + ciphersuite, + bob_credential.clone(), + Extensions::default(), backend, - queued_message - .into_protocol_message() - .expect("Unexpected message type"), + &bob_signature_keys, ) - .await - .expect("Could not process message."); + .await; - // Merge Commit - alice_group - .merge_pending_commit(backend) - .await - .expect("error merging pending commit"); + // Create RemoveProposal and process it + // ANCHOR: propose_remove + let (mls_message_out, _proposal_ref) = alice_group + .propose_remove_member( + backend, + &alice_signature_keys, + charlie_group.own_leaf_index(), + ) + .await + .expect("Could not create proposal to remove Charlie."); + // ANCHOR_END: propose_remove - // Merge Commit - if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = - charlie_processed_message.into_content() - { - charlie_group - .merge_staged_commit(backend, *staged_commit) + let charlie_processed_message = charlie_group + .process_message( + backend, + mls_message_out + .into_protocol_message() + .expect("Unexpected message type"), + ) .await - .expect("Error merging staged commit."); - } else { - unreachable!("Expected a StagedCommit."); - } + .expect("Could not process message."); + + // Check that we received the correct proposals + if let ProcessedMessageContent::ProposalMessage(staged_proposal) = + charlie_processed_message.into_content() + { + if let Proposal::Remove(ref remove_proposal) = staged_proposal.proposal() { + // Check that Charlie was removed + assert_eq!(remove_proposal.removed(), charlie_group.own_leaf_index()); + // Store proposal + charlie_group.store_pending_proposal(*staged_proposal.clone()); + } else { + unreachable!("Expected a Proposal."); + } + + // Check that Alice removed Charlie + assert!(matches!( + staged_proposal.sender(), + Sender::Member(member) if *member == alice_group.own_leaf_index() + )); + } else { + unreachable!("Expected a QueuedProposal."); + } - // Make sure the group contains two members - assert_eq!(alice_group.members().count(), 2); + // Create AddProposal and remove it + // ANCHOR: rollback_proposal_by_ref + let (_mls_message_out, proposal_ref) = alice_group + .propose_add_member( + backend, + &alice_signature_keys, + bob_key_package.clone().into(), + ) + .await + .expect("Could not create proposal to add Bob"); + alice_group + .remove_pending_proposal(backend.key_store(), &proposal_ref) + .await + .expect("The proposal was not found"); + // ANCHOR_END: rollback_proposal_by_ref - // Check that Alice & Bob are the members of the group - let members = alice_group.members().collect::>(); - assert_eq!(members[0].credential.identity(), b"Alice"); - assert_eq!(members[1].credential.identity(), b"Bob"); + // Create AddProposal and process it + // ANCHOR: propose_add + let (mls_message_out, _proposal_ref) = alice_group + .propose_add_member( + backend, + &alice_signature_keys, + bob_key_package.clone().into(), + ) + .await + .expect("Could not create proposal to add Bob"); + // ANCHOR_END: propose_add - // Bob creates a new group - let mut bob_group = MlsGroup::new_from_welcome( - backend, - &mls_group_config, - welcome_option - .expect("Welcome was not returned") - .into_welcome() - .expect("Unexpected message type."), - Some(alice_group.export_ratchet_tree().into()), - ) - .await - .expect("Error creating group from Welcome"); + let charlie_processed_message = charlie_group + .process_message( + backend, + mls_message_out + .into_protocol_message() + .expect("Unexpected message type"), + ) + .await + .expect("Could not process message."); + + // Check that we received the correct proposals + // ANCHOR: inspect_add_proposal + if let ProcessedMessageContent::ProposalMessage(staged_proposal) = + charlie_processed_message.into_content() + { + // In the case we received an Add Proposal + if let Proposal::Add(add_proposal) = staged_proposal.proposal() { + // Check that Bob was added + assert_eq!( + add_proposal.key_package().leaf_node().credential(), + &bob_credential.credential + ); + } else { + panic!("Expected an AddProposal."); + } + + // Check that Alice added Bob + assert!(matches!( + staged_proposal.sender(), + Sender::Member(member) if *member == alice_group.own_leaf_index() + )); + // Store proposal + charlie_group.store_pending_proposal(*staged_proposal); + } + // ANCHOR_END: inspect_add_proposal + else { + unreachable!("Expected a QueuedProposal."); + } - // Make sure the group contains two members - assert_eq!(alice_group.members().count(), 2); + // Commit to the proposals and process it + let (queued_message, welcome_option, _group_info) = alice_group + .commit_to_pending_proposals(backend, &alice_signature_keys) + .await + .expect("Could not flush proposals"); - // Check that Alice & Bob are the members of the group - let members = alice_group.members().collect::>(); - assert_eq!(members[0].credential.identity(), b"Alice"); - assert_eq!(members[1].credential.identity(), b"Bob"); + let charlie_processed_message = charlie_group + .process_message( + backend, + queued_message + .into_protocol_message() + .expect("Unexpected message type"), + ) + .await + .expect("Could not process message."); - // Make sure the group contains two members - assert_eq!(bob_group.members().count(), 2); + // Merge Commit + alice_group + .merge_pending_commit(backend) + .await + .expect("error merging pending commit"); + + // Merge Commit + if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = + charlie_processed_message.into_content() + { + charlie_group + .merge_staged_commit(backend, *staged_commit) + .await + .expect("Error merging staged commit."); + } else { + unreachable!("Expected a StagedCommit."); + } - // Check that Alice & Bob are the members of the group - let members = bob_group.members().collect::>(); - assert_eq!(members[0].credential.identity(), b"Alice"); - assert_eq!(members[1].credential.identity(), b"Bob"); + // Make sure the group contains two members + assert_eq!(alice_group.members().count(), 2); - // === Alice sends a message to the group === - let message_alice = b"Hi, I'm Alice!"; - let queued_message = alice_group - .create_message(backend, &alice_signature_keys, message_alice) - .await - .expect("Error creating application message"); + // Check that Alice & Bob are the members of the group + let members = alice_group.members().collect::>(); + assert_eq!(members[0].credential.identity(), b"Alice"); + assert_eq!(members[1].credential.identity(), b"Bob"); - let bob_processed_message = bob_group - .process_message( + // Bob creates a new group + let mut bob_group = MlsGroup::new_from_welcome( backend, - queued_message - .into_protocol_message() - .expect("Unexpected message type"), + &mls_group_config, + welcome_option + .expect("Welcome was not returned") + .into_welcome() + .expect("Unexpected message type."), + Some(alice_group.export_ratchet_tree().into()), ) .await - .expect("Could not process message."); + .expect("Error creating group from Welcome"); - // Get sender information - // As provided by the processed message - let sender_cred_from_msg = bob_processed_message.credential().clone(); + // Make sure the group contains two members + assert_eq!(alice_group.members().count(), 2); - // As provided by looking up the sender manually via the `member()` function - // ANCHOR: member_lookup - let sender_cred_from_group = - if let Sender::Member(sender_index) = bob_processed_message.sender() { - bob_group - .member(*sender_index) - .expect("Could not find sender in group.") - .clone() - } else { - unreachable!("Expected sender type to be `Member`.") - }; - // ANCHOR_END: member_lookup - - // Check that we received the correct message - if let ProcessedMessageContent::ApplicationMessage(application_message) = - bob_processed_message.into_content() - { - // Check the message - assert_eq!(application_message.into_bytes(), message_alice); - // Check that Alice sent the message - assert_eq!(sender_cred_from_msg.credential, sender_cred_from_group); - assert_eq!( - &sender_cred_from_msg.credential, - alice_group.credential().expect("Expected a credential.") - ); - } else { - unreachable!("Expected an ApplicationMessage."); - } + // Check that Alice & Bob are the members of the group + let members = alice_group.members().collect::>(); + assert_eq!(members[0].credential.identity(), b"Alice"); + assert_eq!(members[1].credential.identity(), b"Bob"); - // === Bob leaves the group === + // Make sure the group contains two members + assert_eq!(bob_group.members().count(), 2); - // ANCHOR: leaving - let queued_message = bob_group - .leave_group(backend, &bob_signature_keys) - .await - .expect("Could not leave group"); - // ANCHOR_END: leaving + // Check that Alice & Bob are the members of the group + let members = bob_group.members().collect::>(); + assert_eq!(members[0].credential.identity(), b"Alice"); + assert_eq!(members[1].credential.identity(), b"Bob"); - let alice_processed_message = alice_group - .process_message( - backend, - queued_message - .into_protocol_message() - .expect("Unexpected message type"), - ) - .await - .expect("Could not process message."); + // === Alice sends a message to the group === + let message_alice = b"Hi, I'm Alice!"; + let queued_message = alice_group + .create_message(backend, &alice_signature_keys, message_alice) + .await + .expect("Error creating application message"); - // Store proposal - if let ProcessedMessageContent::ProposalMessage(staged_proposal) = - alice_processed_message.into_content() - { - // Store proposal - alice_group.store_pending_proposal(*staged_proposal); - } else { - unreachable!("Expected a QueuedProposal."); - } + let bob_processed_message = bob_group + .process_message( + backend, + queued_message + .into_protocol_message() + .expect("Unexpected message type"), + ) + .await + .expect("Could not process message."); + + // Get sender information + // As provided by the processed message + let sender_cred_from_msg = bob_processed_message.credential().clone(); + + // As provided by looking up the sender manually via the `member()` function + // ANCHOR: member_lookup + let sender_cred_from_group = + if let Sender::Member(sender_index) = bob_processed_message.sender() { + bob_group + .member(*sender_index) + .expect("Could not find sender in group.") + .clone() + } else { + unreachable!("Expected sender type to be `Member`.") + }; + // ANCHOR_END: member_lookup + + // Check that we received the correct message + if let ProcessedMessageContent::ApplicationMessage(application_message) = + bob_processed_message.into_content() + { + // Check the message + assert_eq!(application_message.into_bytes(), message_alice); + // Check that Alice sent the message + assert_eq!(sender_cred_from_msg.credential, sender_cred_from_group); + assert_eq!( + &sender_cred_from_msg.credential, + alice_group.credential().expect("Expected a credential.") + ); + } else { + unreachable!("Expected an ApplicationMessage."); + } - // Should fail because you cannot remove yourself from a group - assert!(matches!( - bob_group - .commit_to_pending_proposals(backend, &bob_signature_keys) - .await, - Err(CommitToPendingProposalsError::CreateCommitError( - CreateCommitError::CannotRemoveSelf - )) - )); + // === Bob leaves the group === - let (queued_message, _welcome_option, _group_info) = alice_group - .commit_to_pending_proposals(backend, &alice_signature_keys) - .await - .expect("Could not commit to proposals."); - - // Check that Bob's group is still active - assert!(bob_group.is_active()); - - // Check that we received the correct proposals - if let Some(staged_commit) = alice_group.pending_commit() { - let remove = staged_commit - .remove_proposals() - .next() - .expect("Expected a proposal."); - // Check that Bob was removed - assert_eq!( - remove.remove_proposal().removed(), - bob_group.own_leaf_index() - ); - // Check that Bob removed himself - assert!(matches!( - remove.sender(), - Sender::Member(member) if *member == bob_group.own_leaf_index() - )); - // Merge staged Commit - } else { - unreachable!("Expected a StagedCommit."); - } + // ANCHOR: leaving + let queued_message = bob_group + .leave_group(backend, &bob_signature_keys) + .await + .expect("Could not leave group"); + // ANCHOR_END: leaving - alice_group - .merge_pending_commit(backend) - .await - .expect("Could not merge Commit."); + let alice_processed_message = alice_group + .process_message( + backend, + queued_message + .into_protocol_message() + .expect("Unexpected message type"), + ) + .await + .expect("Could not process message."); - let bob_processed_message = bob_group - .process_message( - backend, - queued_message - .into_protocol_message() - .expect("Unexpected message type"), - ) - .await - .expect("Could not process message."); - - // Check that we received the correct proposals - if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = - bob_processed_message.into_content() - { - let remove = staged_commit - .remove_proposals() - .next() - .expect("Expected a proposal."); - // Check that Bob was removed - assert_eq!( - remove.remove_proposal().removed(), - bob_group.own_leaf_index() - ); - // Check that Bob removed himself + // Store proposal + if let ProcessedMessageContent::ProposalMessage(staged_proposal) = + alice_processed_message.into_content() + { + // Store proposal + alice_group.store_pending_proposal(*staged_proposal); + } else { + unreachable!("Expected a QueuedProposal."); + } + + // Should fail because you cannot remove yourself from a group assert!(matches!( - remove.sender(), - Sender::Member(member) if *member == bob_group.own_leaf_index() + bob_group + .commit_to_pending_proposals(backend, &bob_signature_keys) + .await, + Err(CommitToPendingProposalsError::CreateCommitError( + CreateCommitError::CannotRemoveSelf + )) )); - assert!(staged_commit.self_removed()); - // Merge staged Commit - bob_group - .merge_staged_commit(backend, *staged_commit) + + let (queued_message, _welcome_option, _group_info) = alice_group + .commit_to_pending_proposals(backend, &alice_signature_keys) .await - .expect("Error merging staged commit."); - } else { - unreachable!("Expected a StagedCommit."); - } + .expect("Could not commit to proposals."); + + // Check that Bob's group is still active + assert!(bob_group.is_active()); + + // Check that we received the correct proposals + if let Some(staged_commit) = alice_group.pending_commit() { + let remove = staged_commit + .remove_proposals() + .next() + .expect("Expected a proposal."); + // Check that Bob was removed + assert_eq!( + remove.remove_proposal().removed(), + bob_group.own_leaf_index() + ); + // Check that Bob removed himself + assert!(matches!( + remove.sender(), + Sender::Member(member) if *member == bob_group.own_leaf_index() + )); + // Merge staged Commit + } else { + unreachable!("Expected a StagedCommit."); + } - // Check that Bob's group is no longer active - assert!(!bob_group.is_active()); + alice_group + .merge_pending_commit(backend) + .await + .expect("Could not merge Commit."); - // Make sure the group contains one member - assert_eq!(alice_group.members().count(), 1); + let bob_processed_message = bob_group + .process_message( + backend, + queued_message + .into_protocol_message() + .expect("Unexpected message type"), + ) + .await + .expect("Could not process message."); + + // Check that we received the correct proposals + if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = + bob_processed_message.into_content() + { + let remove = staged_commit + .remove_proposals() + .next() + .expect("Expected a proposal."); + // Check that Bob was removed + assert_eq!( + remove.remove_proposal().removed(), + bob_group.own_leaf_index() + ); + // Check that Bob removed himself + assert!(matches!( + remove.sender(), + Sender::Member(member) if *member == bob_group.own_leaf_index() + )); + assert!(staged_commit.self_removed()); + // Merge staged Commit + bob_group + .merge_staged_commit(backend, *staged_commit) + .await + .expect("Error merging staged commit."); + } else { + unreachable!("Expected a StagedCommit."); + } - // Check that Alice is the only member of the group - let members = alice_group.members().collect::>(); - assert_eq!(members[0].credential.identity(), b"Alice"); + // Check that Bob's group is no longer active + assert!(!bob_group.is_active()); - // === Re-Add Bob with external Add proposal === + // Make sure the group contains one member + assert_eq!(alice_group.members().count(), 1); - // Create a new KeyPackageBundle for Bob - let bob_key_package = generate_key_package( - ciphersuite, - bob_credential.clone(), - Extensions::default(), - backend, - &bob_signature_keys, - ) - .await; + // Check that Alice is the only member of the group + let members = alice_group.members().collect::>(); + assert_eq!(members[0].credential.identity(), b"Alice"); - // ANCHOR: external_join_proposal - let proposal = JoinProposal::new( - bob_key_package, - alice_group.group_id().clone(), - alice_group.epoch(), - &bob_signature_keys, - ) - .expect("Could not create external Add proposal"); - // ANCHOR_END: external_join_proposal + // === Re-Add Bob with external Add proposal === - // ANCHOR: decrypt_external_join_proposal - let alice_processed_message = alice_group - .process_message( + // Create a new KeyPackageBundle for Bob + let bob_key_package = generate_key_package( + ciphersuite, + bob_credential.clone(), + Extensions::default(), backend, - proposal - .into_protocol_message() - .expect("Unexpected message type."), + &bob_signature_keys, ) - .await - .expect("Could not process message."); - match alice_processed_message.into_content() { - ProcessedMessageContent::ExternalJoinProposalMessage(proposal) => { - alice_group.store_pending_proposal(*proposal); - let (_commit, welcome, _group_info) = alice_group - .commit_to_pending_proposals(backend, &alice_signature_keys) - .await - .expect("Could not commit"); - assert_eq!(alice_group.members().count(), 1); - alice_group - .merge_pending_commit(backend) - .await - .expect("Could not merge commit"); - assert_eq!(alice_group.members().count(), 2); + .await; + + // ANCHOR: external_join_proposal + let proposal = JoinProposal::new( + bob_key_package, + alice_group.group_id().clone(), + alice_group.epoch(), + &bob_signature_keys, + ) + .expect("Could not create external Add proposal"); + // ANCHOR_END: external_join_proposal - let bob_group = MlsGroup::new_from_welcome( + // ANCHOR: decrypt_external_join_proposal + let alice_processed_message = alice_group + .process_message( backend, - &mls_group_config, - welcome - .unwrap() - .into_welcome() + proposal + .into_protocol_message() .expect("Unexpected message type."), - None, ) .await - .expect("Bob could not join the group"); - assert_eq!(bob_group.members().count(), 2); + .expect("Could not process message."); + match alice_processed_message.into_content() { + ProcessedMessageContent::ExternalJoinProposalMessage(proposal) => { + alice_group.store_pending_proposal(*proposal); + let (_commit, welcome, _group_info) = alice_group + .commit_to_pending_proposals(backend, &alice_signature_keys) + .await + .expect("Could not commit"); + assert_eq!(alice_group.members().count(), 1); + alice_group + .merge_pending_commit(backend) + .await + .expect("Could not merge commit"); + assert_eq!(alice_group.members().count(), 2); + + let bob_group = MlsGroup::new_from_welcome( + backend, + &mls_group_config, + welcome + .unwrap() + .into_welcome() + .expect("Unexpected message type."), + None, + ) + .await + .expect("Bob could not join the group"); + assert_eq!(bob_group.members().count(), 2); + } + _ => unreachable!(), } - _ => unreachable!(), - } - // ANCHOR_END: decrypt_external_join_proposal - - // get Bob's index - let bob_index = alice_group - .members() - .find_map(|member| { - if member.credential.identity() == b"Bob" { - Some(member.index) - } else { - None + // ANCHOR_END: decrypt_external_join_proposal + + // get Bob's index + let bob_index = alice_group + .members() + .find_map(|member| { + if member.credential.identity() == b"Bob" { + Some(member.index) + } else { + None + } + }) + .unwrap(); + + // ANCHOR: external_remove_proposal + let proposal = ExternalProposal::new_remove( + bob_index, + alice_group.group_id().clone(), + alice_group.epoch(), + &ds_signature_keys, + SenderExtensionIndex::new(0), + ) + .expect("Could not create external Remove proposal"); + // ANCHOR_END: external_remove_proposal + + // ANCHOR: decrypt_external_join_proposal + let alice_processed_message = alice_group + .process_message( + backend, + proposal + .into_protocol_message() + .expect("Unexpected message type."), + ) + .await + .expect("Could not process message."); + match alice_processed_message.into_content() { + ProcessedMessageContent::ProposalMessage(proposal) => { + alice_group.store_pending_proposal(*proposal); + assert_eq!(alice_group.members().count(), 2); + alice_group + .commit_to_pending_proposals(backend, &alice_signature_keys) + .await + .expect("Could not commit"); + alice_group + .merge_pending_commit(backend) + .await + .expect("Could not merge commit"); + assert_eq!(alice_group.members().count(), 1); } - }) - .unwrap(); - - // ANCHOR: external_remove_proposal - let proposal = ExternalProposal::new_remove( - bob_index, - alice_group.group_id().clone(), - alice_group.epoch(), - &ds_signature_keys, - SenderExtensionIndex::new(0), - ) - .expect("Could not create external Remove proposal"); - // ANCHOR_END: external_remove_proposal + _ => unreachable!(), + } + // ANCHOR_END: decrypt_external_join_proposal - // ANCHOR: decrypt_external_join_proposal - let alice_processed_message = alice_group - .process_message( + // === Save the group state === + + // Create a new KeyPackageBundle for Bob + let bob_key_package = generate_key_package( + ciphersuite, + bob_credential, + Extensions::default(), backend, - proposal - .into_protocol_message() - .expect("Unexpected message type."), + &bob_signature_keys, ) - .await - .expect("Could not process message."); - match alice_processed_message.into_content() { - ProcessedMessageContent::ProposalMessage(proposal) => { - alice_group.store_pending_proposal(*proposal); - assert_eq!(alice_group.members().count(), 2); - alice_group - .commit_to_pending_proposals(backend, &alice_signature_keys) - .await - .expect("Could not commit"); - alice_group - .merge_pending_commit(backend) - .await - .expect("Could not merge commit"); - assert_eq!(alice_group.members().count(), 1); - } - _ => unreachable!(), - } - // ANCHOR_END: decrypt_external_join_proposal + .await; - // === Save the group state === - - // Create a new KeyPackageBundle for Bob - let bob_key_package = generate_key_package( - ciphersuite, - bob_credential, - Extensions::default(), - backend, - &bob_signature_keys, - ) - .await; + // Add Bob to the group + let (_queued_message, welcome, _group_info) = alice_group + .add_members(backend, &alice_signature_keys, vec![bob_key_package.into()]) + .await + .expect("Could not add Bob"); - // Add Bob to the group - let (_queued_message, welcome, _group_info) = alice_group - .add_members(backend, &alice_signature_keys, vec![bob_key_package.into()]) - .await - .expect("Could not add Bob"); + // Merge Commit + alice_group + .merge_pending_commit(backend) + .await + .expect("error merging pending commit"); - // Merge Commit - alice_group - .merge_pending_commit(backend) + let mut bob_group = MlsGroup::new_from_welcome( + backend, + &mls_group_config, + welcome.into_welcome().expect("Unexpected message type."), + Some(alice_group.export_ratchet_tree().into()), + ) .await - .expect("error merging pending commit"); - - let mut bob_group = MlsGroup::new_from_welcome( - backend, - &mls_group_config, - welcome.into_welcome().expect("Unexpected message type."), - Some(alice_group.export_ratchet_tree().into()), - ) - .await - .expect("Could not create group from Welcome"); + .expect("Could not create group from Welcome"); - assert_eq!( - alice_group.export_secret(backend, "before load", &[], 32), - bob_group.export_secret(backend, "before load", &[], 32) - ); + assert_eq!( + alice_group.export_secret(backend, "before load", &[], 32), + bob_group.export_secret(backend, "before load", &[], 32) + ); - // Check that the state flag gets reset when saving - assert_eq!(bob_group.state_changed(), InnerState::Changed); - //save(&mut bob_group); + // Check that the state flag gets reset when saving + assert_eq!(bob_group.state_changed(), InnerState::Changed); + //save(&mut bob_group); - bob_group - .save(backend) - .await - .expect("Could not write group state to file"); + bob_group + .save(backend) + .await + .expect("Could not write group state to file"); - // Check that the state flag gets reset when saving - assert_eq!(bob_group.state_changed(), InnerState::Persisted); + // Check that the state flag gets reset when saving + assert_eq!(bob_group.state_changed(), InnerState::Persisted); - let bob_group = MlsGroup::load(&group_id, backend) - .await - .expect("Could not load group from file"); + let bob_group = MlsGroup::load(&group_id, backend) + .await + .expect("Could not load group from file"); - // Make sure the state is still the same - assert_eq!( - alice_group.export_secret(backend, "after load", &[], 32), - bob_group.export_secret(backend, "after load", &[], 32) - ); + // Make sure the state is still the same + assert_eq!( + alice_group.export_secret(backend, "after load", &[], 32), + bob_group.export_secret(backend, "after load", &[], 32) + ); + }) + .await; } #[apply(ciphersuites_and_backends)] diff --git a/openmls/tests/test_mls_group.rs b/openmls/tests/test_mls_group.rs index 50ea3136a..b27dffde3 100644 --- a/openmls/tests/test_mls_group.rs +++ b/openmls/tests/test_mls_group.rs @@ -43,6 +43,7 @@ async fn generate_key_package( /// - Test saving the group state #[apply(ciphersuites_and_backends)] async fn mls_group_operations(ciphersuite: Ciphersuite, backend: &impl OpenMlsCryptoProvider) { + Box::pin(async { for wire_format_policy in WIRE_FORMAT_POLICIES.iter() { let group_id = GroupId::from_slice(b"Test Group"); @@ -978,6 +979,8 @@ async fn mls_group_operations(ciphersuite: Ciphersuite, backend: &impl OpenMlsCr bob_group.export_secret(backend, "after load", &[], 32) ); } +}) +.await; } #[apply(ciphersuites_and_backends)]