1use std::collections::BTreeSet;
15use std::str;
16
17use nostr::prelude::*;
18use nrc_mls_storage::groups::types as group_types;
19use nrc_mls_storage::messages::types as message_types;
20use nrc_mls_storage::NostrMlsStorageProvider;
21use openmls::group::GroupId;
22use openmls::prelude::*;
23use openmls_basic_credential::SignatureKeyPair;
24use tls_codec::Serialize as TlsSerialize;
25
26use super::extension::NostrGroupDataExtension;
27use super::NostrMls;
28use crate::error::Error;
29
30#[derive(Debug)]
32pub struct GroupResult {
33 pub group: group_types::Group,
35 pub welcome_rumors: Vec<UnsignedEvent>,
37}
38
39#[derive(Debug)]
41pub struct UpdateGroupResult {
42 pub evolution_event: Event,
44 pub welcome_rumors: Option<Vec<UnsignedEvent>>,
46}
47
48#[derive(Debug, Clone)]
50pub struct NostrGroupConfigData {
51 pub name: String,
53 pub description: String,
55 pub image_url: Option<String>,
57 pub image_key: Option<Vec<u8>>,
59 pub image_nonce: Option<Vec<u8>>,
61 pub relays: Vec<RelayUrl>,
63 pub admins: Vec<PublicKey>,
65}
66
67#[derive(Debug, Clone, Default)]
69pub struct NostrGroupDataUpdate {
70 pub name: Option<String>,
72 pub description: Option<String>,
74 pub image_url: Option<Option<String>>,
76 pub image_key: Option<Option<Vec<u8>>>,
78 pub image_nonce: Option<Option<Vec<u8>>>,
80 pub relays: Option<Vec<RelayUrl>>,
82 pub admins: Option<Vec<PublicKey>>,
84}
85
86impl NostrGroupConfigData {
87 pub fn new(
89 name: String,
90 description: String,
91 image_url: Option<String>,
92 image_key: Option<Vec<u8>>,
93 image_nonce: Option<Vec<u8>>,
94 relays: Vec<RelayUrl>,
95 admins: Vec<PublicKey>,
96 ) -> Self {
97 Self {
98 name,
99 description,
100 image_url,
101 image_key,
102 image_nonce,
103 relays,
104 admins,
105 }
106 }
107}
108
109impl NostrGroupDataUpdate {
110 pub fn new() -> Self {
112 Self::default()
113 }
114
115 pub fn name<T>(mut self, name: T) -> Self
117 where
118 T: Into<String>,
119 {
120 self.name = Some(name.into());
121 self
122 }
123
124 pub fn description<T>(mut self, description: T) -> Self
126 where
127 T: Into<String>,
128 {
129 self.description = Some(description.into());
130 self
131 }
132
133 pub fn image_url<T>(mut self, image_url: Option<T>) -> Self
135 where
136 T: Into<String>,
137 {
138 self.image_url = Some(image_url.map(Into::into));
139 self
140 }
141
142 pub fn image_key(mut self, image_key: Option<Vec<u8>>) -> Self {
144 self.image_key = Some(image_key);
145 self
146 }
147
148 pub fn image_nonce(mut self, image_nonce: Option<Vec<u8>>) -> Self {
150 self.image_nonce = Some(image_nonce);
151 self
152 }
153
154 pub fn relays(mut self, relays: Vec<RelayUrl>) -> Self {
156 self.relays = Some(relays);
157 self
158 }
159
160 pub fn admins(mut self, admins: Vec<PublicKey>) -> Self {
162 self.admins = Some(admins);
163 self
164 }
165}
166
167impl<Storage> NostrMls<Storage>
168where
169 Storage: NostrMlsStorageProvider,
170{
171 pub(crate) fn get_own_pubkey(&self, group: &MlsGroup) -> Result<PublicKey, Error> {
182 let own_leaf = group.own_leaf().ok_or(Error::OwnLeafNotFound)?;
183 let credentials: BasicCredential =
184 BasicCredential::try_from(own_leaf.credential().clone())?;
185 let hex_bytes: &[u8] = credentials.identity();
186 let hex_str: &str = str::from_utf8(hex_bytes)?;
187 let public_key = PublicKey::from_hex(hex_str)?;
188 Ok(public_key)
189 }
190
191 pub(crate) fn is_leaf_node_admin(
204 &self,
205 group_id: &GroupId,
206 leaf_node: &LeafNode,
207 ) -> Result<bool, Error> {
208 let pubkey = self.pubkey_for_leaf_node(leaf_node)?;
209 let stored_group = self.get_group(group_id)?.ok_or(Error::GroupNotFound)?;
210 Ok(stored_group.admin_pubkeys.contains(&pubkey))
211 }
212
213 pub(crate) fn is_member_admin(
226 &self,
227 group_id: &GroupId,
228 member: &Member,
229 ) -> Result<bool, Error> {
230 let pubkey = self.pubkey_for_member(member)?;
231 let stored_group = self.get_group(group_id)?.ok_or(Error::GroupNotFound)?;
232 Ok(stored_group.admin_pubkeys.contains(&pubkey))
233 }
234
235 pub(crate) fn pubkey_for_leaf_node(&self, leaf_node: &LeafNode) -> Result<PublicKey, Error> {
246 let credentials: BasicCredential =
247 BasicCredential::try_from(leaf_node.credential().clone())?;
248 let hex_bytes: &[u8] = credentials.identity();
249 let hex_str: &str = str::from_utf8(hex_bytes)?;
250 let public_key = PublicKey::from_hex(hex_str)?;
251 Ok(public_key)
252 }
253
254 pub(crate) fn pubkey_for_member(&self, member: &Member) -> Result<PublicKey, Error> {
265 let credentials: BasicCredential = BasicCredential::try_from(member.credential.clone())?;
266 let hex_bytes: &[u8] = credentials.identity();
267 let hex_str: &str = str::from_utf8(hex_bytes)?;
268 let public_key = PublicKey::from_hex(hex_str)?;
269 Ok(public_key)
270 }
271
272 pub(crate) fn load_mls_signer(&self, group: &MlsGroup) -> Result<SignatureKeyPair, Error> {
283 let own_leaf: &LeafNode = group.own_leaf().ok_or(Error::OwnLeafNotFound)?;
284 let public_key: &[u8] = own_leaf.signature_key().as_slice();
285
286 SignatureKeyPair::read(
287 self.provider.storage(),
288 public_key,
289 group.ciphersuite().signature_algorithm(),
290 )
291 .ok_or(Error::CantLoadSigner)
292 }
293
294 pub(crate) fn load_mls_group(&self, group_id: &GroupId) -> Result<Option<MlsGroup>, Error> {
306 MlsGroup::load(self.provider.storage(), group_id)
307 .map_err(|e| Error::Provider(e.to_string()))
308 }
309
310 pub(crate) fn exporter_secret(
324 &self,
325 group_id: &GroupId,
326 ) -> Result<group_types::GroupExporterSecret, Error> {
327 let group = self.load_mls_group(group_id)?.ok_or(Error::GroupNotFound)?;
328
329 match self
330 .storage()
331 .get_group_exporter_secret(group_id, group.epoch().as_u64())
332 .map_err(|e| Error::Group(e.to_string()))?
333 {
334 Some(group_exporter_secret) => Ok(group_exporter_secret),
335 None => {
337 let export_secret: [u8; 32] = group
338 .export_secret(self.provider.crypto(), "nostr", b"nostr", 32)?
339 .try_into()
340 .map_err(|_| {
341 Error::Group("Failed to convert export secret to [u8; 32]".to_string())
342 })?;
343 let group_exporter_secret = group_types::GroupExporterSecret {
344 mls_group_id: group_id.clone(),
345 epoch: group.epoch().as_u64(),
346 secret: export_secret,
347 };
348
349 self.storage()
350 .save_group_exporter_secret(group_exporter_secret.clone())
351 .map_err(|e| Error::Group(e.to_string()))?;
352
353 Ok(group_exporter_secret)
354 }
355 }
356 }
357
358 pub fn get_group(&self, group_id: &GroupId) -> Result<Option<group_types::Group>, Error> {
370 self.storage()
371 .find_group_by_mls_group_id(group_id)
372 .map_err(|e| Error::Group(e.to_string()))
373 }
374
375 pub fn get_groups(&self) -> Result<Vec<group_types::Group>, Error> {
382 self.storage()
383 .all_groups()
384 .map_err(|e| Error::Group(e.to_string()))
385 }
386
387 pub fn get_members(&self, group_id: &GroupId) -> Result<BTreeSet<PublicKey>, Error> {
398 let group = self.load_mls_group(group_id)?.ok_or(Error::GroupNotFound)?;
399
400 let mut members = group.members();
402 members.try_fold(BTreeSet::new(), |mut acc, m| {
403 let credentials: BasicCredential = BasicCredential::try_from(m.credential)?;
404 let hex_bytes: &[u8] = credentials.identity();
405 let hex_str: &str = str::from_utf8(hex_bytes)?;
406 let public_key = PublicKey::from_hex(hex_str)?;
407 acc.insert(public_key);
408 Ok(acc)
409 })
410 }
411
412 pub(crate) fn pending_added_members_pubkeys(
427 &self,
428 group_id: &GroupId,
429 ) -> Result<Vec<PublicKey>, Error> {
430 let mls_group = self.load_mls_group(group_id)?.ok_or(Error::GroupNotFound)?;
432
433 let mut added_pubkeys = Vec::new();
434
435 let pending_proposals = mls_group.pending_proposals();
437
438 for proposal in pending_proposals {
440 if let Proposal::Add(add_proposal) = proposal.proposal() {
441 let leaf_node = add_proposal.key_package().leaf_node();
443 let pubkey = self.pubkey_for_leaf_node(leaf_node)?;
444 added_pubkeys.push(pubkey);
445 }
446 }
447
448 Ok(added_pubkeys)
449 }
450
451 pub fn add_members(
465 &self,
466 group_id: &GroupId,
467 key_package_events: &[Event],
468 ) -> Result<UpdateGroupResult, Error> {
469 let mut mls_group = self.load_mls_group(group_id)?.ok_or(Error::GroupNotFound)?;
470 let mls_signer: SignatureKeyPair = self.load_mls_signer(&mls_group)?;
471
472 let own_leaf = mls_group.own_leaf().ok_or(Error::OwnLeafNotFound)?;
474 if !self.is_leaf_node_admin(mls_group.group_id(), own_leaf)? {
475 return Err(Error::Group(
476 "Only group admins can add members".to_string(),
477 ));
478 }
479
480 let mut key_packages_vec: Vec<KeyPackage> = Vec::new();
482 for event in key_package_events {
483 let key_package: KeyPackage = self.parse_key_package(event)?;
485 key_packages_vec.push(key_package);
486 }
487
488 let (commit_message, welcome_message, _group_info) = mls_group
489 .add_members(&self.provider, &mls_signer, &key_packages_vec)
490 .map_err(|e| Error::Group(e.to_string()))?;
491
492 let serialized_commit_message = commit_message
493 .tls_serialize_detached()
494 .map_err(|e| Error::Group(e.to_string()))?;
495
496 let commit_event =
497 self.build_encrypted_message_event(mls_group.group_id(), serialized_commit_message)?;
498
499 let processed_message: message_types::ProcessedMessage = message_types::ProcessedMessage {
501 wrapper_event_id: commit_event.id,
502 message_event_id: None,
503 processed_at: Timestamp::now(),
504 state: message_types::ProcessedMessageState::ProcessedCommit,
505 failure_reason: None,
506 };
507
508 self.storage()
509 .save_processed_message(processed_message)
510 .map_err(|e| Error::Message(e.to_string()))?;
511
512 let serialized_welcome_message = welcome_message
513 .tls_serialize_detached()
514 .map_err(|e| Error::Group(e.to_string()))?;
515
516 let group_relays = self
518 .get_relays(mls_group.group_id())?
519 .into_iter()
520 .collect::<Vec<_>>();
521
522 let welcome_rumors = self.build_welcome_rumors_for_key_packages(
523 &mls_group,
524 serialized_welcome_message,
525 key_package_events.to_vec(),
526 &group_relays,
527 )?;
528
529 Ok(UpdateGroupResult {
537 evolution_event: commit_event,
538 welcome_rumors, })
540 }
541
542 pub fn remove_members(
556 &self,
557 group_id: &GroupId,
558 pubkeys: &[PublicKey],
559 ) -> Result<UpdateGroupResult, Error> {
560 let mut mls_group = self.load_mls_group(group_id)?.ok_or(Error::GroupNotFound)?;
561
562 let signer: SignatureKeyPair = self.load_mls_signer(&mls_group)?;
563
564 let own_leaf = mls_group.own_leaf().ok_or(Error::OwnLeafNotFound)?;
566 if !self.is_leaf_node_admin(group_id, own_leaf)? {
567 return Err(Error::Group(
568 "Only group admins can remove members".to_string(),
569 ));
570 }
571
572 let mut leaf_indices = Vec::new();
574 let members = mls_group.members();
575
576 for (index, member) in members.enumerate() {
577 let pubkey = self.pubkey_for_member(&member)?;
578 if pubkeys.contains(&pubkey) {
579 leaf_indices.push(LeafNodeIndex::new(index as u32));
580 }
581 }
582
583 if leaf_indices.is_empty() {
584 return Err(Error::Group(
585 "No matching members found to remove".to_string(),
586 ));
587 }
588
589 let (commit_message, welcome_option, _group_info) = mls_group
592 .remove_members(&self.provider, &signer, &leaf_indices)
593 .map_err(|e| Error::Group(e.to_string()))?;
594
595 let serialized_commit_message = commit_message
596 .tls_serialize_detached()
597 .map_err(|e| Error::Group(e.to_string()))?;
598
599 let commit_event =
600 self.build_encrypted_message_event(mls_group.group_id(), serialized_commit_message)?;
601
602 let processed_message: message_types::ProcessedMessage = message_types::ProcessedMessage {
604 wrapper_event_id: commit_event.id,
605 message_event_id: None,
606 processed_at: Timestamp::now(),
607 state: message_types::ProcessedMessageState::ProcessedCommit,
608 failure_reason: None,
609 };
610
611 self.storage()
612 .save_processed_message(processed_message)
613 .map_err(|e| Error::Message(e.to_string()))?;
614
615 if welcome_option.is_some() {
617 return Err(Error::Group(
618 "Found welcomes when removing users".to_string(),
619 ));
620 }
621 Ok(UpdateGroupResult {
636 evolution_event: commit_event,
637 welcome_rumors: None, })
639 }
640
641 fn update_group_data_extension(
642 &self,
643 mls_group: &mut MlsGroup,
644 group_id: &GroupId,
645 group_data: &NostrGroupDataExtension,
646 ) -> Result<UpdateGroupResult, Error> {
647 let own_leaf = mls_group.own_leaf().ok_or(Error::OwnLeafNotFound)?;
649 if !self.is_leaf_node_admin(group_id, own_leaf)? {
650 return Err(Error::Group(
651 "Only group admins can update group context extensions".to_string(),
652 ));
653 }
654
655 let extension = Self::get_unknown_extension_from_group_data(group_data)?;
656 let mut extensions = mls_group.extensions().clone();
657 extensions.add_or_replace(extension);
658
659 let signature_keypair = self.load_mls_signer(mls_group)?;
660 let (message_out, _, _) = mls_group.update_group_context_extensions(
661 &self.provider,
662 extensions,
663 &signature_keypair,
664 )?;
665 let commit_event = self.build_encrypted_message_event(
666 mls_group.group_id(),
667 message_out.tls_serialize_detached()?,
668 )?;
669
670 let processed_message: message_types::ProcessedMessage = message_types::ProcessedMessage {
672 wrapper_event_id: commit_event.id,
673 message_event_id: None,
674 processed_at: Timestamp::now(),
675 state: message_types::ProcessedMessageState::ProcessedCommit,
676 failure_reason: None,
677 };
678
679 self.storage()
680 .save_processed_message(processed_message)
681 .map_err(|e| Error::Message(e.to_string()))?;
682
683 Ok(UpdateGroupResult {
684 evolution_event: commit_event,
685 welcome_rumors: None,
686 })
687 }
688
689 pub fn update_group_data(
722 &self,
723 group_id: &GroupId,
724 update: NostrGroupDataUpdate,
725 ) -> Result<UpdateGroupResult, Error> {
726 let mut mls_group = self.load_mls_group(group_id)?.ok_or(Error::GroupNotFound)?;
727
728 let mut group_data = NostrGroupDataExtension::from_group(&mls_group)?;
729
730 if let Some(name) = update.name {
732 group_data.name = name;
733 }
734
735 if let Some(description) = update.description {
736 group_data.description = description;
737 }
738
739 if let Some(image_url) = update.image_url {
740 group_data.image_url = image_url;
741 }
742
743 if let Some(image_key) = update.image_key {
744 group_data.image_key = image_key;
745 }
746
747 if let Some(image_nonce) = update.image_nonce {
748 group_data.image_nonce = image_nonce;
749 }
750
751 if let Some(relays) = update.relays {
752 group_data.relays = relays.into_iter().collect();
753 }
754
755 if let Some(admins) = update.admins {
756 group_data.admins = admins.into_iter().collect();
757 }
758
759 self.update_group_data_extension(&mut mls_group, group_id, &group_data)
760 }
761
762 pub fn get_relays(&self, group_id: &GroupId) -> Result<BTreeSet<RelayUrl>, Error> {
773 let relays = self
774 .storage()
775 .group_relays(group_id)
776 .map_err(|e| Error::Group(e.to_string()))?;
777 Ok(relays.into_iter().map(|r| r.relay_url).collect())
778 }
779
780 fn get_unknown_extension_from_group_data(
781 group_data: &NostrGroupDataExtension,
782 ) -> Result<Extension, Error> {
783 let serialized_group_data = group_data.as_raw().tls_serialize_detached()?;
784
785 Ok(Extension::Unknown(
786 group_data.extension_type(),
787 UnknownExtension(serialized_group_data),
788 ))
789 }
790
791 pub fn create_group(
822 &self,
823 creator_public_key: &PublicKey,
824 member_key_package_events: Vec<Event>,
825 config: NostrGroupConfigData,
826 ) -> Result<GroupResult, Error> {
827 let member_pubkeys = member_key_package_events
829 .clone()
830 .into_iter()
831 .map(|e| e.pubkey)
832 .collect::<Vec<PublicKey>>();
833
834 let admins = config.admins.clone();
835
836 self.validate_group_members(creator_public_key, &member_pubkeys, &admins)?;
838
839 let (credential, signer) = self.generate_credential_with_key(creator_public_key)?;
840
841 tracing::debug!(
842 target: "nostr_mls::groups::create_mls_group",
843 "Credential and signer created, {:?}",
844 credential
845 );
846
847 let group_data = NostrGroupDataExtension::new(
848 config.name,
849 config.description,
850 admins,
851 config.relays.clone(),
852 config.image_url.clone(),
853 config.image_key.clone(),
854 config.image_nonce.clone(),
855 );
856
857 tracing::debug!(
858 target: "nostr_mls::groups::create_mls_group",
859 "Group data created, {:?}",
860 group_data
861 );
862
863 let extension = Self::get_unknown_extension_from_group_data(&group_data)?;
864 let required_capabilities_extension = self.required_capabilities_extension();
865 let extensions = Extensions::from_vec(vec![extension, required_capabilities_extension])?;
866
867 tracing::debug!(
868 target: "nostr_mls::groups::create_mls_group",
869 "Group config extensions created, {:?}",
870 extensions
871 );
872
873 let capabilities = self.capabilities();
875 let group_config = MlsGroupCreateConfig::builder()
876 .ciphersuite(self.ciphersuite)
877 .use_ratchet_tree_extension(true)
878 .capabilities(capabilities)
879 .with_group_context_extensions(extensions)?
880 .build();
881
882 tracing::debug!(
883 target: "nostr_mls::groups::create_mls_group",
884 "Group config built, {:?}",
885 group_config
886 );
887
888 let mut mls_group =
889 MlsGroup::new(&self.provider, &signer, &group_config, credential.clone())?;
890
891 let mut key_packages_vec: Vec<KeyPackage> = Vec::new();
892 for event in &member_key_package_events {
893 let key_package: KeyPackage = self.parse_key_package(event)?;
895 key_packages_vec.push(key_package);
896 }
897
898 let (_, welcome_out, _group_info) =
900 mls_group.add_members(&self.provider, &signer, &key_packages_vec)?;
901
902 mls_group.merge_pending_commit(&self.provider)?;
904
905 let serialized_welcome_message = welcome_out.tls_serialize_detached()?;
907
908 let welcome_rumors = self
909 .build_welcome_rumors_for_key_packages(
910 &mls_group,
911 serialized_welcome_message,
912 member_key_package_events,
913 &config.relays,
914 )?
915 .ok_or(Error::Welcome("Error creating welcome rumors".to_string()))?;
916
917 let group = group_types::Group {
919 mls_group_id: mls_group.group_id().clone(),
920 nostr_group_id: group_data.clone().nostr_group_id,
921 name: group_data.clone().name,
922 description: group_data.clone().description,
923 admin_pubkeys: group_data.clone().admins,
924 last_message_id: None,
925 last_message_at: None,
926 epoch: mls_group.epoch().as_u64(),
927 state: group_types::GroupState::Active,
928 image_url: config.image_url,
929 image_key: config.image_key,
930 image_nonce: config.image_nonce,
931 };
932
933 self.storage().save_group(group.clone()).map_err(
934 |e: nrc_mls_storage::groups::error::GroupError| Error::Group(e.to_string()),
935 )?;
936
937 self.storage()
939 .replace_group_relays(&group.mls_group_id, config.relays.into_iter().collect())
940 .map_err(|e| Error::Group(e.to_string()))?;
941
942 Ok(GroupResult {
943 group,
944 welcome_rumors,
945 })
946 }
947
948 pub fn self_update(&self, group_id: &GroupId) -> Result<UpdateGroupResult, Error> {
975 let mut mls_group = self.load_mls_group(group_id)?.ok_or(Error::GroupNotFound)?;
976
977 let current_secret: group_types::GroupExporterSecret = self
978 .storage()
979 .get_group_exporter_secret(group_id, mls_group.epoch().as_u64())
980 .map_err(|e| Error::Group(e.to_string()))?
981 .ok_or(Error::GroupExporterSecretNotFound)?;
982
983 tracing::debug!(target: "nostr_openmls::groups::self_update", "Current epoch: {:?}", current_secret.epoch);
984
985 let current_signer: SignatureKeyPair = self.load_mls_signer(&mls_group)?;
987
988 let own_leaf = mls_group.own_leaf().ok_or(Error::OwnLeafNotFound)?;
990
991 let new_signature_keypair = SignatureKeyPair::new(self.ciphersuite.signature_algorithm())?;
992
993 new_signature_keypair
994 .store(self.provider.storage())
995 .map_err(|e| Error::Provider(e.to_string()))?;
996
997 let pubkey = BasicCredential::try_from(own_leaf.credential().clone())?
998 .identity()
999 .to_vec();
1000
1001 let new_credential: BasicCredential = BasicCredential::new(pubkey);
1002 let new_credential_with_key = CredentialWithKey {
1003 credential: new_credential.into(),
1004 signature_key: new_signature_keypair.public().into(),
1005 };
1006
1007 let new_signer_bundle = NewSignerBundle {
1008 signer: &new_signature_keypair,
1009 credential_with_key: new_credential_with_key.clone(),
1010 };
1011
1012 let leaf_node_params = LeafNodeParameters::builder()
1013 .with_credential_with_key(new_credential_with_key)
1014 .with_capabilities(own_leaf.capabilities().clone())
1015 .with_extensions(own_leaf.extensions().clone())
1016 .build();
1017
1018 let commit_message_bundle = mls_group.self_update_with_new_signer(
1019 &self.provider,
1020 ¤t_signer,
1021 new_signer_bundle,
1022 leaf_node_params,
1023 )?;
1024
1025 let serialized_commit_message = commit_message_bundle.commit().tls_serialize_detached()?;
1027
1028 let commit_event =
1029 self.build_encrypted_message_event(mls_group.group_id(), serialized_commit_message)?;
1030
1031 let processed_message: message_types::ProcessedMessage = message_types::ProcessedMessage {
1033 wrapper_event_id: commit_event.id,
1034 message_event_id: None,
1035 processed_at: Timestamp::now(),
1036 state: message_types::ProcessedMessageState::ProcessedCommit,
1037 failure_reason: None,
1038 };
1039
1040 self.storage()
1041 .save_processed_message(processed_message)
1042 .map_err(|e| Error::Message(e.to_string()))?;
1043
1044 let serialized_welcome_message = commit_message_bundle
1045 .welcome()
1046 .map(|w| {
1047 w.tls_serialize_detached()
1048 .map_err(|e| Error::Group(e.to_string()))
1049 })
1050 .transpose()?;
1051
1052 if serialized_welcome_message.is_some() {
1054 return Err(Error::Group(
1055 "Found welcomes when performing a self update".to_string(),
1056 ));
1057 }
1058
1059 Ok(UpdateGroupResult {
1060 evolution_event: commit_event,
1061 welcome_rumors: None, })
1063 }
1064
1065 pub fn leave_group(&self, group_id: &GroupId) -> Result<UpdateGroupResult, Error> {
1075 let mut group = self.load_mls_group(group_id)?.ok_or(Error::GroupNotFound)?;
1076
1077 let signer: SignatureKeyPair = self.load_mls_signer(&group)?;
1078
1079 let leave_message = group
1080 .leave_group(&self.provider, &signer)
1081 .map_err(|e| Error::Group(e.to_string()))?;
1082
1083 let serialized_message_out = leave_message
1084 .tls_serialize_detached()
1085 .map_err(|e| Error::Group(e.to_string()))?;
1086
1087 let evolution_event =
1088 self.build_encrypted_message_event(group.group_id(), serialized_message_out)?;
1089
1090 let processed_message: message_types::ProcessedMessage = message_types::ProcessedMessage {
1092 wrapper_event_id: evolution_event.id,
1093 message_event_id: None,
1094 processed_at: Timestamp::now(),
1095 state: message_types::ProcessedMessageState::ProcessedCommit,
1096 failure_reason: None,
1097 };
1098
1099 self.storage()
1100 .save_processed_message(processed_message)
1101 .map_err(|e| Error::Message(e.to_string()))?;
1102
1103 Ok(UpdateGroupResult {
1104 evolution_event,
1105 welcome_rumors: None,
1106 })
1107 }
1108
1109 pub fn merge_pending_commit(&self, group_id: &GroupId) -> Result<(), Error> {
1119 let mut mls_group = self.load_mls_group(group_id)?.ok_or(Error::GroupNotFound)?;
1120 mls_group.merge_pending_commit(&self.provider)?;
1121
1122 self.sync_group_metadata_from_mls(group_id)?;
1124
1125 Ok(())
1126 }
1127
1128 pub fn sync_group_metadata_from_mls(&self, group_id: &GroupId) -> Result<(), Error> {
1141 let mls_group = self.load_mls_group(group_id)?.ok_or(Error::GroupNotFound)?;
1142 let mut stored_group = self.get_group(group_id)?.ok_or(Error::GroupNotFound)?;
1143
1144 stored_group.epoch = mls_group.epoch().as_u64();
1146
1147 if let Ok(group_data) = NostrGroupDataExtension::from_group(&mls_group) {
1149 stored_group.name = group_data.name;
1150 stored_group.description = group_data.description;
1151 stored_group.image_url = group_data.image_url;
1152 stored_group.image_key = group_data.image_key;
1153 stored_group.admin_pubkeys = group_data.admins;
1154 stored_group.nostr_group_id = group_data.nostr_group_id;
1155
1156 self.storage()
1158 .replace_group_relays(group_id, group_data.relays)
1159 .map_err(|e| Error::Group(e.to_string()))?;
1160 }
1161
1162 self.storage()
1163 .save_group(stored_group)
1164 .map_err(|e| Error::Group(e.to_string()))?;
1165
1166 Ok(())
1167 }
1168
1169 fn validate_group_members(
1190 &self,
1191 creator_pubkey: &PublicKey,
1192 member_pubkeys: &[PublicKey],
1193 admin_pubkeys: &[PublicKey],
1194 ) -> Result<bool, Error> {
1195 if !admin_pubkeys.contains(creator_pubkey) {
1197 return Err(Error::Group("Creator must be an admin".to_string()));
1198 }
1199
1200 if member_pubkeys.contains(creator_pubkey) {
1202 return Err(Error::Group(
1203 "Creator must not be included as a member".to_string(),
1204 ));
1205 }
1206
1207 for pubkey in admin_pubkeys.iter() {
1209 if !member_pubkeys.contains(pubkey) && creator_pubkey != pubkey {
1210 return Err(Error::Group("Admin must be a member".to_string()));
1211 }
1212 }
1213 Ok(true)
1214 }
1215
1216 pub(crate) fn build_encrypted_message_event(
1218 &self,
1219 group_id: &GroupId,
1220 serialized_content: Vec<u8>,
1221 ) -> Result<Event, Error> {
1222 let group = self.get_group(group_id)?.ok_or(Error::GroupNotFound)?;
1223
1224 let secret: group_types::GroupExporterSecret = self.exporter_secret(group_id)?;
1226
1227 let secret_key: SecretKey = SecretKey::from_slice(&secret.secret)?;
1229 let export_nostr_keys: Keys = Keys::new(secret_key);
1230
1231 let encrypted_content: String = nip44::encrypt(
1235 export_nostr_keys.secret_key(),
1236 &export_nostr_keys.public_key,
1237 &serialized_content,
1238 nip44::Version::default(),
1239 )?;
1240
1241 let ephemeral_nostr_keys: Keys = Keys::generate();
1243
1244 let tag: Tag = Tag::custom(TagKind::h(), [hex::encode(group.nostr_group_id)]);
1245
1246 let event = EventBuilder::new(Kind::MlsGroupMessage, encrypted_content)
1247 .tag(tag)
1248 .sign_with_keys(&ephemeral_nostr_keys)?;
1249
1250 Ok(event)
1251 }
1252
1253 pub(crate) fn build_welcome_rumors_for_key_packages(
1254 &self,
1255 group: &MlsGroup,
1256 serialized_welcome: Vec<u8>,
1257 key_package_events: Vec<Event>,
1258 group_relays: &[RelayUrl],
1259 ) -> Result<Option<Vec<UnsignedEvent>>, Error> {
1260 let committer_pubkey = self.get_own_pubkey(group)?;
1261 let mut welcome_rumors_vec = Vec::new();
1262
1263 for event in key_package_events {
1264 let welcome_rumor =
1266 EventBuilder::new(Kind::MlsWelcome, hex::encode(&serialized_welcome))
1267 .tags(vec![
1268 Tag::from_standardized(TagStandard::Relays(group_relays.to_vec())),
1269 Tag::event(event.id),
1270 ])
1271 .build(committer_pubkey);
1272
1273 welcome_rumors_vec.push(welcome_rumor);
1274 }
1275
1276 let welcome_rumors = if !welcome_rumors_vec.is_empty() {
1277 Some(welcome_rumors_vec)
1278 } else {
1279 None
1280 };
1281
1282 Ok(welcome_rumors)
1283 }
1284}
1285
1286#[cfg(test)]
1287mod tests {
1288 use std::collections::BTreeSet;
1289
1290 use nostr::{Keys, PublicKey};
1291 use nostr_mls_memory_storage::NostrMlsMemoryStorage;
1292 use nrc_mls_storage::messages::{types as message_types, MessageStorage};
1293 use openmls::group::GroupId;
1294 use openmls::prelude::BasicCredential;
1295
1296 use super::NostrGroupDataExtension;
1297 use crate::groups::NostrGroupDataUpdate;
1298 use crate::test_util::*;
1299 use crate::tests::create_test_nostr_mls;
1300
1301 #[test]
1302 fn test_validate_group_members() {
1303 let nostr_mls = create_test_nostr_mls();
1304 let (creator, members, admins) = create_test_group_members();
1305 let creator_pk = creator.public_key();
1306 let member_pks: Vec<PublicKey> = members.iter().map(|k| k.public_key()).collect();
1307
1308 assert!(nostr_mls
1310 .validate_group_members(&creator_pk, &member_pks, &admins)
1311 .is_ok());
1312
1313 let bad_admins = vec![member_pks[0]];
1315 assert!(nostr_mls
1316 .validate_group_members(&creator_pk, &member_pks, &bad_admins)
1317 .is_err());
1318
1319 let bad_members = vec![creator_pk, member_pks[0]];
1321 assert!(nostr_mls
1322 .validate_group_members(&creator_pk, &bad_members, &admins)
1323 .is_err());
1324
1325 let non_member = Keys::generate().public_key();
1327 let bad_admins = vec![creator_pk, non_member];
1328 assert!(nostr_mls
1329 .validate_group_members(&creator_pk, &member_pks, &bad_admins)
1330 .is_err());
1331 }
1332
1333 #[test]
1334 fn test_create_group_basic() {
1335 let creator_nostr_mls = create_test_nostr_mls();
1336 let (creator, initial_members, admins) = create_test_group_members();
1337 let creator_pk = creator.public_key();
1338
1339 let mut initial_key_package_events = Vec::new();
1341 for member_keys in &initial_members {
1342 let key_package_event = create_key_package_event(&creator_nostr_mls, member_keys);
1343 initial_key_package_events.push(key_package_event);
1344 }
1345
1346 let create_result = creator_nostr_mls
1348 .create_group(
1349 &creator_pk,
1350 initial_key_package_events,
1351 create_nostr_group_config_data(admins),
1352 )
1353 .expect("Failed to create group");
1354
1355 let group_id = &create_result.group.mls_group_id;
1356
1357 creator_nostr_mls
1359 .merge_pending_commit(group_id)
1360 .expect("Failed to merge pending commit");
1361
1362 let members = creator_nostr_mls
1364 .get_members(group_id)
1365 .expect("Failed to get members");
1366
1367 assert_eq!(members.len(), 3); assert!(members.contains(&creator_pk));
1369 for member_keys in &initial_members {
1370 assert!(members.contains(&member_keys.public_key()));
1371 }
1372 }
1373
1374 #[test]
1375 fn test_get_members() {
1376 let creator_nostr_mls = create_test_nostr_mls();
1377 let (creator, initial_members, admins) = create_test_group_members();
1378 let creator_pk = creator.public_key();
1379
1380 let mut initial_key_package_events = Vec::new();
1382 for member_keys in &initial_members {
1383 let key_package_event = create_key_package_event(&creator_nostr_mls, member_keys);
1384 initial_key_package_events.push(key_package_event);
1385 }
1386
1387 let create_result = creator_nostr_mls
1389 .create_group(
1390 &creator_pk,
1391 initial_key_package_events,
1392 create_nostr_group_config_data(admins),
1393 )
1394 .expect("Failed to create group");
1395
1396 let group_id = &create_result.group.mls_group_id;
1397
1398 creator_nostr_mls
1400 .merge_pending_commit(group_id)
1401 .expect("Failed to merge pending commit");
1402
1403 let members = creator_nostr_mls
1405 .get_members(group_id)
1406 .expect("Failed to get members");
1407
1408 assert_eq!(members.len(), 3); assert!(members.contains(&creator_pk));
1410 for member_keys in &initial_members {
1411 assert!(members.contains(&member_keys.public_key()));
1412 }
1413 }
1414
1415 #[test]
1416 fn test_add_members_epoch_advancement() {
1417 let creator_nostr_mls = create_test_nostr_mls();
1418 let (creator, initial_members, admins) = create_test_group_members();
1419 let creator_pk = creator.public_key();
1420
1421 let mut initial_key_package_events = Vec::new();
1423 for member_keys in &initial_members {
1424 let key_package_event = create_key_package_event(&creator_nostr_mls, member_keys);
1425 initial_key_package_events.push(key_package_event);
1426 }
1427
1428 let create_result = creator_nostr_mls
1430 .create_group(
1431 &creator_pk,
1432 initial_key_package_events,
1433 create_nostr_group_config_data(admins),
1434 )
1435 .expect("Failed to create group");
1436
1437 let group_id = &create_result.group.mls_group_id;
1438
1439 creator_nostr_mls
1441 .merge_pending_commit(group_id)
1442 .expect("Failed to merge pending commit");
1443
1444 let initial_group = creator_nostr_mls
1446 .get_group(group_id)
1447 .expect("Failed to get group")
1448 .expect("Group should exist");
1449 let initial_epoch = initial_group.epoch;
1450
1451 let new_member = Keys::generate();
1453 let new_key_package_event = create_key_package_event(&creator_nostr_mls, &new_member);
1454
1455 let _add_result = creator_nostr_mls
1457 .add_members(group_id, &[new_key_package_event])
1458 .expect("Failed to add member");
1459
1460 creator_nostr_mls
1462 .merge_pending_commit(group_id)
1463 .expect("Failed to merge pending commit for member addition");
1464
1465 let mls_group = creator_nostr_mls
1467 .load_mls_group(group_id)
1468 .expect("Failed to load MLS group")
1469 .expect("MLS group should exist");
1470 let final_mls_epoch = mls_group.epoch().as_u64();
1471
1472 assert!(
1473 final_mls_epoch > initial_epoch,
1474 "MLS group epoch should advance after adding members (initial: {}, final: {})",
1475 initial_epoch,
1476 final_mls_epoch
1477 );
1478
1479 let final_members = creator_nostr_mls
1481 .get_members(group_id)
1482 .expect("Failed to get members");
1483 assert!(
1484 final_members.contains(&new_member.public_key()),
1485 "New member should be in the group"
1486 );
1487 assert_eq!(
1488 final_members.len(),
1489 4, "Should have 4 total members"
1491 );
1492 }
1493
1494 #[test]
1495 fn test_get_own_pubkey() {
1496 let creator_nostr_mls = create_test_nostr_mls();
1497 let (creator, initial_members, admins) = create_test_group_members();
1498 let creator_pk = creator.public_key();
1499
1500 let mut initial_key_package_events = Vec::new();
1502 for member_keys in &initial_members {
1503 let key_package_event = create_key_package_event(&creator_nostr_mls, member_keys);
1504 initial_key_package_events.push(key_package_event);
1505 }
1506
1507 let create_result = creator_nostr_mls
1509 .create_group(
1510 &creator_pk,
1511 initial_key_package_events,
1512 create_nostr_group_config_data(admins),
1513 )
1514 .expect("Failed to create group");
1515
1516 let group_id = &create_result.group.mls_group_id;
1517
1518 creator_nostr_mls
1520 .merge_pending_commit(group_id)
1521 .expect("Failed to merge pending commit");
1522
1523 let mls_group = creator_nostr_mls
1524 .load_mls_group(group_id)
1525 .expect("Failed to load MLS group")
1526 .expect("MLS group should exist");
1527
1528 let own_pubkey = creator_nostr_mls
1530 .get_own_pubkey(&mls_group)
1531 .expect("Failed to get own pubkey");
1532
1533 assert_eq!(
1534 own_pubkey, creator_pk,
1535 "Own pubkey should match creator pubkey"
1536 );
1537 }
1538
1539 #[test]
1540 fn test_admin_check() {
1541 let creator_nostr_mls = create_test_nostr_mls();
1542 let (creator, initial_members, admins) = create_test_group_members();
1543 let creator_pk = creator.public_key();
1544
1545 let mut initial_key_package_events = Vec::new();
1547 for member_keys in &initial_members {
1548 let key_package_event = create_key_package_event(&creator_nostr_mls, member_keys);
1549 initial_key_package_events.push(key_package_event);
1550 }
1551
1552 let create_result = creator_nostr_mls
1554 .create_group(
1555 &creator_pk,
1556 initial_key_package_events,
1557 create_nostr_group_config_data(admins),
1558 )
1559 .expect("Failed to create group");
1560
1561 let group_id = &create_result.group.mls_group_id;
1562
1563 creator_nostr_mls
1565 .merge_pending_commit(group_id)
1566 .expect("Failed to merge pending commit");
1567
1568 let stored_group = creator_nostr_mls
1570 .get_group(group_id)
1571 .expect("Failed to get group")
1572 .expect("Group should exist");
1573
1574 assert!(
1575 stored_group.admin_pubkeys.contains(&creator_pk),
1576 "Creator should be admin"
1577 );
1578 }
1579
1580 #[test]
1581 fn test_admin_permission_checks() {
1582 let admin_nostr_mls = create_test_nostr_mls();
1583 let non_admin_nostr_mls = create_test_nostr_mls();
1584
1585 let admin_keys = Keys::generate();
1587 let non_admin_keys = Keys::generate();
1588 let member1_keys = Keys::generate();
1589
1590 let admin_pk = admin_keys.public_key();
1591 let _non_admin_pk = non_admin_keys.public_key();
1592 let member1_pk = member1_keys.public_key();
1593
1594 let non_admin_event = create_key_package_event(&admin_nostr_mls, &non_admin_keys);
1596 let member1_event = create_key_package_event(&admin_nostr_mls, &member1_keys);
1597
1598 let create_result = admin_nostr_mls
1601 .create_group(
1602 &admin_pk,
1603 vec![non_admin_event.clone(), member1_event.clone()],
1604 create_nostr_group_config_data(vec![admin_pk]), )
1606 .expect("Failed to create group");
1607
1608 let group_id = &create_result.group.mls_group_id;
1609
1610 admin_nostr_mls
1612 .merge_pending_commit(group_id)
1613 .expect("Failed to merge pending commit");
1614
1615 let new_member_keys = Keys::generate();
1617 let _new_member_pk = new_member_keys.public_key();
1618 let new_member_event = create_key_package_event(&non_admin_nostr_mls, &new_member_keys);
1619
1620 let add_result = admin_nostr_mls.add_members(group_id, &[new_member_event]);
1622 assert!(add_result.is_ok(), "Admin should be able to add members");
1623
1624 admin_nostr_mls
1626 .merge_pending_commit(group_id)
1627 .expect("Failed to merge pending commit for member addition");
1628
1629 let remove_result = admin_nostr_mls.remove_members(group_id, &[member1_pk]);
1631 assert!(
1632 remove_result.is_ok(),
1633 "Admin should be able to remove members"
1634 );
1635
1636 }
1640
1641 #[test]
1642 fn test_pubkey_for_member() {
1643 let creator_nostr_mls = create_test_nostr_mls();
1644 let (creator, initial_members, admins) = create_test_group_members();
1645 let creator_pk = creator.public_key();
1646
1647 let mut initial_key_package_events = Vec::new();
1649 for member_keys in &initial_members {
1650 let key_package_event = create_key_package_event(&creator_nostr_mls, member_keys);
1651 initial_key_package_events.push(key_package_event);
1652 }
1653
1654 let create_result = creator_nostr_mls
1656 .create_group(
1657 &creator_pk,
1658 initial_key_package_events,
1659 create_nostr_group_config_data(admins),
1660 )
1661 .expect("Failed to create group");
1662
1663 let group_id = &create_result.group.mls_group_id;
1664
1665 creator_nostr_mls
1667 .merge_pending_commit(group_id)
1668 .expect("Failed to merge pending commit");
1669
1670 let mls_group = creator_nostr_mls
1671 .load_mls_group(group_id)
1672 .expect("Failed to load MLS group")
1673 .expect("MLS group should exist");
1674
1675 let members: Vec<_> = mls_group.members().collect();
1677 let mut found_pubkeys = Vec::new();
1678
1679 for member in &members {
1680 let pubkey = creator_nostr_mls
1681 .pubkey_for_member(member)
1682 .expect("Failed to get pubkey for member");
1683 found_pubkeys.push(pubkey);
1684 }
1685
1686 assert!(
1688 found_pubkeys.contains(&creator_pk),
1689 "Should find creator pubkey"
1690 );
1691 for member_keys in &initial_members {
1692 assert!(
1693 found_pubkeys.contains(&member_keys.public_key()),
1694 "Should find member pubkey: {:?}",
1695 member_keys.public_key()
1696 );
1697 }
1698 assert_eq!(found_pubkeys.len(), 3, "Should have 3 members total");
1699 }
1700
1701 #[test]
1704 fn test_remove_members_group_not_found() {
1705 use openmls::group::GroupId;
1706
1707 let nostr_mls = create_test_nostr_mls();
1708 let non_existent_group_id = GroupId::from_slice(&[1, 2, 3, 4, 5]);
1709 let dummy_pubkey = Keys::generate().public_key();
1710
1711 let result = nostr_mls.remove_members(&non_existent_group_id, &[dummy_pubkey]);
1712 assert!(
1713 matches!(result, Err(crate::Error::GroupNotFound)),
1714 "Should return GroupNotFound error for non-existent group"
1715 );
1716 }
1717
1718 #[test]
1719 fn test_remove_members_no_matching_members() {
1720 let creator_nostr_mls = create_test_nostr_mls();
1721 let (creator, initial_members, admins) = create_test_group_members();
1722 let creator_pk = creator.public_key();
1723
1724 let mut initial_key_package_events = Vec::new();
1726 for member_keys in &initial_members {
1727 let key_package_event = create_key_package_event(&creator_nostr_mls, member_keys);
1728 initial_key_package_events.push(key_package_event);
1729 }
1730
1731 let create_result = creator_nostr_mls
1733 .create_group(
1734 &creator_pk,
1735 initial_key_package_events,
1736 create_nostr_group_config_data(admins),
1737 )
1738 .expect("Failed to create group");
1739
1740 let group_id = &create_result.group.mls_group_id;
1741
1742 creator_nostr_mls
1744 .merge_pending_commit(group_id)
1745 .expect("Failed to merge pending commit");
1746
1747 let non_member = Keys::generate().public_key();
1749 let result = creator_nostr_mls.remove_members(group_id, &[non_member]);
1750
1751 assert!(
1752 matches!(
1753 result,
1754 Err(crate::Error::Group(ref msg)) if msg.contains("No matching members found")
1755 ),
1756 "Should return error when no matching members found"
1757 );
1758 }
1759
1760 #[test]
1761 fn test_remove_members_epoch_advancement() {
1762 let creator_nostr_mls = create_test_nostr_mls();
1763 let (creator, initial_members, admins) = create_test_group_members();
1764 let creator_pk = creator.public_key();
1765
1766 let mut initial_key_package_events = Vec::new();
1768 for member_keys in &initial_members {
1769 let key_package_event = create_key_package_event(&creator_nostr_mls, member_keys);
1770 initial_key_package_events.push(key_package_event);
1771 }
1772
1773 let create_result = creator_nostr_mls
1775 .create_group(
1776 &creator_pk,
1777 initial_key_package_events,
1778 create_nostr_group_config_data(admins),
1779 )
1780 .expect("Failed to create group");
1781
1782 let group_id = &create_result.group.mls_group_id;
1783
1784 creator_nostr_mls
1786 .merge_pending_commit(group_id)
1787 .expect("Failed to merge pending commit");
1788
1789 let initial_group = creator_nostr_mls
1791 .get_group(group_id)
1792 .expect("Failed to get group")
1793 .expect("Group should exist");
1794 let initial_epoch = initial_group.epoch;
1795
1796 let member_to_remove = initial_members[0].public_key();
1798 let _remove_result = creator_nostr_mls
1799 .remove_members(group_id, &[member_to_remove])
1800 .expect("Failed to remove member");
1801
1802 creator_nostr_mls
1804 .merge_pending_commit(group_id)
1805 .expect("Failed to merge pending commit for member removal");
1806
1807 let mls_group = creator_nostr_mls
1809 .load_mls_group(group_id)
1810 .expect("Failed to load MLS group")
1811 .expect("MLS group should exist");
1812 let final_mls_epoch = mls_group.epoch().as_u64();
1813
1814 assert!(
1815 final_mls_epoch > initial_epoch,
1816 "MLS group epoch should advance after removing members (initial: {}, final: {})",
1817 initial_epoch,
1818 final_mls_epoch
1819 );
1820
1821 let final_members = creator_nostr_mls
1823 .get_members(group_id)
1824 .expect("Failed to get members");
1825 assert!(
1826 !final_members.contains(&member_to_remove),
1827 "Removed member should not be in the group"
1828 );
1829 assert_eq!(
1830 final_members.len(),
1831 2, "Should have 2 total members after removal"
1833 );
1834 }
1835
1836 #[test]
1837 fn test_self_update_success() {
1838 let creator_nostr_mls = create_test_nostr_mls();
1839 let (creator, initial_members, admins) = create_test_group_members();
1840 let creator_pk = creator.public_key();
1841
1842 let mut initial_key_package_events = Vec::new();
1844 for member_keys in &initial_members {
1845 let key_package_event = create_key_package_event(&creator_nostr_mls, member_keys);
1846 initial_key_package_events.push(key_package_event);
1847 }
1848
1849 let create_result = creator_nostr_mls
1851 .create_group(
1852 &creator_pk,
1853 initial_key_package_events,
1854 create_nostr_group_config_data(admins),
1855 )
1856 .expect("Failed to create group");
1857
1858 let group_id = &create_result.group.mls_group_id;
1859
1860 creator_nostr_mls
1862 .merge_pending_commit(group_id)
1863 .expect("Failed to merge pending commit");
1864
1865 let initial_members_set = creator_nostr_mls
1867 .get_members(group_id)
1868 .expect("Failed to get initial members");
1869 assert_eq!(initial_members_set.len(), 3); let initial_mls_group = creator_nostr_mls
1873 .load_mls_group(group_id)
1874 .expect("Failed to load MLS group")
1875 .expect("MLS group should exist");
1876 let initial_epoch = initial_mls_group.epoch().as_u64();
1877
1878 let _initial_secret = creator_nostr_mls
1880 .exporter_secret(group_id)
1881 .expect("Failed to get initial exporter secret");
1882
1883 let update_result = creator_nostr_mls
1885 .self_update(group_id)
1886 .expect("Failed to perform self update");
1887
1888 creator_nostr_mls
1890 .merge_pending_commit(group_id)
1891 .expect("Failed to merge pending commit for self update");
1892
1893 assert!(
1895 !update_result.evolution_event.content.is_empty(),
1896 "Evolution event should not be empty"
1897 );
1898 let final_members = creator_nostr_mls
1903 .get_members(group_id)
1904 .expect("Failed to get final members");
1905 assert_eq!(
1906 final_members.len(),
1907 3,
1908 "Member count should remain the same after self update"
1909 );
1910
1911 assert!(
1913 final_members.contains(&creator_pk),
1914 "Creator should still be in group"
1915 );
1916 for initial_member_keys in &initial_members {
1917 assert!(
1918 final_members.contains(&initial_member_keys.public_key()),
1919 "Initial member should still be in group"
1920 );
1921 }
1922
1923 let final_mls_group = creator_nostr_mls
1925 .load_mls_group(group_id)
1926 .expect("Failed to load MLS group")
1927 .expect("MLS group should exist");
1928 let final_epoch = final_mls_group.epoch().as_u64();
1929
1930 assert!(
1931 final_epoch > initial_epoch,
1932 "Epoch should advance after self update (initial: {}, final: {})",
1933 initial_epoch,
1934 final_epoch
1935 );
1936 }
1937
1938 #[test]
1939 fn test_self_update_group_not_found() {
1940 use openmls::group::GroupId;
1941
1942 let nostr_mls = create_test_nostr_mls();
1943 let non_existent_group_id = GroupId::from_slice(&[1, 2, 3, 4, 5]);
1944
1945 let result = nostr_mls.self_update(&non_existent_group_id);
1946 assert!(
1947 matches!(result, Err(crate::Error::GroupNotFound)),
1948 "Should return GroupNotFound error for non-existent group"
1949 );
1950 }
1951
1952 #[test]
1953 fn test_self_update_key_rotation() {
1954 let creator_nostr_mls = create_test_nostr_mls();
1955 let (creator, initial_members, admins) = create_test_group_members();
1956 let creator_pk = creator.public_key();
1957
1958 let mut initial_key_package_events = Vec::new();
1960 for member_keys in &initial_members {
1961 let key_package_event = create_key_package_event(&creator_nostr_mls, member_keys);
1962 initial_key_package_events.push(key_package_event);
1963 }
1964
1965 let create_result = creator_nostr_mls
1967 .create_group(
1968 &creator_pk,
1969 initial_key_package_events,
1970 create_nostr_group_config_data(admins),
1971 )
1972 .expect("Failed to create group");
1973
1974 let group_id = &create_result.group.mls_group_id;
1975
1976 creator_nostr_mls
1978 .merge_pending_commit(group_id)
1979 .expect("Failed to merge pending commit");
1980
1981 let initial_mls_group = creator_nostr_mls
1983 .load_mls_group(group_id)
1984 .expect("Failed to load MLS group")
1985 .expect("MLS group should exist");
1986 let initial_own_leaf = initial_mls_group
1987 .own_leaf()
1988 .expect("Failed to get initial own leaf");
1989 let initial_signature_key = initial_own_leaf.signature_key().as_slice().to_vec();
1990
1991 let _initial_secret = creator_nostr_mls
1993 .exporter_secret(group_id)
1994 .expect("Failed to get initial exporter secret");
1995
1996 let _update_result = creator_nostr_mls
1998 .self_update(group_id)
1999 .expect("Failed to perform self update");
2000
2001 creator_nostr_mls
2003 .merge_pending_commit(group_id)
2004 .expect("Failed to merge pending commit for self update");
2005
2006 let final_mls_group = creator_nostr_mls
2008 .load_mls_group(group_id)
2009 .expect("Failed to load MLS group")
2010 .expect("MLS group should exist");
2011 let final_own_leaf = final_mls_group
2012 .own_leaf()
2013 .expect("Failed to get final own leaf");
2014 let final_signature_key = final_own_leaf.signature_key().as_slice().to_vec();
2015
2016 assert_ne!(
2018 initial_signature_key, final_signature_key,
2019 "Signature key should be different after self update"
2020 );
2021
2022 let initial_credential = BasicCredential::try_from(initial_own_leaf.credential().clone())
2024 .expect("Failed to extract initial credential");
2025 let final_credential = BasicCredential::try_from(final_own_leaf.credential().clone())
2026 .expect("Failed to extract final credential");
2027
2028 assert_eq!(
2029 initial_credential.identity(),
2030 final_credential.identity(),
2031 "Public key identity should remain the same after self update"
2032 );
2033 }
2034
2035 #[test]
2036 fn test_self_update_exporter_secret_rotation() {
2037 let creator_nostr_mls = create_test_nostr_mls();
2038 let (creator, initial_members, admins) = create_test_group_members();
2039 let creator_pk = creator.public_key();
2040
2041 let mut initial_key_package_events = Vec::new();
2043 for member_keys in &initial_members {
2044 let key_package_event = create_key_package_event(&creator_nostr_mls, member_keys);
2045 initial_key_package_events.push(key_package_event);
2046 }
2047
2048 let create_result = creator_nostr_mls
2050 .create_group(
2051 &creator_pk,
2052 initial_key_package_events,
2053 create_nostr_group_config_data(admins),
2054 )
2055 .expect("Failed to create group");
2056
2057 let group_id = &create_result.group.mls_group_id;
2058
2059 creator_nostr_mls
2061 .merge_pending_commit(group_id)
2062 .expect("Failed to merge pending commit");
2063
2064 let initial_secret = creator_nostr_mls
2066 .exporter_secret(group_id)
2067 .expect("Failed to get initial exporter secret");
2068
2069 let _update_result = creator_nostr_mls
2071 .self_update(group_id)
2072 .expect("Failed to perform self update");
2073
2074 creator_nostr_mls
2076 .merge_pending_commit(group_id)
2077 .expect("Failed to merge pending commit for self update");
2078
2079 let final_secret = creator_nostr_mls
2081 .exporter_secret(group_id)
2082 .expect("Failed to get final exporter secret");
2083
2084 assert_ne!(
2086 initial_secret.secret, final_secret.secret,
2087 "Exporter secret should be different after self update"
2088 );
2089
2090 assert!(
2092 final_secret.epoch > initial_secret.epoch,
2093 "Epoch should advance after self update (initial: {}, final: {})",
2094 initial_secret.epoch,
2095 final_secret.epoch
2096 );
2097
2098 assert_eq!(
2100 initial_secret.mls_group_id, final_secret.mls_group_id,
2101 "Group ID should remain the same"
2102 );
2103 }
2104
2105 #[test]
2106 fn test_update_group_data() {
2107 let creator_nostr_mls = create_test_nostr_mls();
2108 let (creator, initial_members, admins) = create_test_group_members();
2109 let creator_pk = creator.public_key();
2110
2111 let mut initial_key_package_events = Vec::new();
2113 for member_keys in &initial_members {
2114 let key_package_event = create_key_package_event(&creator_nostr_mls, member_keys);
2115 initial_key_package_events.push(key_package_event);
2116 }
2117
2118 let create_result = creator_nostr_mls
2120 .create_group(
2121 &creator_pk,
2122 initial_key_package_events,
2123 create_nostr_group_config_data(admins),
2124 )
2125 .expect("Failed to create group");
2126
2127 let group_id = &create_result.group.mls_group_id;
2128
2129 creator_nostr_mls
2131 .merge_pending_commit(group_id)
2132 .expect("Failed to merge pending commit");
2133
2134 let initial_mls_group = creator_nostr_mls
2136 .load_mls_group(group_id)
2137 .expect("Failed to load MLS group")
2138 .expect("MLS group should exist");
2139 let initial_group_data = NostrGroupDataExtension::from_group(&initial_mls_group).unwrap();
2140
2141 let new_name = "Updated Name".to_string();
2143 let update = NostrGroupDataUpdate::new().name(new_name.clone());
2144 let update_result = creator_nostr_mls
2145 .update_group_data(group_id, update)
2146 .expect("Failed to update group name");
2147
2148 assert!(!update_result.evolution_event.content.is_empty());
2149 assert!(update_result.welcome_rumors.is_none());
2150
2151 creator_nostr_mls
2152 .merge_pending_commit(group_id)
2153 .expect("Failed to merge pending commit");
2154
2155 let updated_mls_group = creator_nostr_mls
2156 .load_mls_group(group_id)
2157 .expect("Failed to load MLS group")
2158 .expect("MLS group should exist");
2159 let updated_group_data = NostrGroupDataExtension::from_group(&updated_mls_group).unwrap();
2160
2161 assert_eq!(updated_group_data.name, new_name);
2162 assert_eq!(
2163 updated_group_data.description,
2164 initial_group_data.description
2165 );
2166 assert_eq!(updated_group_data.image_url, initial_group_data.image_url);
2167
2168 let new_description = "Updated Description".to_string();
2170 let new_image_url = "https://example.com/new-image.png".to_string();
2171 let new_image_key = vec![1, 2, 3, 4, 5];
2172
2173 let update = NostrGroupDataUpdate::new()
2174 .description(new_description.clone())
2175 .image_url(Some(new_image_url.clone()))
2176 .image_key(Some(new_image_key.clone()));
2177
2178 let update_result = creator_nostr_mls
2179 .update_group_data(group_id, update)
2180 .expect("Failed to update multiple fields");
2181
2182 assert!(!update_result.evolution_event.content.is_empty());
2183
2184 creator_nostr_mls
2185 .merge_pending_commit(group_id)
2186 .expect("Failed to merge pending commit");
2187
2188 let final_mls_group = creator_nostr_mls
2189 .load_mls_group(group_id)
2190 .expect("Failed to load MLS group")
2191 .expect("MLS group should exist");
2192 let final_group_data = NostrGroupDataExtension::from_group(&final_mls_group).unwrap();
2193
2194 assert_eq!(final_group_data.name, new_name); assert_eq!(final_group_data.description, new_description);
2196 assert_eq!(final_group_data.image_url, Some(new_image_url));
2197 assert_eq!(final_group_data.image_key, Some(new_image_key));
2198
2199 let update = NostrGroupDataUpdate::new()
2201 .image_url::<String>(None)
2202 .image_key(None);
2203
2204 let update_result = creator_nostr_mls
2205 .update_group_data(group_id, update)
2206 .expect("Failed to clear optional fields");
2207
2208 assert!(!update_result.evolution_event.content.is_empty());
2209
2210 creator_nostr_mls
2211 .merge_pending_commit(group_id)
2212 .expect("Failed to merge pending commit");
2213
2214 let cleared_mls_group = creator_nostr_mls
2215 .load_mls_group(group_id)
2216 .expect("Failed to load MLS group")
2217 .expect("MLS group should exist");
2218 let cleared_group_data = NostrGroupDataExtension::from_group(&cleared_mls_group).unwrap();
2219
2220 assert_eq!(cleared_group_data.name, new_name);
2221 assert_eq!(cleared_group_data.description, new_description);
2222 assert_eq!(cleared_group_data.image_url, None);
2223 assert_eq!(cleared_group_data.image_key, None);
2224
2225 let empty_update = NostrGroupDataUpdate::new();
2227 let update_result = creator_nostr_mls
2228 .update_group_data(group_id, empty_update)
2229 .expect("Failed to apply empty update");
2230
2231 assert!(!update_result.evolution_event.content.is_empty());
2232
2233 creator_nostr_mls
2234 .merge_pending_commit(group_id)
2235 .expect("Failed to merge pending commit");
2236
2237 let unchanged_mls_group = creator_nostr_mls
2238 .load_mls_group(group_id)
2239 .expect("Failed to load MLS group")
2240 .expect("MLS group should exist");
2241 let unchanged_group_data =
2242 NostrGroupDataExtension::from_group(&unchanged_mls_group).unwrap();
2243
2244 assert_eq!(unchanged_group_data.name, cleared_group_data.name);
2245 assert_eq!(
2246 unchanged_group_data.description,
2247 cleared_group_data.description
2248 );
2249 assert_eq!(unchanged_group_data.image_url, cleared_group_data.image_url);
2250 assert_eq!(unchanged_group_data.image_key, cleared_group_data.image_key);
2251 }
2252
2253 #[test]
2254 fn test_sync_group_metadata_from_mls() {
2255 let creator_nostr_mls = create_test_nostr_mls();
2256 let (creator, initial_members, admins) = create_test_group_members();
2257 let creator_pk = creator.public_key();
2258
2259 let mut initial_key_package_events = Vec::new();
2261 for member_keys in &initial_members {
2262 let key_package_event = create_key_package_event(&creator_nostr_mls, member_keys);
2263 initial_key_package_events.push(key_package_event);
2264 }
2265
2266 let create_result = creator_nostr_mls
2268 .create_group(
2269 &creator_pk,
2270 initial_key_package_events,
2271 create_nostr_group_config_data(admins.clone()),
2272 )
2273 .expect("Failed to create group");
2274
2275 let group_id = &create_result.group.mls_group_id;
2276
2277 creator_nostr_mls
2279 .merge_pending_commit(group_id)
2280 .expect("Failed to merge pending commit");
2281
2282 let initial_stored_group = creator_nostr_mls
2284 .get_group(group_id)
2285 .expect("Failed to get initial stored group")
2286 .expect("Stored group should exist");
2287
2288 let mut mls_group = creator_nostr_mls
2290 .load_mls_group(group_id)
2291 .expect("Failed to load MLS group")
2292 .expect("MLS group should exist");
2293
2294 let mut new_group_data = NostrGroupDataExtension::from_group(&mls_group).unwrap();
2296 new_group_data.name = "Synchronized Name".to_string();
2297 new_group_data.description = "Synchronized Description".to_string();
2298
2299 let extension =
2301 super::NostrMls::<NostrMlsMemoryStorage>::get_unknown_extension_from_group_data(
2302 &new_group_data,
2303 )
2304 .unwrap();
2305 let mut extensions = mls_group.extensions().clone();
2306 extensions.add_or_replace(extension);
2307
2308 let signature_keypair = creator_nostr_mls.load_mls_signer(&mls_group).unwrap();
2309 let (_message_out, _, _) = mls_group
2310 .update_group_context_extensions(
2311 &creator_nostr_mls.provider,
2312 extensions,
2313 &signature_keypair,
2314 )
2315 .unwrap();
2316
2317 mls_group
2319 .merge_pending_commit(&creator_nostr_mls.provider)
2320 .unwrap();
2321
2322 let stale_stored_group = creator_nostr_mls
2324 .get_group(group_id)
2325 .expect("Failed to get stale stored group")
2326 .expect("Stored group should exist");
2327
2328 assert_eq!(stale_stored_group.name, initial_stored_group.name);
2330 assert_eq!(
2331 stale_stored_group.description,
2332 initial_stored_group.description
2333 );
2334 assert_eq!(stale_stored_group.epoch, initial_stored_group.epoch);
2335
2336 creator_nostr_mls
2338 .sync_group_metadata_from_mls(group_id)
2339 .expect("Failed to sync group metadata");
2340
2341 let synced_stored_group = creator_nostr_mls
2343 .get_group(group_id)
2344 .expect("Failed to get synced stored group")
2345 .expect("Stored group should exist");
2346
2347 assert_eq!(synced_stored_group.name, "Synchronized Name");
2348 assert_eq!(synced_stored_group.description, "Synchronized Description");
2349 assert!(synced_stored_group.epoch > initial_stored_group.epoch);
2350 assert_eq!(
2351 synced_stored_group.admin_pubkeys,
2352 admins.into_iter().collect::<BTreeSet<_>>()
2353 );
2354
2355 assert_eq!(
2357 synced_stored_group.mls_group_id,
2358 initial_stored_group.mls_group_id
2359 );
2360 assert_eq!(
2361 synced_stored_group.last_message_id,
2362 initial_stored_group.last_message_id
2363 );
2364 assert_eq!(
2365 synced_stored_group.last_message_at,
2366 initial_stored_group.last_message_at
2367 );
2368 assert_eq!(synced_stored_group.state, initial_stored_group.state);
2369 }
2370
2371 #[test]
2372 fn test_extension_updates_create_processed_messages() {
2373 let creator_nostr_mls = create_test_nostr_mls();
2374 let (creator, initial_members, admins) = create_test_group_members();
2375 let creator_pk = creator.public_key();
2376
2377 let mut initial_key_package_events = Vec::new();
2379 for member_keys in &initial_members {
2380 let key_package_event = create_key_package_event(&creator_nostr_mls, member_keys);
2381 initial_key_package_events.push(key_package_event);
2382 }
2383
2384 let create_result = creator_nostr_mls
2386 .create_group(
2387 &creator_pk,
2388 initial_key_package_events,
2389 create_nostr_group_config_data(admins),
2390 )
2391 .expect("Failed to create group");
2392
2393 let group_id = &create_result.group.mls_group_id;
2394
2395 creator_nostr_mls
2397 .merge_pending_commit(group_id)
2398 .expect("Failed to merge pending commit");
2399
2400 let test_cases = vec![
2402 ("update_group_name", "New Name"),
2403 ("update_group_description", "New Description"),
2404 ];
2405
2406 for (operation, _value) in test_cases {
2407 let update_result = match operation {
2408 "update_group_name" => {
2409 let update = NostrGroupDataUpdate::new().name("New Name".to_string());
2410 creator_nostr_mls.update_group_data(group_id, update)
2411 }
2412 "update_group_description" => {
2413 let update =
2414 NostrGroupDataUpdate::new().description("New Description".to_string());
2415 creator_nostr_mls.update_group_data(group_id, update)
2416 }
2417 _ => panic!("Unknown operation"),
2418 };
2419
2420 let update_result = update_result.unwrap_or_else(|_| panic!("Failed to {}", operation));
2421 let commit_event_id = update_result.evolution_event.id;
2422
2423 let processed_message = creator_nostr_mls
2425 .storage()
2426 .find_processed_message_by_event_id(&commit_event_id)
2427 .expect("Failed to query processed message")
2428 .expect("ProcessedMessage should exist");
2429
2430 assert_eq!(processed_message.wrapper_event_id, commit_event_id);
2431 assert_eq!(processed_message.message_event_id, None);
2432 assert_eq!(
2433 processed_message.state,
2434 message_types::ProcessedMessageState::ProcessedCommit
2435 );
2436 assert_eq!(processed_message.failure_reason, None);
2437
2438 creator_nostr_mls
2440 .merge_pending_commit(group_id)
2441 .unwrap_or_else(|_| panic!("Failed to merge pending commit for {}", operation));
2442 }
2443 }
2444
2445 #[test]
2446 fn test_stored_group_sync_after_all_operations() {
2447 let creator_nostr_mls = create_test_nostr_mls();
2448 let (creator, initial_members, admins) = create_test_group_members();
2449 let creator_pk = creator.public_key();
2450
2451 let mut initial_key_package_events = Vec::new();
2453 for member_keys in &initial_members {
2454 let key_package_event = create_key_package_event(&creator_nostr_mls, member_keys);
2455 initial_key_package_events.push(key_package_event);
2456 }
2457
2458 let create_result = creator_nostr_mls
2460 .create_group(
2461 &creator_pk,
2462 initial_key_package_events,
2463 create_nostr_group_config_data(admins),
2464 )
2465 .expect("Failed to create group");
2466
2467 let group_id = &create_result.group.mls_group_id;
2468
2469 let verify_epoch_sync = || {
2471 let mls_group = creator_nostr_mls.load_mls_group(group_id).unwrap().unwrap();
2472 let stored_group = creator_nostr_mls.get_group(group_id).unwrap().unwrap();
2473 assert_eq!(
2474 stored_group.epoch,
2475 mls_group.epoch().as_u64(),
2476 "Stored group epoch should match MLS group epoch"
2477 );
2478 };
2479
2480 verify_epoch_sync();
2482
2483 let new_member = Keys::generate();
2485 let new_key_package_event = create_key_package_event(&creator_nostr_mls, &new_member);
2486 let _add_result = creator_nostr_mls
2487 .add_members(group_id, &[new_key_package_event])
2488 .expect("Failed to add member");
2489
2490 creator_nostr_mls
2491 .merge_pending_commit(group_id)
2492 .expect("Failed to merge pending commit for add member");
2493 verify_epoch_sync();
2494
2495 let _initial_secret = creator_nostr_mls
2498 .exporter_secret(group_id)
2499 .expect("Failed to get initial exporter secret");
2500
2501 let _self_update_result = creator_nostr_mls
2502 .self_update(group_id)
2503 .expect("Failed to perform self update");
2504
2505 creator_nostr_mls
2506 .merge_pending_commit(group_id)
2507 .expect("Failed to merge pending commit for self update");
2508 verify_epoch_sync();
2509
2510 let update = NostrGroupDataUpdate::new().name("Final Name".to_string());
2512 let _name_result = creator_nostr_mls
2513 .update_group_data(group_id, update)
2514 .expect("Failed to update group name");
2515
2516 creator_nostr_mls
2517 .merge_pending_commit(group_id)
2518 .expect("Failed to merge pending commit for name update");
2519 verify_epoch_sync();
2520
2521 let final_mls_group = creator_nostr_mls.load_mls_group(group_id).unwrap().unwrap();
2523 let final_stored_group = creator_nostr_mls.get_group(group_id).unwrap().unwrap();
2524 let final_group_data = NostrGroupDataExtension::from_group(&final_mls_group).unwrap();
2525
2526 assert_eq!(final_stored_group.name, final_group_data.name);
2527 assert_eq!(final_stored_group.description, final_group_data.description);
2528 assert_eq!(final_stored_group.admin_pubkeys, final_group_data.admins);
2529 assert_eq!(
2530 final_stored_group.nostr_group_id,
2531 final_group_data.nostr_group_id
2532 );
2533 }
2534
2535 #[test]
2536 fn test_sync_group_metadata_error_cases() {
2537 let creator_nostr_mls = create_test_nostr_mls();
2538
2539 let non_existent_group_id = GroupId::from_slice(&[1, 2, 3, 4, 5]);
2541 let result = creator_nostr_mls.sync_group_metadata_from_mls(&non_existent_group_id);
2542 assert!(matches!(result, Err(crate::Error::GroupNotFound)));
2543 }
2544}