use crate::{
error::{PhalanxError, Result},
identity::{Identity, PublicKey},
message::{GroupMessage, MessageContent, MessageType},
protocol::{ProtocolVersion, HandshakeMessage, KeyRotationMessage},
crypto::{SymmetricKey, hash_multiple},
key_manager::{AdvancedKeyManager, KeySet, RotationPolicy},
};
use std::collections::{HashMap, HashSet};
use std::time::{SystemTime, UNIX_EPOCH};
use x25519_dalek::PublicKey as X25519PublicKey;
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GroupConfig {
pub max_members: usize,
pub key_rotation_interval: u64,
pub persistent_storage: bool,
pub visibility: GroupVisibility,
pub admin_only_operations: Vec<GroupOperation>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum GroupVisibility {
Public,
Private,
InviteOnly,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum GroupOperation {
AddMember,
RemoveMember,
ChangeSettings,
RotateKeys,
DeleteMessage,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MemberRole {
Member,
Admin,
Owner,
Moderator,
Guest,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MemberStatus {
Active,
Muted,
Banned,
Pending,
Suspended,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum GroupPermission {
SendMessages,
DeleteOwnMessages,
DeleteAnyMessages,
InviteMembers,
RemoveMembers,
ModifySettings,
ManageRoles,
RotateKeys,
ViewAuditLogs,
ManageBans,
CreateChannels,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MemberPresence {
Online,
Away,
DoNotDisturb,
Offline,
Invisible,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GroupMember {
pub public_key: PublicKey,
pub ephemeral_key: Option<X25519PublicKey>,
pub role: MemberRole,
pub joined_at: u64,
pub last_seen: Option<u64>,
pub nickname: Option<String>,
pub status: MemberStatus,
pub permissions: Vec<GroupPermission>,
pub presence: MemberPresence,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct MembershipProof {
pub proof_type: String,
pub proof_data: Vec<u8>,
pub signature: Option<ed25519_dalek::Signature>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AuditLogEntry {
pub timestamp: u64,
pub actor: [u8; 32],
pub action: AuditAction,
pub target: Option<[u8; 32]>,
pub details: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum AuditAction {
MemberJoined,
MemberLeft,
MemberRemoved,
RoleChanged,
MemberBanned,
MemberUnbanned,
KeysRotated,
SettingsChanged,
ChannelCreated,
ChannelDeleted,
MessageDeleted,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GroupChannel {
pub id: String,
pub name: String,
pub description: Option<String>,
pub members: HashSet<[u8; 32]>,
pub permissions: HashMap<[u8; 32], Vec<GroupPermission>>,
pub archived: bool,
pub created_at: u64,
pub last_activity: Option<u64>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BanEntry {
pub banned_at: u64,
pub banned_by: [u8; 32],
pub reason: Option<String>,
pub expires_at: Option<u64>,
pub ban_type: BanType,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum BanType {
Full,
ReadOnly,
Temporary,
}
#[derive(Debug)]
pub struct PhalanxGroup {
group_id: [u8; 32],
identity: Identity,
group_key: SymmetricKey,
config: GroupConfig,
members: HashMap<[u8; 32], GroupMember>,
message_sequence: u64,
key_sequence: u64,
last_key_rotation: u64,
version: ProtocolVersion,
audit_log: Vec<AuditLogEntry>,
channels: HashMap<String, GroupChannel>,
banned_members: HashMap<[u8; 32], BanEntry>,
}
impl Default for GroupConfig {
fn default() -> Self {
Self {
max_members: 100,
key_rotation_interval: crate::constants::DEFAULT_KEY_ROTATION_INTERVAL,
persistent_storage: false,
visibility: GroupVisibility::Private,
admin_only_operations: vec![
GroupOperation::AddMember,
GroupOperation::RemoveMember,
GroupOperation::ChangeSettings,
GroupOperation::RotateKeys,
],
}
}
}
impl PhalanxGroup {
pub fn new(identity: Identity) -> Self {
Self::with_config(identity, GroupConfig::default())
}
fn default_permissions_for_role(role: &MemberRole) -> Vec<GroupPermission> {
use GroupPermission::*;
match role {
MemberRole::Owner => vec![
SendMessages, DeleteOwnMessages, DeleteAnyMessages, InviteMembers,
RemoveMembers, ModifySettings, ManageRoles, RotateKeys, ViewAuditLogs,
ManageBans, CreateChannels
],
MemberRole::Admin => vec![
SendMessages, DeleteOwnMessages, DeleteAnyMessages, InviteMembers,
RemoveMembers, ManageRoles, ViewAuditLogs, ManageBans, CreateChannels
],
MemberRole::Moderator => vec![
SendMessages, DeleteOwnMessages, DeleteAnyMessages, ManageBans, ViewAuditLogs
],
MemberRole::Member => vec![
SendMessages, DeleteOwnMessages
],
MemberRole::Guest => vec![],
}
}
pub fn with_config(identity: Identity, config: GroupConfig) -> Self {
let group_id = Self::generate_group_id(&identity.public_key());
let group_key = SymmetricKey::generate();
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let mut members = HashMap::new();
let creator_member = GroupMember {
public_key: identity.public_key(),
ephemeral_key: None,
role: MemberRole::Owner,
joined_at: timestamp,
last_seen: Some(timestamp),
nickname: None,
status: MemberStatus::Active,
permissions: Self::default_permissions_for_role(&MemberRole::Owner),
presence: MemberPresence::Online,
};
members.insert(identity.id(), creator_member);
let mut audit_log = Vec::new();
audit_log.push(AuditLogEntry {
timestamp,
actor: identity.id(),
action: AuditAction::MemberJoined,
target: Some(identity.id()),
details: Some("Group creator".to_string()),
});
Self {
group_id,
identity,
group_key,
config,
members,
message_sequence: 0,
key_sequence: 0,
last_key_rotation: timestamp,
version: ProtocolVersion::current(),
audit_log,
channels: HashMap::new(),
banned_members: HashMap::new(),
}
}
pub fn join(
identity: Identity,
handshake: HandshakeMessage,
group_key: SymmetricKey,
) -> Result<Self> {
let payload = handshake.verify_and_decrypt()?;
let config = GroupConfig::default();
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let mut members = HashMap::new();
let member = GroupMember {
public_key: identity.public_key(),
ephemeral_key: Some(handshake.ephemeral_key),
role: MemberRole::Member,
joined_at: timestamp,
last_seen: Some(timestamp),
nickname: None,
status: MemberStatus::Active,
permissions: Self::default_permissions_for_role(&MemberRole::Member),
presence: MemberPresence::Online,
};
members.insert(identity.id(), member);
let sender = GroupMember {
public_key: handshake.sender_key.clone(),
ephemeral_key: Some(handshake.ephemeral_key),
role: MemberRole::Owner, joined_at: timestamp, last_seen: Some(timestamp),
nickname: None,
status: MemberStatus::Active,
permissions: Self::default_permissions_for_role(&MemberRole::Owner),
presence: MemberPresence::Online,
};
members.insert(handshake.sender_key.id(), sender);
Ok(Self {
group_id: payload.group_id,
identity,
group_key,
config,
members,
message_sequence: 0,
key_sequence: 0,
last_key_rotation: timestamp,
version: ProtocolVersion::current(),
audit_log: Vec::new(),
channels: HashMap::new(),
banned_members: HashMap::new(),
})
}
pub fn group_id(&self) -> &[u8; 32] {
&self.group_id
}
pub fn identity(&self) -> &Identity {
&self.identity
}
pub fn config(&self) -> &GroupConfig {
&self.config
}
pub fn members(&self) -> &HashMap<[u8; 32], GroupMember> {
&self.members
}
pub fn get_member(&self, member_id: &[u8; 32]) -> Option<&GroupMember> {
self.members.get(member_id)
}
pub fn is_admin(&self) -> bool {
if let Some(member) = self.members.get(&self.identity.id()) {
matches!(member.role, MemberRole::Admin | MemberRole::Owner)
} else {
false
}
}
pub fn has_permission(&self, permission: &GroupPermission) -> bool {
if let Some(member) = self.members.get(&self.identity.id()) {
member.permissions.contains(permission)
} else {
false
}
}
pub fn add_member(&mut self, member_key: PublicKey, role: MemberRole) -> Result<()> {
if !self.has_permission(&GroupPermission::InviteMembers) {
return Err(PhalanxError::membership("No permission to add members"));
}
if self.banned_members.contains_key(&member_key.id()) {
return Err(PhalanxError::membership("Cannot add banned member"));
}
if self.members.len() >= self.config.max_members {
return Err(PhalanxError::group("Group is at maximum capacity"));
}
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let member = GroupMember {
public_key: member_key.clone(),
ephemeral_key: None,
role: role.clone(),
joined_at: timestamp,
last_seen: None,
nickname: None,
status: MemberStatus::Active,
permissions: Self::default_permissions_for_role(&role),
presence: MemberPresence::Online,
};
let member_id = member_key.id();
self.members.insert(member_id, member);
self.audit_log.push(AuditLogEntry {
timestamp,
actor: self.identity.id(),
action: AuditAction::MemberJoined,
target: Some(member_id),
details: Some(format!("Added with role: {:?}", role)),
});
Ok(())
}
pub fn remove_member(&mut self, member_id: &[u8; 32]) -> Result<()> {
if self.config.admin_only_operations.contains(&GroupOperation::RemoveMember) && !self.is_admin() {
return Err(PhalanxError::membership("Only admins can remove members"));
}
if member_id == &self.identity.id() {
return Err(PhalanxError::membership("Cannot remove yourself"));
}
if let Some(member) = self.members.get(member_id) {
if member.role == MemberRole::Owner {
return Err(PhalanxError::membership("Cannot remove group owner"));
}
}
self.members.remove(member_id);
self.rotate_keys()?;
Ok(())
}
pub fn ban_member(&mut self, member_id: &[u8; 32], reason: Option<String>, duration: Option<u64>) -> Result<()> {
if !self.has_permission(&GroupPermission::ManageBans) {
return Err(PhalanxError::membership("No permission to ban members"));
}
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
self.members.remove(member_id);
let ban_entry = BanEntry {
banned_at: timestamp,
banned_by: self.identity.id(),
reason: reason.clone(),
expires_at: duration.map(|d| timestamp + d),
ban_type: BanType::Full,
};
self.banned_members.insert(*member_id, ban_entry);
self.audit_log.push(AuditLogEntry {
timestamp,
actor: self.identity.id(),
action: AuditAction::MemberBanned,
target: Some(*member_id),
details: reason,
});
Ok(())
}
pub fn unban_member(&mut self, member_id: &[u8; 32]) -> Result<()> {
if !self.has_permission(&GroupPermission::ManageBans) {
return Err(PhalanxError::membership("No permission to unban members"));
}
self.banned_members.remove(member_id);
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
self.audit_log.push(AuditLogEntry {
timestamp,
actor: self.identity.id(),
action: AuditAction::MemberUnbanned,
target: Some(*member_id),
details: None,
});
Ok(())
}
pub fn change_member_role(&mut self, member_id: &[u8; 32], new_role: MemberRole) -> Result<()> {
if !self.has_permission(&GroupPermission::ManageRoles) {
return Err(PhalanxError::membership("No permission to change member roles"));
}
let member = self.members.get_mut(member_id)
.ok_or_else(|| PhalanxError::membership("Member not found"))?;
let old_role = member.role.clone();
member.role = new_role.clone();
member.permissions = Self::default_permissions_for_role(&new_role);
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
self.audit_log.push(AuditLogEntry {
timestamp,
actor: self.identity.id(),
action: AuditAction::RoleChanged,
target: Some(*member_id),
details: Some(format!("{:?} -> {:?}", old_role, new_role)),
});
Ok(())
}
pub fn set_member_permissions(&mut self, member_id: &[u8; 32], permissions: Vec<GroupPermission>) -> Result<()> {
if !self.has_permission(&GroupPermission::ManageRoles) {
return Err(PhalanxError::membership("No permission to set member permissions"));
}
let member = self.members.get_mut(member_id)
.ok_or_else(|| PhalanxError::membership("Member not found"))?;
member.permissions = permissions;
Ok(())
}
pub fn create_channel(&mut self, channel_id: String, name: String, description: Option<String>) -> Result<()> {
if !self.has_permission(&GroupPermission::CreateChannels) {
return Err(PhalanxError::membership("No permission to create channels"));
}
if self.channels.contains_key(&channel_id) {
return Err(PhalanxError::group("Channel already exists"));
}
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let channel = GroupChannel {
id: channel_id.clone(),
name,
description,
members: HashSet::new(),
permissions: HashMap::new(),
archived: false,
created_at: timestamp,
last_activity: None,
};
let channel_id_for_audit = channel_id.clone();
self.channels.insert(channel_id, channel);
self.audit_log.push(AuditLogEntry {
timestamp,
actor: self.identity.id(),
action: AuditAction::ChannelCreated,
target: None,
details: Some(format!("Channel: {}", channel_id_for_audit)),
});
Ok(())
}
pub fn add_member_to_channel(&mut self, channel_id: &str, member_id: &[u8; 32]) -> Result<()> {
if !self.has_permission(&GroupPermission::ManageRoles) {
return Err(PhalanxError::membership("No permission to manage channel membership"));
}
if !self.members.contains_key(member_id) {
return Err(PhalanxError::membership("Member not found in group"));
}
let channel = self.channels.get_mut(channel_id)
.ok_or_else(|| PhalanxError::group("Channel not found"))?;
channel.members.insert(*member_id);
Ok(())
}
pub fn set_presence(&mut self, presence: MemberPresence) -> Result<()> {
let member = self.members.get_mut(&self.identity.id())
.ok_or_else(|| PhalanxError::membership("Member not found"))?;
member.presence = presence;
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
member.last_seen = Some(timestamp);
Ok(())
}
pub fn get_audit_log(&self) -> Result<&Vec<AuditLogEntry>> {
if !self.has_permission(&GroupPermission::ViewAuditLogs) {
return Err(PhalanxError::membership("No permission to view audit logs"));
}
Ok(&self.audit_log)
}
pub fn advanced_key_rotation(&mut self) -> Result<()> {
if !self.has_permission(&GroupPermission::RotateKeys) {
return Err(PhalanxError::membership("No permission to rotate keys"));
}
self.group_key = SymmetricKey::generate();
self.key_sequence += 1;
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
self.last_key_rotation = timestamp;
self.audit_log.push(AuditLogEntry {
timestamp,
actor: self.identity.id(),
action: AuditAction::KeysRotated,
target: None,
details: Some(format!("Advanced rotation to version {}", self.key_sequence)),
});
Ok(())
}
pub fn schedule_key_rotation(&mut self, _policy: RotationPolicy) -> Result<()> {
if !self.has_permission(&GroupPermission::RotateKeys) {
return Err(PhalanxError::membership("No permission to schedule key rotation"));
}
Ok(())
}
pub fn get_health_metrics(&self) -> GroupHealthMetrics {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let active_members = self.members.values()
.filter(|m| {
if let Some(last_seen) = m.last_seen {
timestamp - last_seen < 3600 } else {
false
}
})
.count();
let banned_count = self.banned_members.len();
let channel_count = self.channels.len();
let key_rotation_health = if self.needs_key_rotation() {
HealthStatus::Warning
} else {
HealthStatus::Healthy
};
GroupHealthMetrics {
total_members: self.members.len(),
active_members,
banned_members: banned_count,
channels: channel_count,
key_rotation_health,
last_activity: timestamp,
}
}
pub fn encrypt_message(&mut self, content: &MessageContent) -> Result<GroupMessage> {
self.message_sequence += 1;
GroupMessage::new(
&self.identity,
MessageType::Text,
content,
self.message_sequence,
&self.group_key,
)
}
pub fn decrypt_message(&self, message: &GroupMessage) -> Result<MessageContent> {
let sender_id = message.sender.id();
if !self.members.contains_key(&sender_id) {
return Err(PhalanxError::membership("Message from non-member"));
}
message.decrypt(&self.group_key)
}
pub fn rotate_keys(&mut self) -> Result<KeyRotationMessage> {
if self.config.admin_only_operations.contains(&GroupOperation::RotateKeys) && !self.is_admin() {
return Err(PhalanxError::membership("Only admins can rotate keys"));
}
self.group_key = SymmetricKey::generate();
self.key_sequence += 1;
self.last_key_rotation = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let mut member_keys = Vec::new();
for member in self.members.values_mut() {
if let Some(ephemeral) = &member.ephemeral_key {
member_keys.push((member.public_key.clone(), *ephemeral));
}
}
KeyRotationMessage::new(&self.identity, self.key_sequence, member_keys)
}
pub fn needs_key_rotation(&self) -> bool {
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
current_time - self.last_key_rotation >= self.config.key_rotation_interval
}
pub fn update_member_activity(&mut self, member_id: &[u8; 32]) {
if let Some(member) = self.members.get_mut(member_id) {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
member.last_seen = Some(timestamp);
}
}
pub fn set_member_nickname(&mut self, member_id: &[u8; 32], nickname: Option<String>) -> Result<()> {
let member = self.members.get_mut(member_id)
.ok_or_else(|| PhalanxError::membership("Member not found"))?;
member.nickname = nickname;
Ok(())
}
fn generate_group_id(creator_key: &PublicKey) -> [u8; 32] {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
.to_be_bytes();
hash_multiple(&[
&creator_key.id(),
×tamp,
b"PHALANX_GROUP_V1",
])
}
pub fn create_handshake(&self) -> Result<HandshakeMessage> {
HandshakeMessage::new(
&self.identity,
self.group_id,
vec!["phalanx/v1".to_string()],
"phalanx-client/1.0".to_string(),
)
}
pub fn create_handshake_with_group_key(&mut self, recipient_public_key: &PublicKey) -> Result<HandshakeMessage> {
HandshakeMessage::new_with_group_key(
&mut self.identity,
recipient_public_key,
self.group_id,
vec!["phalanx/v1".to_string()],
"phalanx-client/1.0".to_string(),
&self.group_key,
)
}
pub fn join_with_handshake(
mut identity: Identity,
handshake: HandshakeMessage,
) -> Result<Self> {
let group_key = handshake.extract_group_key(&mut identity)?
.ok_or_else(|| PhalanxError::protocol("No group key in handshake message"))?;
Self::join(identity, handshake, group_key)
}
pub fn stats(&self) -> GroupStats {
let active_members = self.members.values()
.filter(|m| {
if let Some(last_seen) = m.last_seen {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
now - last_seen < 3600 } else {
false
}
})
.count();
GroupStats {
total_members: self.members.len(),
active_members,
message_count: self.message_sequence,
key_rotation_count: self.key_sequence,
created_at: self.members.get(&self.identity.id())
.map(|m| m.joined_at)
.unwrap_or(0),
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GroupStats {
pub total_members: usize,
pub active_members: usize,
pub message_count: u64,
pub key_rotation_count: u64,
pub created_at: u64,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GroupHealthMetrics {
pub total_members: usize,
pub active_members: usize,
pub banned_members: usize,
pub channels: usize,
pub key_rotation_health: HealthStatus,
pub last_activity: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum HealthStatus {
Healthy,
Warning,
Critical,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::message::MessageContent;
#[test]
fn test_group_creation() {
let identity = Identity::generate();
let group = PhalanxGroup::new(identity.clone());
assert_eq!(group.members.len(), 1);
assert!(group.members.contains_key(&identity.id()));
assert!(group.is_admin()); }
#[test]
fn test_message_encryption_decryption() {
let identity = Identity::generate();
let mut group = PhalanxGroup::new(identity);
let content = MessageContent::text("Hello, group!");
let encrypted = group.encrypt_message(&content).unwrap();
let decrypted = group.decrypt_message(&encrypted).unwrap();
assert_eq!(decrypted.as_string().unwrap(), "Hello, group!");
}
#[test]
fn test_member_management() {
let admin = Identity::generate();
let member = Identity::generate();
let mut group = PhalanxGroup::new(admin);
group.add_member(member.public_key(), MemberRole::Member).unwrap();
assert_eq!(group.members.len(), 2);
group.remove_member(&member.id()).unwrap();
assert_eq!(group.members.len(), 1);
}
#[test]
fn test_key_rotation() {
let identity = Identity::generate();
let mut group = PhalanxGroup::new(identity);
let old_sequence = group.key_sequence;
let rotation_msg = group.rotate_keys().unwrap();
assert_eq!(group.key_sequence, old_sequence + 1);
assert!(rotation_msg.verify(&group.identity.public_key()).is_ok());
}
#[test]
fn test_permissions() {
let admin = Identity::generate();
let member = Identity::generate();
let mut group = PhalanxGroup::new(admin);
group.add_member(member.public_key(), MemberRole::Member).unwrap();
group.identity = member;
let new_member = Identity::generate();
let result = group.add_member(new_member.public_key(), MemberRole::Member);
assert!(result.is_err());
}
}