nrc_mls/
groups.rs

1//! Nostr MLS Group Management
2//!
3//! This module provides functionality for managing MLS groups in Nostr:
4//! - Group creation and configuration
5//! - Member management (adding/removing members)
6//! - Group state updates and synchronization
7//! - Group metadata handling
8//! - Group secret management
9//!
10//! Groups in Nostr MLS have both an MLS group ID and a Nostr group ID. The MLS group ID
11//! is used internally by the MLS protocol, while the Nostr group ID is used for
12//! relay-based message routing and group discovery.
13
14use 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/// Result of creating a new MLS group
31#[derive(Debug)]
32pub struct GroupResult {
33    /// The stored group
34    pub group: group_types::Group,
35    /// A vec of Kind:444 Welcome Events to be published for members added during creation.
36    pub welcome_rumors: Vec<UnsignedEvent>,
37}
38
39/// Result of updating a group
40#[derive(Debug)]
41pub struct UpdateGroupResult {
42    /// A Kind:445 Event containing the proposal or commit message. To be published to the group relays.
43    pub evolution_event: Event,
44    /// A vec of Kind:444 Welcome Events to be published for any members added as part of the update.
45    pub welcome_rumors: Option<Vec<UnsignedEvent>>,
46}
47
48/// Configuration data for the Group
49#[derive(Debug, Clone)]
50pub struct NostrGroupConfigData {
51    /// Group name
52    pub name: String,
53    /// Group description
54    pub description: String,
55    /// URL to encrypted group image
56    pub image_url: Option<String>,
57    /// Key to decrypt the image
58    pub image_key: Option<Vec<u8>>,
59    /// Nonce to decrypt the image
60    pub image_nonce: Option<Vec<u8>>,
61    /// Relays used by the group
62    pub relays: Vec<RelayUrl>,
63    /// Group admins
64    pub admins: Vec<PublicKey>,
65}
66
67/// Configuration for updating group data with optional fields
68#[derive(Debug, Clone, Default)]
69pub struct NostrGroupDataUpdate {
70    /// Group name (optional)
71    pub name: Option<String>,
72    /// Group description (optional)
73    pub description: Option<String>,
74    /// URL to encrypted group image (optional, use Some(None) to clear)
75    pub image_url: Option<Option<String>>,
76    /// Key to decrypt the image (optional, use Some(None) to clear)
77    pub image_key: Option<Option<Vec<u8>>>,
78    /// Nonce to decrypt the image (optional, use Some(None) to clear)
79    pub image_nonce: Option<Option<Vec<u8>>>,
80    /// Relays used by the group (optional)
81    pub relays: Option<Vec<RelayUrl>>,
82    /// Group admins (optional)
83    pub admins: Option<Vec<PublicKey>>,
84}
85
86impl NostrGroupConfigData {
87    /// Creates NostrGroupConfigData
88    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    /// Creates a new empty update configuration
111    pub fn new() -> Self {
112        Self::default()
113    }
114
115    /// Sets the name to be updated
116    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    /// Sets the description to be updated
125    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    /// Sets the image URL to be updated
134    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    /// Sets the image key to be updated
143    pub fn image_key(mut self, image_key: Option<Vec<u8>>) -> Self {
144        self.image_key = Some(image_key);
145        self
146    }
147
148    /// Sets the image key to be updated
149    pub fn image_nonce(mut self, image_nonce: Option<Vec<u8>>) -> Self {
150        self.image_nonce = Some(image_nonce);
151        self
152    }
153
154    /// Sets the relays to be updated
155    pub fn relays(mut self, relays: Vec<RelayUrl>) -> Self {
156        self.relays = Some(relays);
157        self
158    }
159
160    /// Sets the admins to be updated
161    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    /// Gets the current user's public key from an MLS group
172    ///
173    /// # Arguments
174    ///
175    /// * `group` - Reference to the MLS group
176    ///
177    /// # Returns
178    ///
179    /// * `Ok(PublicKey)` - The current user's public key
180    /// * `Err(Error)` - If the user's leaf node is not found or there is an error extracting the public key
181    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    /// Checks if the LeafNode is an admin of an MLS group
192    ///
193    /// # Arguments
194    ///
195    /// * `group_id` - The MLS group ID
196    /// * `leaf_node` - The leaf to check as an admin
197    ///
198    /// # Returns
199    ///
200    /// * `Ok(true)` - The leaf node is an admin
201    /// * `Ok(false)` - The leaf node is not an admin
202    /// * `Err(Error)` - If the public key cannot be extracted or the group is not found
203    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    /// Checks if the Member is an admin of an MLS group
214    ///
215    /// # Arguments
216    ///
217    /// * `group_id` - The MLS group ID
218    /// * `member` - The member to check as an admin
219    ///
220    /// # Returns
221    ///
222    /// * `Ok(true)` - The member is an admin
223    /// * `Ok(false)` - The member is not an admin
224    /// * `Err(Error)` - If the public key cannot be extracted or the group is not found
225    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    /// Extracts the public key from a leaf node
236    ///
237    /// # Arguments
238    ///
239    /// * `leaf_node` - Reference to the leaf node
240    ///
241    /// # Returns
242    ///
243    /// * `Ok(PublicKey)` - The public key extracted from the leaf node
244    /// * `Err(Error)` - If the public key cannot be extracted or there is an error converting the public key to hex
245    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    /// Extracts the public key from a member
255    ///
256    /// # Arguments
257    ///
258    /// * `member` - Reference to the member
259    ///
260    /// # Returns
261    ///
262    /// * `Ok(PublicKey)` - The public key extracted from the member
263    /// * `Err(Error)` - If the public key cannot be extracted or there is an error converting the public key to hex
264    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    /// Loads the signature key pair for the current member in an MLS group
273    ///
274    /// # Arguments
275    ///
276    /// * `group` - Reference to the MLS group
277    ///
278    /// # Returns
279    ///
280    /// * `Ok(SignatureKeyPair)` - The member's signature key pair
281    /// * `Err(Error)` - If the key pair cannot be loaded
282    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    /// Loads an MLS group from storage by its ID
295    ///
296    /// # Arguments
297    ///
298    /// * `group_id` - The MLS group ID to load
299    ///
300    /// # Returns
301    ///
302    /// * `Ok(Some(MlsGroup))` - The loaded group if found
303    /// * `Ok(None)` - If no group exists with the given ID
304    /// * `Err(Error)` - If there is an error loading the group
305    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    /// Exports the current epoch's secret key from an MLS group
311    ///
312    /// This secret is used for NIP-44 message encryption in Group Message Events (kind:445).
313    /// The secret is cached in storage to avoid re-exporting it for each message.
314    ///
315    /// # Arguments
316    ///
317    /// * `group_id` - The MLS group ID
318    ///
319    /// # Returns
320    ///
321    /// * `Ok(GroupExporterSecret)` - The exported secret
322    /// * `Err(Error)` - If the group is not found or there is an error exporting the secret
323    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            // If it's not already in the storage, export the secret and save it
336            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    /// Retrieves a Nostr MLS group by its MLS group ID
359    ///
360    /// # Arguments
361    ///
362    /// * `group_id` - The MLS group ID to look up
363    ///
364    /// # Returns
365    ///
366    /// * `Ok(Some(Group))` - The group if found
367    /// * `Ok(None)` - If no group exists with the given ID
368    /// * `Err(Error)` - If there is an error accessing storage
369    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    /// Retrieves all Nostr MLS groups from storage
376    ///
377    /// # Returns
378    ///
379    /// * `Ok(Vec<Group>)` - List of all groups
380    /// * `Err(Error)` - If there is an error accessing storage
381    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    /// Gets the public keys of all members in an MLS group
388    ///
389    /// # Arguments
390    ///
391    /// * `group_id` - The MLS group ID
392    ///
393    /// # Returns
394    ///
395    /// * `Ok(BTreeSet<PublicKey>)` - Set of member public keys
396    /// * `Err(Error)` - If the group is not found or there is an error accessing member data
397    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        // Store members in a variable to extend its lifetime
401        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    /// Gets the public keys of members that will be added from pending proposals in an MLS group
413    ///
414    /// This helper method loads an MLS group and examines its pending proposals to identify
415    /// any Add proposals that would add new members to the group. For each new member,
416    /// it extracts their public key from their LeafNode.
417    ///
418    /// # Arguments
419    ///
420    /// * `group_id` - The MLS group ID to examine for pending proposals
421    ///
422    /// # Returns
423    ///
424    /// * `Ok(Vec<PublicKey>)` - List of public keys for newly added members in pending proposals
425    /// * `Err(Error)` - If there's an error loading the group or extracting member information
426    pub(crate) fn pending_added_members_pubkeys(
427        &self,
428        group_id: &GroupId,
429    ) -> Result<Vec<PublicKey>, Error> {
430        // Load the MLS group
431        let mls_group = self.load_mls_group(group_id)?.ok_or(Error::GroupNotFound)?;
432
433        let mut added_pubkeys = Vec::new();
434
435        // Get pending proposals from the group
436        let pending_proposals = mls_group.pending_proposals();
437
438        // Extract public keys from Add proposals
439        for proposal in pending_proposals {
440            if let Proposal::Add(add_proposal) = proposal.proposal() {
441                // Extract the public key from the LeafNode using the same pattern as other methods
442                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    /// Add members to a group
452    ///
453    /// NOTE: This function doesn't merge the pending commit. Clients must call this function manually only after successful publish of the commit message to relays.
454    ///
455    /// # Arguments
456    ///
457    /// * `group_id` - The MLS group ID
458    /// * `key_package_events` - The nostr key package events (Kind:443) for each new member to add
459    ///
460    /// # Returns
461    ///
462    /// * `Ok(UpdateGroupResult)`
463    /// * `Err(Error)` - If there is an error adding members
464    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        // Check if current user is an admin
473        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        // Parse key packages from events
481        let mut key_packages_vec: Vec<KeyPackage> = Vec::new();
482        for event in key_package_events {
483            // TODO: Error handling for failure here
484            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        // Create processed_message to track state of message
500        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        // Get relays for this group
517        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        // let serialized_group_info = group_info
530        //     .map(|g| {
531        //         g.tls_serialize_detached()
532        //             .map_err(|e| Error::Group(e.to_string()))
533        //     })
534        //     .transpose()?;
535
536        Ok(UpdateGroupResult {
537            evolution_event: commit_event,
538            welcome_rumors, // serialized_group_info,
539        })
540    }
541
542    /// Remove members from a group
543    ///
544    /// NOTE: This function doesn't merge the pending commit. Clients must call this function manually only after successful publish of the commit message to relays.
545    ///
546    /// # Arguments
547    ///
548    /// * `group_id` - The MLS group ID
549    /// * `pubkeys` - The Nostr public keys of the members to remove
550    ///
551    /// # Returns
552    ///
553    /// * `Ok(UpdateGroupResult)`
554    /// * `Err(Error)` - If there is an error removing members
555    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        // Check if current user is an admin
565        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        // Convert pubkeys to leaf indices
573        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        // TODO: Get a list of users to be added from any proposals and create welcome events for them
590
591        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        // Create processed_message to track state of message
603        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        // For now, if we find welcomes, throw an error.
616        if welcome_option.is_some() {
617            return Err(Error::Group(
618                "Found welcomes when removing users".to_string(),
619            ));
620        }
621        // let serialized_welcome_message = welcome_option
622        //     .map(|w| {
623        //         w.tls_serialize_detached()
624        //             .map_err(|e| Error::Group(e.to_string()))
625        //     })
626        //     .transpose()?;
627
628        // let serialized_group_info = group_info
629        //     .map(|g| {
630        //         g.tls_serialize_detached()
631        //             .map_err(|e| Error::Group(e.to_string()))
632        //     })
633        //     .transpose()?;
634
635        Ok(UpdateGroupResult {
636            evolution_event: commit_event,
637            welcome_rumors: None, // serialized_group_info,
638        })
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        // Check if current user is an admin
648        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        // Create processed_message to track state of message
671        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    /// Updates group data with the specified configuration
690    ///
691    /// This method allows updating one or more fields of the group data in a single operation.
692    /// Only the fields specified in the update configuration will be modified.
693    ///
694    /// # Arguments
695    ///
696    /// * `group_id` - The MLS group ID
697    /// * `update` - Configuration specifying which fields to update and their new values
698    ///
699    /// # Returns
700    ///
701    /// * `Ok(UpdateGroupResult)` - Update result containing the evolution event
702    /// * `Err(Error)` - If the group is not found or the operation fails
703    ///
704    /// # Examples
705    ///
706    /// ```rust,ignore
707    /// // Update only the name
708    /// let update = NostrGroupDataUpdate::new().name("New Group Name");
709    /// mls.update_group_data(&group_id, update)?;
710    ///
711    /// // Update name and description together
712    /// let update = NostrGroupDataUpdate::new()
713    ///     .name("New Name")
714    ///     .description("New Description");
715    /// mls.update_group_data(&group_id, update)?;
716    ///
717    /// // Update image, clearing the existing one
718    /// let update = NostrGroupDataUpdate::new().image_url(None);
719    /// mls.update_group_data(&group_id, update)?;
720    /// ```
721    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        // Apply updates only for fields that are specified
731        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    /// Retrieves the set of relay URLs associated with an MLS group
763    ///
764    /// # Arguments
765    ///
766    /// * `group_id` - The MLS group ID
767    ///
768    /// # Returns
769    ///
770    /// * `Ok(BTreeSet<RelayUrl>)` - Set of relay URLs where group messages are published
771    /// * `Err(Error)` - If there is an error accessing storage or the group is not found
772    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    /// Creates a new MLS group with the specified members and settings.
792    ///
793    /// This function creates a new MLS group with the given name, description, members, and administrators.
794    /// It generates the necessary cryptographic credentials, configures the group with Nostr-specific extensions,
795    /// and adds the specified members.
796    ///
797    /// NOTE: This function doesn't merge the pending commit. Clients must call this function manually only after successful publish of the commit message to relays.
798    ///
799    /// # Arguments
800    ///
801    /// * `name` - The name of the group
802    /// * `description` - A description of the group
803    /// * `creator_public_key` - The Nostr public key of the group creator
804    /// * `member_key_package_events` - A vector of Nostr events (Kind:443) containing key packages for the initial group members
805    /// * `admins` - A vector of Nostr public keys for group administrators
806    /// * `group_relays` - A vector of relay URLs where group messages will be published
807    ///
808    /// # Returns
809    ///
810    /// A `GroupResult` containing:
811    /// - The created MLS group
812    /// - A Vec of UnsignedEvents representing the welcomes to be sent to new users
813    ///
814    /// # Errors
815    ///
816    /// Returns a `Error` if:
817    /// - Credential generation fails
818    /// - Group creation fails
819    /// - Adding members fails
820    /// - Message serialization fails
821    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        // Get member pubkeys
828        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        // Validate group members
837        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        // Build the group config
874        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            // TODO: Error handling for failure here
894            let key_package: KeyPackage = self.parse_key_package(event)?;
895            key_packages_vec.push(key_package);
896        }
897
898        // Add members to the group
899        let (_, welcome_out, _group_info) =
900            mls_group.add_members(&self.provider, &signer, &key_packages_vec)?;
901
902        // Merge the pending commit to finalize the group state - we do this during creation because we don't have a commit event to fan out to the group relays
903        mls_group.merge_pending_commit(&self.provider)?;
904
905        // Serialize the welcome message and send it to the members
906        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        // Save the NostrMLS Group
918        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        // Save the group relays after saving the group
938        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    /// Updates the current member's leaf node in an MLS group.
949    /// Does not currently support updating any group attributes.
950    ///
951    /// This function performs a self-update operation in the specified MLS group by:
952    /// 1. Loading the group from storage
953    /// 2. Generating a new signature keypair
954    /// 3. Storing the keypair
955    /// 4. Creating and applying a self-update proposal
956    ///
957    /// NOTE: This function doesn't merge the pending commit. Clients must call this function manually only after successful publish of the commit message to relays.
958    ///
959    /// # Arguments
960    ///
961    /// * `group_id` - The ID of the MLS group
962    ///
963    /// # Returns
964    ///
965    /// An UpdateGroupResult
966    ///
967    /// # Errors
968    ///
969    /// Returns a Error if:
970    /// - The group cannot be loaded from storage
971    /// - The specified group is not found
972    /// - Failed to generate or store signature keypair
973    /// - Failed to perform self-update operation
974    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        // Load current signer
986        let current_signer: SignatureKeyPair = self.load_mls_signer(&mls_group)?;
987
988        // Get own leaf
989        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            &current_signer,
1021            new_signer_bundle,
1022            leaf_node_params,
1023        )?;
1024
1025        // Serialize the message
1026        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        // Create processed_message to track state of message
1032        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        // For now, if we find welcomes, throw an error.
1053        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, // serialized_group_info,
1062        })
1063    }
1064
1065    /// Create a proposal to leave the group
1066    /// It's not possible to unilaterally leave a group because you can't commit yourself out of the tree.
1067    ///
1068    /// # Arguments
1069    ///
1070    /// * `group_id` - The ID of the MLS group
1071    ///
1072    /// # Returns
1073    /// * `Ok(UpdateGroupResult)`
1074    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        // Create processed_message to track state of message
1091        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    /// Merge any pending commits.
1110    /// This should be called AFTER publishing the Kind:445 message that contains a commit message to mitigate race conditions
1111    ///
1112    /// # Arguments
1113    /// * `group_id` - the MlsGroup GroupId value
1114    ///
1115    /// Returns
1116    /// * `Ok(())` - if the commits were merged successfully
1117    /// * Err(GroupError) - if something goes wrong
1118    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        // Sync the stored group metadata with the updated MLS group state
1123        self.sync_group_metadata_from_mls(group_id)?;
1124
1125        Ok(())
1126    }
1127
1128    /// Synchronizes the stored group metadata with the current MLS group state
1129    ///
1130    /// This helper method ensures that all fields in the stored `group_types::Group`
1131    /// remain consistent with the MLS group state and extensions after operations.
1132    /// It should be called after any operation that changes the group state or extensions.
1133    ///
1134    /// # Arguments
1135    /// * `group_id` - The MLS group ID to synchronize
1136    ///
1137    /// # Returns
1138    /// * `Ok(())` - if synchronization succeeds
1139    /// * `Err(Error)` - if the group is not found or synchronization fails
1140    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        // Update epoch from MLS group
1145        stored_group.epoch = mls_group.epoch().as_u64();
1146
1147        // Update extension data from NostrGroupDataExtension
1148        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            // Sync relays atomically - replace entire relay set with current extension data
1157            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    /// Validates the members and admins of a group during creation
1170    ///
1171    /// # Arguments
1172    /// * `creator_pubkey` - The public key of the group creator
1173    /// * `member_pubkeys` - List of public keys for group members
1174    /// * `admin_pubkeys` - List of public keys for group admins
1175    ///
1176    /// # Returns
1177    /// * `Ok(true)` if validation passes
1178    /// * `Err(GroupError::InvalidParameters)` if validation fails
1179    ///
1180    /// # Validation Rules
1181    /// - Creator must be an admin but not included in member list
1182    /// - All admins must also be members (except creator)
1183    ///
1184    /// # Errors
1185    /// Returns `GroupError::InvalidParameters` with descriptive message if:
1186    /// - Creator is not an admin
1187    /// - Creator is in member list
1188    /// - Any admin, other than the creator, is not a member
1189    fn validate_group_members(
1190        &self,
1191        creator_pubkey: &PublicKey,
1192        member_pubkeys: &[PublicKey],
1193        admin_pubkeys: &[PublicKey],
1194    ) -> Result<bool, Error> {
1195        // Creator must be an admin
1196        if !admin_pubkeys.contains(creator_pubkey) {
1197            return Err(Error::Group("Creator must be an admin".to_string()));
1198        }
1199
1200        // Creator must not be included as a member
1201        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        // Check that admins are valid pubkeys and are members
1208        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    /// Creates a NIP-44 encrypted message event Kind: 445 signing with an ephemeral keypair.
1217    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        // Export secret
1225        let secret: group_types::GroupExporterSecret = self.exporter_secret(group_id)?;
1226
1227        // Convert that secret to nostr keys
1228        let secret_key: SecretKey = SecretKey::from_slice(&secret.secret)?;
1229        let export_nostr_keys: Keys = Keys::new(secret_key);
1230
1231        // Encrypt the message content
1232        // At some group size this will become too large for NIP44 encryption or relay event size limits.
1233        // We're not sure yet what size, but it's something to be aware of.
1234        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        // Generate ephemeral key
1242        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            // Build welcome event rumors for each new user
1265            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        // Test valid configuration
1309        assert!(nostr_mls
1310            .validate_group_members(&creator_pk, &member_pks, &admins)
1311            .is_ok());
1312
1313        // Test creator not in admin list
1314        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        // Test creator in member list
1320        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        // Test admin not in member list
1326        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        // Create key package events for initial members
1340        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        // Create the group
1347        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        // Merge the pending commit to apply the member additions
1358        creator_nostr_mls
1359            .merge_pending_commit(group_id)
1360            .expect("Failed to merge pending commit");
1361
1362        // Verify group was created with correct members
1363        let members = creator_nostr_mls
1364            .get_members(group_id)
1365            .expect("Failed to get members");
1366
1367        assert_eq!(members.len(), 3); // creator + 2 initial members
1368        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        // Create key package events for initial members
1381        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        // Create the group
1388        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        // Merge the pending commit to apply the member additions
1399        creator_nostr_mls
1400            .merge_pending_commit(group_id)
1401            .expect("Failed to merge pending commit");
1402
1403        // Test get_members
1404        let members = creator_nostr_mls
1405            .get_members(group_id)
1406            .expect("Failed to get members");
1407
1408        assert_eq!(members.len(), 3); // creator + 2 initial members
1409        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        // Create key package events for initial members
1422        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        // Create the initial group
1429        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        // Merge the pending commit to apply the member additions
1440        creator_nostr_mls
1441            .merge_pending_commit(group_id)
1442            .expect("Failed to merge pending commit");
1443
1444        // Get initial epoch
1445        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        // Create key package event for new member
1452        let new_member = Keys::generate();
1453        let new_key_package_event = create_key_package_event(&creator_nostr_mls, &new_member);
1454
1455        // Add the new member
1456        let _add_result = creator_nostr_mls
1457            .add_members(group_id, &[new_key_package_event])
1458            .expect("Failed to add member");
1459
1460        // Merge the pending commit for the member addition
1461        creator_nostr_mls
1462            .merge_pending_commit(group_id)
1463            .expect("Failed to merge pending commit for member addition");
1464
1465        // Verify the MLS group epoch was advanced by checking the actual MLS group
1466        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        // Verify the new member was added
1480        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, // creator + 2 initial + 1 new = 4 total
1490            "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        // Create key package events for initial members
1501        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        // Create the group
1508        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        // Merge the pending commit to apply the member additions
1519        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        // Test get_own_pubkey
1529        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        // Create key package events for initial members
1546        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        // Create the group
1553        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        // Merge the pending commit to apply the member additions
1564        creator_nostr_mls
1565            .merge_pending_commit(group_id)
1566            .expect("Failed to merge pending commit");
1567
1568        // Test admin check - verify creator is in admin list
1569        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        // Generate keys
1586        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        // Create key package events for initial members
1595        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        // Create group with admin as creator, non_admin and member1 as members
1599        // Only admin is an admin
1600        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]), // Only admin is an admin
1605            )
1606            .expect("Failed to create group");
1607
1608        let group_id = &create_result.group.mls_group_id;
1609
1610        // Merge the pending commit to apply the member additions
1611        admin_nostr_mls
1612            .merge_pending_commit(group_id)
1613            .expect("Failed to merge pending commit");
1614
1615        // Create a new member to add
1616        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        // Test that admin can add members (should work)
1621        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        // Merge the pending commit for the member addition
1625        admin_nostr_mls
1626            .merge_pending_commit(group_id)
1627            .expect("Failed to merge pending commit for member addition");
1628
1629        // Test that admin can remove members (should work)
1630        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        // Note: Testing non-admin permissions would require the non-admin user to actually
1637        // be part of the MLS group, which would require processing the welcome message.
1638        // For now, we've verified that admin permissions work correctly.
1639    }
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        // Create key package events for initial members
1648        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        // Create the group
1655        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        // Merge the pending commit to apply the member additions
1666        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        // Test pubkey_for_member by checking all members
1676        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        // Verify we found the expected public keys
1687        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    // TODO: Fix remaining test cases that need to be updated to match new API
1702
1703    #[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        // Create key package events for initial members
1725        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        // Create the group
1732        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        // Merge the pending commit to apply the member additions
1743        creator_nostr_mls
1744            .merge_pending_commit(group_id)
1745            .expect("Failed to merge pending commit");
1746
1747        // Try to remove a member that doesn't exist in the group
1748        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        // Create key package events for initial members
1767        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        // Create the group
1774        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        // Merge the pending commit to apply the member additions
1785        creator_nostr_mls
1786            .merge_pending_commit(group_id)
1787            .expect("Failed to merge pending commit");
1788
1789        // Get initial epoch
1790        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        // Remove a member
1797        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        // Merge the pending commit for the member removal
1803        creator_nostr_mls
1804            .merge_pending_commit(group_id)
1805            .expect("Failed to merge pending commit for member removal");
1806
1807        // Verify the MLS group epoch was advanced
1808        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        // Verify the member was removed
1822        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, // creator + 1 remaining member
1832            "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        // Create key package events for initial members
1843        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        // Create the group
1850        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        // Merge the pending commit to apply the member additions
1861        creator_nostr_mls
1862            .merge_pending_commit(group_id)
1863            .expect("Failed to merge pending commit");
1864
1865        // Verify initial group state
1866        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); // creator + 2 initial members
1870
1871        // Get initial group state
1872        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        // Ensure the exporter secret exists before self update (this creates it if it doesn't exist)
1879        let _initial_secret = creator_nostr_mls
1880            .exporter_secret(group_id)
1881            .expect("Failed to get initial exporter secret");
1882
1883        // Perform self update
1884        let update_result = creator_nostr_mls
1885            .self_update(group_id)
1886            .expect("Failed to perform self update");
1887
1888        // Merge the pending commit for the self update
1889        creator_nostr_mls
1890            .merge_pending_commit(group_id)
1891            .expect("Failed to merge pending commit for self update");
1892
1893        // Verify the result contains the expected data
1894        assert!(
1895            !update_result.evolution_event.content.is_empty(),
1896            "Evolution event should not be empty"
1897        );
1898        // Note: self_update typically doesn't produce a welcome message unless there are special circumstances
1899        // assert!(update_result.serialized_welcome_message.is_none(), "Welcome message should typically be None for self-update");
1900
1901        // Verify the group state was updated correctly
1902        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        // Verify all original members are still in the group
1912        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        // Verify the epoch was advanced
1924        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        // Create key package events for initial members
1959        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        // Create the group
1966        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        // Merge the pending commit to apply the member additions
1977        creator_nostr_mls
1978            .merge_pending_commit(group_id)
1979            .expect("Failed to merge pending commit");
1980
1981        // Get initial signature key from the leaf node
1982        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        // Ensure the exporter secret exists before self update (this creates it if it doesn't exist)
1992        let _initial_secret = creator_nostr_mls
1993            .exporter_secret(group_id)
1994            .expect("Failed to get initial exporter secret");
1995
1996        // Perform self update (this should rotate the signing key)
1997        let _update_result = creator_nostr_mls
1998            .self_update(group_id)
1999            .expect("Failed to perform self update");
2000
2001        // Merge the pending commit for the self update
2002        creator_nostr_mls
2003            .merge_pending_commit(group_id)
2004            .expect("Failed to merge pending commit for self update");
2005
2006        // Get the new signature key
2007        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        // Verify the signature key has been rotated
2017        assert_ne!(
2018            initial_signature_key, final_signature_key,
2019            "Signature key should be different after self update"
2020        );
2021
2022        // Verify the public key identity remains the same
2023        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        // Create key package events for initial members
2042        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        // Create the group
2049        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        // Merge the pending commit to apply the member additions
2060        creator_nostr_mls
2061            .merge_pending_commit(group_id)
2062            .expect("Failed to merge pending commit");
2063
2064        // Get initial exporter secret
2065        let initial_secret = creator_nostr_mls
2066            .exporter_secret(group_id)
2067            .expect("Failed to get initial exporter secret");
2068
2069        // Perform self update
2070        let _update_result = creator_nostr_mls
2071            .self_update(group_id)
2072            .expect("Failed to perform self update");
2073
2074        // Merge the pending commit for the self update
2075        creator_nostr_mls
2076            .merge_pending_commit(group_id)
2077            .expect("Failed to merge pending commit for self update");
2078
2079        // Get the new exporter secret
2080        let final_secret = creator_nostr_mls
2081            .exporter_secret(group_id)
2082            .expect("Failed to get final exporter secret");
2083
2084        // Verify the exporter secret has been rotated
2085        assert_ne!(
2086            initial_secret.secret, final_secret.secret,
2087            "Exporter secret should be different after self update"
2088        );
2089
2090        // Verify the epoch has advanced
2091        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        // Verify the group ID remains the same
2099        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        // Create key package events for initial members
2112        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        // Create the group
2119        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        // Merge the pending commit to apply the member additions
2130        creator_nostr_mls
2131            .merge_pending_commit(group_id)
2132            .expect("Failed to merge pending commit");
2133
2134        // Get initial group data for comparison
2135        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        // Test 1: Update only the name
2142        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        // Test 2: Update multiple fields at once
2169        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); // Should remain from previous update
2195        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        // Test 3: Clear optional fields
2200        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        // Test 4: Empty update (should succeed but not change anything)
2226        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        // Create key package events for initial members
2260        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        // Create the group
2267        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        // Merge the pending commit to apply the member additions
2278        creator_nostr_mls
2279            .merge_pending_commit(group_id)
2280            .expect("Failed to merge pending commit");
2281
2282        // Get initial stored group state
2283        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        // Modify the MLS group directly (simulating state change without sync)
2289        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        // Create a new group data extension with different values
2295        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        // Apply the extension update to MLS group (but not to stored group)
2300        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        // Merge the pending commit to advance epoch
2318        mls_group
2319            .merge_pending_commit(&creator_nostr_mls.provider)
2320            .unwrap();
2321
2322        // At this point, MLS group has changed but stored group is stale
2323        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        // Verify stored group is stale
2329        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        // Now test our sync function
2337        creator_nostr_mls
2338            .sync_group_metadata_from_mls(group_id)
2339            .expect("Failed to sync group metadata");
2340
2341        // Verify stored group is now synchronized
2342        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        // Verify other fields remain unchanged
2356        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        // Create key package events for initial members
2378        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        // Create the group
2385        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        // Merge the pending commit to apply the member additions
2396        creator_nostr_mls
2397            .merge_pending_commit(group_id)
2398            .expect("Failed to merge pending commit");
2399
2400        // Test that each extension update creates a ProcessedMessage
2401        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            // Verify ProcessedMessage was created with correct state
2424            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            // Clean up by merging the commit
2439            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        // Create key package events for initial members
2452        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        // Create the group
2459        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        // Helper function to verify stored group epoch matches MLS group epoch
2470        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        // Test 1: After group creation (should already be synced)
2481        verify_epoch_sync();
2482
2483        // Test 2: After adding members
2484        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        // Test 3: After self update
2496        // Ensure the exporter secret exists before self update (this creates it if it doesn't exist)
2497        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        // Test 4: After extension updates
2511        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        // Test 5: Verify stored group metadata matches extension data
2522        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        // Test with non-existent group
2540        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}