1use super::*;
17use crate::quantum_crypto::types::*;
18use std::collections::HashSet;
19
20impl ThresholdGroup {
21 pub fn check_permission(
23 &self,
24 participant_id: &ParticipantId,
25 permission: Permission,
26 ) -> Result<()> {
27 let participant = self
28 .active_participants
29 .iter()
30 .find(|p| &p.participant_id == participant_id)
31 .ok_or_else(|| ThresholdError::ParticipantNotFound(participant_id.clone()))?;
32
33 match (&participant.role, permission) {
34 (ParticipantRole::Leader { permissions }, Permission::AddParticipant) => {
35 if permissions.can_add_participants {
36 Ok(())
37 } else {
38 Err(ThresholdError::Unauthorized(
39 "Cannot add participants".to_string(),
40 ))
41 }
42 }
43 (ParticipantRole::Leader { permissions }, Permission::RemoveParticipant) => {
44 if permissions.can_remove_participants {
45 Ok(())
46 } else {
47 Err(ThresholdError::Unauthorized(
48 "Cannot remove participants".to_string(),
49 ))
50 }
51 }
52 (ParticipantRole::Leader { permissions }, Permission::UpdateThreshold) => {
53 if permissions.can_update_threshold {
54 Ok(())
55 } else {
56 Err(ThresholdError::Unauthorized(
57 "Cannot update threshold".to_string(),
58 ))
59 }
60 }
61 (ParticipantRole::Member { permissions }, Permission::Sign) => {
62 if permissions.can_sign {
63 Ok(())
64 } else {
65 Err(ThresholdError::Unauthorized("Cannot sign".to_string()))
66 }
67 }
68 (ParticipantRole::Observer, _) => Err(ThresholdError::Unauthorized(
69 "Observers have read-only access".to_string(),
70 )),
71 _ => Err(ThresholdError::Unauthorized(
72 "Permission denied".to_string(),
73 )),
74 }
75 }
76
77 pub fn get_active_participants(&self) -> Vec<&ParticipantInfo> {
79 self.active_participants
80 .iter()
81 .filter(|p| matches!(p.status, ParticipantStatus::Active))
82 .collect()
83 }
84
85 pub fn active_participant_count(&self) -> u16 {
87 self.get_active_participants().len() as u16
88 }
89
90 pub fn has_threshold_participants(&self) -> bool {
92 self.active_participant_count() >= self.threshold
93 }
94
95 pub fn add_pending_participant(&mut self, participant: ParticipantInfo) -> Result<()> {
97 if self
99 .active_participants
100 .iter()
101 .any(|p| p.participant_id == participant.participant_id)
102 {
103 return Err(ThresholdError::InvalidParameters(
104 "Participant already exists".to_string(),
105 ));
106 }
107
108 if self
109 .pending_participants
110 .iter()
111 .any(|p| p.participant_id == participant.participant_id)
112 {
113 return Err(ThresholdError::InvalidParameters(
114 "Participant already pending".to_string(),
115 ));
116 }
117
118 self.pending_participants.push(participant);
119 self.version += 1;
120 self.last_updated = SystemTime::now();
121
122 Ok(())
123 }
124
125 pub fn mark_for_removal(&mut self, participant_id: &ParticipantId) -> Result<()> {
127 let participant = self
128 .active_participants
129 .iter_mut()
130 .find(|p| &p.participant_id == participant_id)
131 .ok_or_else(|| ThresholdError::ParticipantNotFound(participant_id.clone()))?;
132
133 participant.status = ParticipantStatus::PendingRemoval;
134 self.version += 1;
135 self.last_updated = SystemTime::now();
136
137 if self.active_participant_count() < self.threshold {
139 return Err(ThresholdError::InsufficientParticipants {
140 required: self.threshold,
141 available: self.active_participant_count(),
142 });
143 }
144
145 Ok(())
146 }
147
148 pub fn update_participant_role(
150 &mut self,
151 participant_id: &ParticipantId,
152 new_role: ParticipantRole,
153 ) -> Result<()> {
154 let participant = self
155 .active_participants
156 .iter_mut()
157 .find(|p| &p.participant_id == participant_id)
158 .ok_or_else(|| ThresholdError::ParticipantNotFound(participant_id.clone()))?;
159
160 participant.role = new_role;
161 self.version += 1;
162 self.last_updated = SystemTime::now();
163
164 Ok(())
165 }
166
167 pub fn suspend_participant(
169 &mut self,
170 participant_id: &ParticipantId,
171 reason: String,
172 duration: std::time::Duration,
173 ) -> Result<()> {
174 let participant = self
175 .active_participants
176 .iter_mut()
177 .find(|p| &p.participant_id == participant_id)
178 .ok_or_else(|| ThresholdError::ParticipantNotFound(participant_id.clone()))?;
179
180 participant.status = ParticipantStatus::Suspended {
181 reason,
182 until: SystemTime::now() + duration,
183 };
184
185 self.version += 1;
186 self.last_updated = SystemTime::now();
187
188 if self.active_participant_count() < self.threshold {
190 return Err(ThresholdError::InsufficientParticipants {
191 required: self.threshold,
192 available: self.active_participant_count(),
193 });
194 }
195
196 Ok(())
197 }
198
199 pub fn update_threshold(&mut self, new_threshold: u16) -> Result<()> {
201 if new_threshold == 0 {
202 return Err(ThresholdError::InvalidParameters(
203 "Threshold must be at least 1".to_string(),
204 ));
205 }
206
207 if new_threshold > self.participants {
208 return Err(ThresholdError::InvalidParameters(
209 "Threshold cannot exceed total participants".to_string(),
210 ));
211 }
212
213 if new_threshold > self.active_participant_count() {
214 return Err(ThresholdError::InvalidParameters(
215 "Threshold cannot exceed active participants".to_string(),
216 ));
217 }
218
219 self.threshold = new_threshold;
220 self.version += 1;
221 self.last_updated = SystemTime::now();
222
223 Ok(())
224 }
225
226 pub fn get_participants_by_role(&self, role_filter: RoleFilter) -> Vec<&ParticipantInfo> {
228 self.active_participants
229 .iter()
230 .filter(|p| {
231 matches!(
232 (&p.role, &role_filter),
233 (ParticipantRole::Leader { .. }, RoleFilter::Leaders)
234 | (ParticipantRole::Member { .. }, RoleFilter::Members)
235 | (ParticipantRole::Observer, RoleFilter::Observers)
236 | (_, RoleFilter::All)
237 )
238 })
239 .collect()
240 }
241
242 pub fn get_hierarchy(&self) -> GroupHierarchy {
244 GroupHierarchy {
245 group_id: self.group_id.clone(),
246 parent: self.metadata.parent_group.clone(),
247 name: self.metadata.name.clone(),
248 threshold: self.threshold,
249 participants: self.participants,
250 purpose: self.metadata.purpose.clone(),
251 }
252 }
253
254 pub fn validate(&self) -> Result<()> {
256 if self.threshold == 0 {
258 return Err(ThresholdError::InvalidParameters(
259 "Invalid threshold: must be at least 1".to_string(),
260 ));
261 }
262
263 if self.threshold > self.participants {
264 return Err(ThresholdError::InvalidParameters(
265 "Invalid threshold: exceeds total participants".to_string(),
266 ));
267 }
268
269 let mut seen_ids = HashSet::new();
271 for participant in &self.active_participants {
272 if !seen_ids.insert(&participant.participant_id) {
273 return Err(ThresholdError::InvalidParameters(format!(
274 "Duplicate participant ID: {:?}",
275 participant.participant_id
276 )));
277 }
278 }
279
280 let has_leader = self
282 .active_participants
283 .iter()
284 .any(|p| matches!(p.role, ParticipantRole::Leader { .. }));
285
286 if !has_leader {
287 return Err(ThresholdError::InvalidParameters(
288 "Group must have at least one leader".to_string(),
289 ));
290 }
291
292 Ok(())
293 }
294
295 pub fn add_audit_entry(&mut self, entry: GroupAuditEntry) {
297 self.audit_log.push(entry);
298
299 if self.audit_log.len() > 1000 {
301 self.audit_log.drain(0..100);
302 }
303 }
304}
305
306#[derive(Debug, Clone, Copy, PartialEq)]
308pub enum Permission {
309 AddParticipant,
310 RemoveParticipant,
311 UpdateThreshold,
312 Sign,
313 Vote,
314 CreateSubgroup,
315 AssignRoles,
316}
317
318#[derive(Debug, Clone, PartialEq)]
320pub enum RoleFilter {
321 All,
322 Leaders,
323 Members,
324 Observers,
325}
326
327#[derive(Debug, Clone)]
329pub struct GroupHierarchy {
330 pub group_id: GroupId,
331 pub parent: Option<GroupId>,
332 pub name: String,
333 pub threshold: u16,
334 pub participants: u16,
335 pub purpose: GroupPurpose,
336}
337
338#[derive(Debug, Clone)]
340pub struct GroupStats {
341 pub total_participants: u16,
342 pub active_participants: u16,
343 pub pending_participants: u16,
344 pub suspended_participants: u16,
345 pub leaders: u16,
346 pub members: u16,
347 pub observers: u16,
348 pub total_operations: usize,
349 pub successful_operations: usize,
350 pub failed_operations: usize,
351}
352
353impl ThresholdGroup {
354 pub fn get_stats(&self) -> GroupStats {
356 let mut stats = GroupStats {
357 total_participants: self.participants,
358 active_participants: 0,
359 pending_participants: self.pending_participants.len() as u16,
360 suspended_participants: 0,
361 leaders: 0,
362 members: 0,
363 observers: 0,
364 total_operations: self.audit_log.len(),
365 successful_operations: 0,
366 failed_operations: 0,
367 };
368
369 for participant in &self.active_participants {
370 match &participant.status {
371 ParticipantStatus::Active => stats.active_participants += 1,
372 ParticipantStatus::Suspended { .. } => stats.suspended_participants += 1,
373 _ => {}
374 }
375
376 match &participant.role {
377 ParticipantRole::Leader { .. } => stats.leaders += 1,
378 ParticipantRole::Member { .. } => stats.members += 1,
379 ParticipantRole::Observer => stats.observers += 1,
380 }
381 }
382
383 for entry in &self.audit_log {
384 match &entry.result {
385 OperationResult::Success => stats.successful_operations += 1,
386 OperationResult::Failed(_) => stats.failed_operations += 1,
387 OperationResult::Pending => {}
388 }
389 }
390
391 stats
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398
399 fn create_test_group() -> ThresholdGroup {
400 let participant1 = ParticipantInfo {
401 participant_id: ParticipantId(1),
402 public_key: vec![1; 32],
403 frost_share_commitment: FrostCommitment(vec![1; 32]),
404 role: ParticipantRole::Leader {
405 permissions: LeaderPermissions::default(),
406 },
407 status: ParticipantStatus::Active,
408 joined_at: SystemTime::now(),
409 metadata: HashMap::new(),
410 };
411
412 let participant2 = ParticipantInfo {
413 participant_id: ParticipantId(2),
414 public_key: vec![2; 32],
415 frost_share_commitment: FrostCommitment(vec![2; 32]),
416 role: ParticipantRole::Member {
417 permissions: MemberPermissions::default(),
418 },
419 status: ParticipantStatus::Active,
420 joined_at: SystemTime::now(),
421 metadata: HashMap::new(),
422 };
423
424 ThresholdGroup {
425 group_id: GroupId([0; 32]),
426 threshold: 2,
427 participants: 2,
428 frost_group_key: FrostGroupPublicKey(vec![0; 32]),
429 active_participants: vec![participant1, participant2],
430 pending_participants: vec![],
431 version: 1,
432 metadata: GroupMetadata {
433 name: "Test Group".to_string(),
434 description: "Test group for unit tests".to_string(),
435 purpose: GroupPurpose::MultiSig,
436 parent_group: None,
437 custom_data: HashMap::new(),
438 },
439 audit_log: vec![],
440 created_at: SystemTime::now(),
441 last_updated: SystemTime::now(),
442 }
443 }
444
445 #[test]
446 fn test_permission_checking() {
447 let group = create_test_group();
448
449 assert!(
451 group
452 .check_permission(&ParticipantId(1), Permission::AddParticipant)
453 .is_ok()
454 );
455
456 assert!(
458 group
459 .check_permission(&ParticipantId(2), Permission::AddParticipant)
460 .is_err()
461 );
462
463 assert!(
465 group
466 .check_permission(&ParticipantId(2), Permission::Sign)
467 .is_ok()
468 );
469 }
470
471 #[test]
472 fn test_group_validation() {
473 let mut group = create_test_group();
474
475 assert!(group.validate().is_ok());
477
478 group.threshold = 0;
480 assert!(group.validate().is_err());
481
482 group.threshold = 3; assert!(group.validate().is_err());
484 }
485
486 #[test]
487 fn test_participant_management() {
488 let mut group = create_test_group();
489
490 let new_participant = ParticipantInfo {
492 participant_id: ParticipantId(3),
493 public_key: vec![3; 32],
494 frost_share_commitment: FrostCommitment(vec![3; 32]),
495 role: ParticipantRole::Member {
496 permissions: MemberPermissions::default(),
497 },
498 status: ParticipantStatus::PendingJoin,
499 joined_at: SystemTime::now(),
500 metadata: HashMap::new(),
501 };
502
503 assert!(group.add_pending_participant(new_participant).is_ok());
504 assert_eq!(group.pending_participants.len(), 1);
505
506 let duplicate = ParticipantInfo {
508 participant_id: ParticipantId(1),
509 public_key: vec![1; 32],
510 frost_share_commitment: FrostCommitment(vec![1; 32]),
511 role: ParticipantRole::Member {
512 permissions: MemberPermissions::default(),
513 },
514 status: ParticipantStatus::PendingJoin,
515 joined_at: SystemTime::now(),
516 metadata: HashMap::new(),
517 };
518
519 assert!(group.add_pending_participant(duplicate).is_err());
520 }
521}