1use crate::dkg::DkgParams;
41use serde::{Deserialize, Serialize};
42use std::collections::HashMap;
43use std::time::SystemTime;
44use thiserror::Error;
45
46#[derive(Debug, Error)]
48pub enum CeremonyError {
49 #[error("Invalid ceremony configuration: {0}")]
50 InvalidConfig(String),
51
52 #[error("Ceremony not in correct state: expected {expected}, got {actual}")]
53 InvalidState { expected: String, actual: String },
54
55 #[error("Participant not found: {0}")]
56 ParticipantNotFound(String),
57
58 #[error("Participant already exists: {0}")]
59 ParticipantAlreadyExists(String),
60
61 #[error("Invalid participant count: expected {expected}, got {actual}")]
62 InvalidParticipantCount { expected: usize, actual: usize },
63
64 #[error("Round {0} not yet complete")]
65 RoundIncomplete(usize),
66
67 #[error("Ceremony timeout: {0}")]
68 Timeout(String),
69
70 #[error("DKG error: {0}")]
71 DkgError(String),
72
73 #[error("Serialization error: {0}")]
74 SerializationError(String),
75
76 #[error("Verification failed: {0}")]
77 VerificationFailed(String),
78}
79
80pub type CeremonyResult<T> = Result<T, CeremonyError>;
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
85pub enum CeremonyState {
86 Setup,
88 WaitingForParticipants,
90 InProgress,
92 Completed,
94 Aborted,
96 Failed,
98}
99
100impl std::fmt::Display for CeremonyState {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 match self {
103 Self::Setup => write!(f, "Setup"),
104 Self::WaitingForParticipants => write!(f, "WaitingForParticipants"),
105 Self::InProgress => write!(f, "InProgress"),
106 Self::Completed => write!(f, "Completed"),
107 Self::Aborted => write!(f, "Aborted"),
108 Self::Failed => write!(f, "Failed"),
109 }
110 }
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct ParticipantInfo {
116 pub id: String,
118 pub index: usize,
120 pub joined_at: u64,
122 pub ready: bool,
124 pub metadata: HashMap<String, String>,
126}
127
128impl ParticipantInfo {
129 pub fn new(id: String, index: usize) -> Self {
131 Self {
132 id,
133 index,
134 joined_at: SystemTime::now()
135 .duration_since(SystemTime::UNIX_EPOCH)
136 .unwrap_or_default()
137 .as_secs(),
138 ready: false,
139 metadata: HashMap::new(),
140 }
141 }
142
143 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
145 self.metadata.insert(key.into(), value.into());
146 self
147 }
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct CeremonyConfig {
153 pub total_participants: usize,
155 pub threshold: usize,
157 pub ceremony_id: String,
159 pub timeout_seconds: u64,
161 pub require_ready_check: bool,
163 pub metadata: HashMap<String, String>,
165}
166
167impl CeremonyConfig {
168 pub fn new(total_participants: usize, threshold: usize, ceremony_id: String) -> Self {
170 Self {
171 total_participants,
172 threshold,
173 ceremony_id,
174 timeout_seconds: 300, require_ready_check: true,
176 metadata: HashMap::new(),
177 }
178 }
179
180 pub fn with_timeout(mut self, seconds: u64) -> Self {
182 self.timeout_seconds = seconds;
183 self
184 }
185
186 pub fn without_ready_check(mut self) -> Self {
188 self.require_ready_check = false;
189 self
190 }
191
192 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
194 self.metadata.insert(key.into(), value.into());
195 self
196 }
197
198 pub fn validate(&self) -> CeremonyResult<()> {
200 if self.total_participants < 2 {
201 return Err(CeremonyError::InvalidConfig(
202 "At least 2 participants required".to_string(),
203 ));
204 }
205
206 if self.threshold < 1 || self.threshold > self.total_participants {
207 return Err(CeremonyError::InvalidConfig(format!(
208 "Threshold must be between 1 and {}, got {}",
209 self.total_participants, self.threshold
210 )));
211 }
212
213 if self.ceremony_id.is_empty() {
214 return Err(CeremonyError::InvalidConfig(
215 "Ceremony ID cannot be empty".to_string(),
216 ));
217 }
218
219 Ok(())
220 }
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct CeremonyAttestation {
226 pub ceremony_id: String,
228 pub config: CeremonyConfig,
230 pub participants: Vec<ParticipantInfo>,
232 pub started_at: u64,
234 pub completed_at: Option<u64>,
236 pub final_state: CeremonyState,
238 pub audit_log: Vec<String>,
240}
241
242impl CeremonyAttestation {
243 pub fn new(ceremony_id: String, config: CeremonyConfig) -> Self {
245 Self {
246 ceremony_id,
247 config,
248 participants: Vec::new(),
249 started_at: SystemTime::now()
250 .duration_since(SystemTime::UNIX_EPOCH)
251 .unwrap_or_default()
252 .as_secs(),
253 completed_at: None,
254 final_state: CeremonyState::Setup,
255 audit_log: Vec::new(),
256 }
257 }
258
259 pub fn log(&mut self, entry: impl Into<String>) {
261 self.audit_log.push(entry.into());
262 }
263
264 pub fn complete(&mut self, state: CeremonyState) {
266 self.completed_at = Some(
267 SystemTime::now()
268 .duration_since(SystemTime::UNIX_EPOCH)
269 .unwrap_or_default()
270 .as_secs(),
271 );
272 self.final_state = state;
273 }
274}
275
276pub struct KeygenCeremony {
278 config: CeremonyConfig,
279 state: CeremonyState,
280 participants: HashMap<String, ParticipantInfo>,
281 dkg_params: Option<DkgParams>,
282 current_round: usize,
283 attestation: CeremonyAttestation,
284}
285
286impl KeygenCeremony {
287 pub fn new(config: CeremonyConfig) -> CeremonyResult<Self> {
289 config.validate()?;
290
291 let attestation = CeremonyAttestation::new(config.ceremony_id.clone(), config.clone());
292
293 Ok(Self {
294 config,
295 state: CeremonyState::Setup,
296 participants: HashMap::new(),
297 dkg_params: None,
298 current_round: 0,
299 attestation,
300 })
301 }
302
303 pub fn state(&self) -> CeremonyState {
305 self.state
306 }
307
308 pub fn ceremony_id(&self) -> &str {
310 &self.config.ceremony_id
311 }
312
313 pub fn add_participant(&mut self, id: String) -> CeremonyResult<String> {
315 if self.state != CeremonyState::Setup && self.state != CeremonyState::WaitingForParticipants
317 {
318 return Err(CeremonyError::InvalidState {
319 expected: "Setup or WaitingForParticipants".to_string(),
320 actual: self.state.to_string(),
321 });
322 }
323
324 if self.participants.contains_key(&id) {
326 return Err(CeremonyError::ParticipantAlreadyExists(id));
327 }
328
329 if self.participants.len() >= self.config.total_participants {
331 return Err(CeremonyError::InvalidParticipantCount {
332 expected: self.config.total_participants,
333 actual: self.participants.len() + 1,
334 });
335 }
336
337 let index = self.participants.len();
338 let participant = ParticipantInfo::new(id.clone(), index);
339
340 self.participants.insert(id.clone(), participant.clone());
341 self.attestation.participants.push(participant);
342 self.attestation
343 .log(format!("Participant {} joined (index {})", id, index));
344
345 if self.state == CeremonyState::Setup {
347 self.state = CeremonyState::WaitingForParticipants;
348 }
349
350 Ok(id)
351 }
352
353 pub fn mark_ready(&mut self, participant_id: &str) -> CeremonyResult<()> {
355 let participant = self
356 .participants
357 .get_mut(participant_id)
358 .ok_or_else(|| CeremonyError::ParticipantNotFound(participant_id.to_string()))?;
359
360 participant.ready = true;
361 self.attestation
362 .log(format!("Participant {} marked ready", participant_id));
363
364 Ok(())
365 }
366
367 pub fn all_ready(&self) -> bool {
369 if self.participants.len() != self.config.total_participants {
370 return false;
371 }
372
373 if self.config.require_ready_check {
374 self.participants.values().all(|p| p.ready)
375 } else {
376 true
377 }
378 }
379
380 pub fn start(&mut self) -> CeremonyResult<()> {
382 if self.state != CeremonyState::WaitingForParticipants {
384 return Err(CeremonyError::InvalidState {
385 expected: "WaitingForParticipants".to_string(),
386 actual: self.state.to_string(),
387 });
388 }
389
390 if !self.all_ready() {
392 return Err(CeremonyError::InvalidState {
393 expected: "All participants ready".to_string(),
394 actual: format!(
395 "{}/{} participants ready",
396 self.participants.values().filter(|p| p.ready).count(),
397 self.config.total_participants
398 ),
399 });
400 }
401
402 let params = DkgParams::new(self.config.total_participants, self.config.threshold);
404
405 self.dkg_params = Some(params);
406 self.state = CeremonyState::InProgress;
407 self.current_round = 1;
408 self.attestation.log("Ceremony started".to_string());
409
410 Ok(())
411 }
412
413 pub fn abort(&mut self, reason: impl Into<String>) {
415 let reason = reason.into();
416 self.state = CeremonyState::Aborted;
417 self.attestation
418 .log(format!("Ceremony aborted: {}", reason));
419 self.attestation.complete(CeremonyState::Aborted);
420 }
421
422 pub fn complete(&mut self) -> CeremonyResult<()> {
424 if self.state != CeremonyState::InProgress {
425 return Err(CeremonyError::InvalidState {
426 expected: "InProgress".to_string(),
427 actual: self.state.to_string(),
428 });
429 }
430
431 self.state = CeremonyState::Completed;
432 self.attestation
433 .log("Ceremony completed successfully".to_string());
434 self.attestation.complete(CeremonyState::Completed);
435
436 Ok(())
437 }
438
439 pub fn attestation(&self) -> &CeremonyAttestation {
441 &self.attestation
442 }
443
444 pub fn get_participant(&self, id: &str) -> Option<&ParticipantInfo> {
446 self.participants.get(id)
447 }
448
449 pub fn list_participants(&self) -> Vec<&ParticipantInfo> {
451 self.participants.values().collect()
452 }
453
454 pub fn current_round(&self) -> usize {
456 self.current_round
457 }
458
459 pub fn dkg_params(&self) -> Option<&DkgParams> {
461 self.dkg_params.as_ref()
462 }
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468
469 #[test]
470 fn test_ceremony_config_validation() {
471 let config = CeremonyConfig::new(3, 2, "test".to_string());
473 assert!(config.validate().is_ok());
474
475 let config = CeremonyConfig::new(1, 1, "test".to_string());
477 assert!(config.validate().is_err());
478
479 let config = CeremonyConfig::new(3, 4, "test".to_string());
481 assert!(config.validate().is_err());
482
483 let config = CeremonyConfig::new(3, 0, "test".to_string());
485 assert!(config.validate().is_err());
486
487 let config = CeremonyConfig::new(3, 2, "".to_string());
489 assert!(config.validate().is_err());
490 }
491
492 #[test]
493 fn test_ceremony_lifecycle() {
494 let config = CeremonyConfig::new(3, 2, "test-ceremony".to_string());
495 let mut ceremony = KeygenCeremony::new(config).unwrap();
496
497 assert_eq!(ceremony.state(), CeremonyState::Setup);
498
499 let p1 = ceremony.add_participant("alice".to_string()).unwrap();
501 assert_eq!(ceremony.state(), CeremonyState::WaitingForParticipants);
502
503 let p2 = ceremony.add_participant("bob".to_string()).unwrap();
504 let p3 = ceremony.add_participant("charlie".to_string()).unwrap();
505
506 let result = ceremony.add_participant("dave".to_string());
508 assert!(result.is_err());
509
510 ceremony.mark_ready(&p1).unwrap();
512 ceremony.mark_ready(&p2).unwrap();
513 ceremony.mark_ready(&p3).unwrap();
514
515 assert!(ceremony.all_ready());
516
517 ceremony.start().unwrap();
519 assert_eq!(ceremony.state(), CeremonyState::InProgress);
520 assert_eq!(ceremony.current_round(), 1);
521
522 ceremony.complete().unwrap();
524 assert_eq!(ceremony.state(), CeremonyState::Completed);
525 }
526
527 #[test]
528 fn test_ceremony_abort() {
529 let config = CeremonyConfig::new(2, 2, "abort-test".to_string());
530 let mut ceremony = KeygenCeremony::new(config).unwrap();
531
532 ceremony.add_participant("alice".to_string()).unwrap();
533 ceremony.abort("Testing abort");
534
535 assert_eq!(ceremony.state(), CeremonyState::Aborted);
536
537 let attestation = ceremony.attestation();
538 assert_eq!(attestation.final_state, CeremonyState::Aborted);
539 assert!(attestation.completed_at.is_some());
540 }
541
542 #[test]
543 fn test_participant_management() {
544 let config = CeremonyConfig::new(3, 2, "participants-test".to_string());
545 let mut ceremony = KeygenCeremony::new(config).unwrap();
546
547 ceremony.add_participant("alice".to_string()).unwrap();
549 ceremony.add_participant("bob".to_string()).unwrap();
550
551 let alice = ceremony.get_participant("alice").unwrap();
553 assert_eq!(alice.id, "alice");
554 assert_eq!(alice.index, 0);
555 assert!(!alice.ready);
556
557 let participants = ceremony.list_participants();
559 assert_eq!(participants.len(), 2);
560
561 assert!(ceremony.get_participant("charlie").is_none());
563 }
564
565 #[test]
566 fn test_ready_check() {
567 let config = CeremonyConfig::new(2, 2, "ready-test".to_string());
568 let mut ceremony = KeygenCeremony::new(config).unwrap();
569
570 ceremony.add_participant("alice".to_string()).unwrap();
571 ceremony.add_participant("bob".to_string()).unwrap();
572
573 assert!(!ceremony.all_ready());
574
575 ceremony.mark_ready("alice").unwrap();
576 assert!(!ceremony.all_ready());
577
578 ceremony.mark_ready("bob").unwrap();
579 assert!(ceremony.all_ready());
580 }
581
582 #[test]
583 fn test_ready_check_disabled() {
584 let config = CeremonyConfig::new(2, 2, "no-ready-test".to_string()).without_ready_check();
585 let mut ceremony = KeygenCeremony::new(config).unwrap();
586
587 ceremony.add_participant("alice".to_string()).unwrap();
588 ceremony.add_participant("bob".to_string()).unwrap();
589
590 assert!(ceremony.all_ready());
592 }
593
594 #[test]
595 fn test_attestation() {
596 let config = CeremonyConfig::new(2, 2, "attestation-test".to_string());
597 let mut ceremony = KeygenCeremony::new(config).unwrap();
598
599 ceremony.add_participant("alice".to_string()).unwrap();
600 ceremony.add_participant("bob".to_string()).unwrap();
601 ceremony.mark_ready("alice").unwrap();
602 ceremony.mark_ready("bob").unwrap();
603 ceremony.start().unwrap();
604 ceremony.complete().unwrap();
605
606 let attestation = ceremony.attestation();
607 assert_eq!(attestation.ceremony_id, "attestation-test");
608 assert_eq!(attestation.participants.len(), 2);
609 assert_eq!(attestation.final_state, CeremonyState::Completed);
610 assert!(attestation.completed_at.is_some());
611 assert!(!attestation.audit_log.is_empty());
612 }
613
614 #[test]
615 fn test_dkg_params_initialization() {
616 let config = CeremonyConfig::new(3, 2, "dkg-test".to_string());
617 let mut ceremony = KeygenCeremony::new(config).unwrap();
618
619 assert!(ceremony.dkg_params().is_none());
621
622 ceremony.add_participant("alice".to_string()).unwrap();
623 ceremony.add_participant("bob".to_string()).unwrap();
624 ceremony.add_participant("charlie".to_string()).unwrap();
625 ceremony.mark_ready("alice").unwrap();
626 ceremony.mark_ready("bob").unwrap();
627 ceremony.mark_ready("charlie").unwrap();
628
629 ceremony.start().unwrap();
630
631 let params = ceremony.dkg_params().unwrap();
633 assert_eq!(params.threshold, 2);
634 assert_eq!(params.total_parties, 3);
635 }
636
637 #[test]
638 fn test_ceremony_state_transitions() {
639 let config = CeremonyConfig::new(2, 2, "state-test".to_string());
640 let mut ceremony = KeygenCeremony::new(config).unwrap();
641
642 assert_eq!(ceremony.state(), CeremonyState::Setup);
644 ceremony.add_participant("alice".to_string()).unwrap();
645 assert_eq!(ceremony.state(), CeremonyState::WaitingForParticipants);
646
647 let result = ceremony.start();
649 assert!(result.is_err());
650
651 ceremony.add_participant("bob".to_string()).unwrap();
652 ceremony.mark_ready("alice").unwrap();
653 ceremony.mark_ready("bob").unwrap();
654
655 ceremony.start().unwrap();
657 assert_eq!(ceremony.state(), CeremonyState::InProgress);
658
659 ceremony.complete().unwrap();
661 assert_eq!(ceremony.state(), CeremonyState::Completed);
662 }
663
664 #[test]
665 fn test_config_builder() {
666 let config = CeremonyConfig::new(5, 3, "builder-test".to_string())
667 .with_timeout(600)
668 .without_ready_check()
669 .with_metadata("purpose", "testing")
670 .with_metadata("version", "1.0");
671
672 assert_eq!(config.timeout_seconds, 600);
673 assert!(!config.require_ready_check);
674 assert_eq!(config.metadata.get("purpose"), Some(&"testing".to_string()));
675 assert_eq!(config.metadata.get("version"), Some(&"1.0".to_string()));
676 }
677
678 #[test]
679 fn test_participant_info_metadata() {
680 let participant = ParticipantInfo::new("alice".to_string(), 0)
681 .with_metadata("role", "coordinator")
682 .with_metadata("location", "US");
683
684 assert_eq!(participant.id, "alice");
685 assert_eq!(participant.index, 0);
686 assert_eq!(
687 participant.metadata.get("role"),
688 Some(&"coordinator".to_string())
689 );
690 assert_eq!(
691 participant.metadata.get("location"),
692 Some(&"US".to_string())
693 );
694 }
695
696 #[test]
697 fn test_duplicate_participant() {
698 let config = CeremonyConfig::new(3, 2, "dup-test".to_string());
699 let mut ceremony = KeygenCeremony::new(config).unwrap();
700
701 ceremony.add_participant("alice".to_string()).unwrap();
702
703 let result = ceremony.add_participant("alice".to_string());
705 assert!(matches!(
706 result,
707 Err(CeremonyError::ParticipantAlreadyExists(_))
708 ));
709 }
710
711 #[test]
712 fn test_mark_ready_nonexistent_participant() {
713 let config = CeremonyConfig::new(2, 2, "nonexistent-test".to_string());
714 let mut ceremony = KeygenCeremony::new(config).unwrap();
715
716 let result = ceremony.mark_ready("alice");
717 assert!(matches!(result, Err(CeremonyError::ParticipantNotFound(_))));
718 }
719}