chie_crypto/
keygen_ceremony.rs

1//! Multi-party key generation ceremony support.
2//!
3//! This module provides a high-level ceremony orchestration layer on top of
4//! the existing DKG (Distributed Key Generation) implementation. It handles:
5//!
6//! - Ceremony coordination across multiple rounds
7//! - Participant management and verification
8//! - State management and persistence
9//! - Communication message routing
10//! - Error handling and recovery
11//! - Ceremony attestation and audit trail
12//!
13//! # Example
14//!
15//! ```
16//! use chie_crypto::keygen_ceremony::*;
17//!
18//! // Create a ceremony for 3 participants with 2-of-3 threshold
19//! let mut ceremony = KeygenCeremony::new(
20//!     CeremonyConfig::new(3, 2, "test-ceremony".to_string())
21//! ).unwrap();
22//!
23//! // Each participant joins the ceremony
24//! let participant_ids: Vec<_> = (0..3)
25//!     .map(|i| ceremony.add_participant(format!("participant-{}", i)).unwrap())
26//!     .collect();
27//!
28//! // Mark all participants as ready
29//! for id in &participant_ids {
30//!     ceremony.mark_ready(id).unwrap();
31//! }
32//!
33//! // Start the ceremony
34//! ceremony.start().unwrap();
35//!
36//! // Ceremony proceeds through rounds until complete
37//! assert_eq!(ceremony.state(), CeremonyState::InProgress);
38//! ```
39
40use crate::dkg::DkgParams;
41use serde::{Deserialize, Serialize};
42use std::collections::HashMap;
43use std::time::SystemTime;
44use thiserror::Error;
45
46/// Errors that can occur during key generation ceremony.
47#[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
80/// Result type for ceremony operations.
81pub type CeremonyResult<T> = Result<T, CeremonyError>;
82
83/// Ceremony state.
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
85pub enum CeremonyState {
86    /// Ceremony is being configured
87    Setup,
88    /// Waiting for all participants to join
89    WaitingForParticipants,
90    /// Ceremony is in progress
91    InProgress,
92    /// Ceremony completed successfully
93    Completed,
94    /// Ceremony was aborted
95    Aborted,
96    /// Ceremony failed
97    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/// Participant information.
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct ParticipantInfo {
116    /// Unique participant ID
117    pub id: String,
118    /// Participant index (0-based)
119    pub index: usize,
120    /// When the participant joined (Unix timestamp)
121    pub joined_at: u64,
122    /// Whether the participant is ready
123    pub ready: bool,
124    /// Metadata
125    pub metadata: HashMap<String, String>,
126}
127
128impl ParticipantInfo {
129    /// Create new participant info
130    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    /// Add metadata
144    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/// Ceremony configuration.
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct CeremonyConfig {
153    /// Total number of participants (N)
154    pub total_participants: usize,
155    /// Threshold (M in M-of-N)
156    pub threshold: usize,
157    /// Ceremony ID
158    pub ceremony_id: String,
159    /// Timeout in seconds (0 = no timeout)
160    pub timeout_seconds: u64,
161    /// Require all participants to be ready before starting
162    pub require_ready_check: bool,
163    /// Metadata
164    pub metadata: HashMap<String, String>,
165}
166
167impl CeremonyConfig {
168    /// Create new ceremony configuration
169    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, // 5 minutes default
175            require_ready_check: true,
176            metadata: HashMap::new(),
177        }
178    }
179
180    /// Set timeout
181    pub fn with_timeout(mut self, seconds: u64) -> Self {
182        self.timeout_seconds = seconds;
183        self
184    }
185
186    /// Disable ready check
187    pub fn without_ready_check(mut self) -> Self {
188        self.require_ready_check = false;
189        self
190    }
191
192    /// Add metadata
193    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    /// Validate configuration
199    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/// Ceremony attestation record.
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct CeremonyAttestation {
226    /// Ceremony ID
227    pub ceremony_id: String,
228    /// Configuration used
229    pub config: CeremonyConfig,
230    /// Participants who participated
231    pub participants: Vec<ParticipantInfo>,
232    /// Start timestamp (Unix seconds)
233    pub started_at: u64,
234    /// Completion timestamp (Unix seconds)
235    pub completed_at: Option<u64>,
236    /// Final state
237    pub final_state: CeremonyState,
238    /// Audit log
239    pub audit_log: Vec<String>,
240}
241
242impl CeremonyAttestation {
243    /// Create new attestation
244    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    /// Add audit log entry
260    pub fn log(&mut self, entry: impl Into<String>) {
261        self.audit_log.push(entry.into());
262    }
263
264    /// Mark as completed
265    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
276/// Key generation ceremony orchestrator.
277pub 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    /// Create a new ceremony
288    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    /// Get current state
304    pub fn state(&self) -> CeremonyState {
305        self.state
306    }
307
308    /// Get ceremony ID
309    pub fn ceremony_id(&self) -> &str {
310        &self.config.ceremony_id
311    }
312
313    /// Add a participant to the ceremony
314    pub fn add_participant(&mut self, id: String) -> CeremonyResult<String> {
315        // Check if ceremony is in correct state
316        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        // Check if participant already exists
325        if self.participants.contains_key(&id) {
326            return Err(CeremonyError::ParticipantAlreadyExists(id));
327        }
328
329        // Check if we've reached the limit
330        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        // Transition to waiting state if this is the first participant
346        if self.state == CeremonyState::Setup {
347            self.state = CeremonyState::WaitingForParticipants;
348        }
349
350        Ok(id)
351    }
352
353    /// Mark a participant as ready
354    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    /// Check if all participants have joined and are ready
368    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    /// Start the ceremony
381    pub fn start(&mut self) -> CeremonyResult<()> {
382        // Check state
383        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        // Check if all participants are ready
391        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        // Initialize DKG parameters
403        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    /// Abort the ceremony
414    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    /// Mark ceremony as completed
423    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    /// Get attestation record
440    pub fn attestation(&self) -> &CeremonyAttestation {
441        &self.attestation
442    }
443
444    /// Get participant info
445    pub fn get_participant(&self, id: &str) -> Option<&ParticipantInfo> {
446        self.participants.get(id)
447    }
448
449    /// List all participants
450    pub fn list_participants(&self) -> Vec<&ParticipantInfo> {
451        self.participants.values().collect()
452    }
453
454    /// Get current round number
455    pub fn current_round(&self) -> usize {
456        self.current_round
457    }
458
459    /// Get DKG parameters
460    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        // Valid config
472        let config = CeremonyConfig::new(3, 2, "test".to_string());
473        assert!(config.validate().is_ok());
474
475        // Invalid: too few participants
476        let config = CeremonyConfig::new(1, 1, "test".to_string());
477        assert!(config.validate().is_err());
478
479        // Invalid: threshold too high
480        let config = CeremonyConfig::new(3, 4, "test".to_string());
481        assert!(config.validate().is_err());
482
483        // Invalid: threshold zero
484        let config = CeremonyConfig::new(3, 0, "test".to_string());
485        assert!(config.validate().is_err());
486
487        // Invalid: empty ceremony ID
488        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        // Add participants
500        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        // Cannot add more participants
507        let result = ceremony.add_participant("dave".to_string());
508        assert!(result.is_err());
509
510        // Mark all as ready
511        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        // Start ceremony
518        ceremony.start().unwrap();
519        assert_eq!(ceremony.state(), CeremonyState::InProgress);
520        assert_eq!(ceremony.current_round(), 1);
521
522        // Complete ceremony
523        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        // Add participants
548        ceremony.add_participant("alice".to_string()).unwrap();
549        ceremony.add_participant("bob".to_string()).unwrap();
550
551        // Get participant
552        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        // List participants
558        let participants = ceremony.list_participants();
559        assert_eq!(participants.len(), 2);
560
561        // Participant not found
562        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        // Should be ready even though no one marked ready
591        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        // DKG params should not be set until ceremony starts
620        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        // DKG params should now be set
632        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        // Setup -> WaitingForParticipants (when first participant joins)
643        assert_eq!(ceremony.state(), CeremonyState::Setup);
644        ceremony.add_participant("alice".to_string()).unwrap();
645        assert_eq!(ceremony.state(), CeremonyState::WaitingForParticipants);
646
647        // Cannot start before all ready
648        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        // WaitingForParticipants -> InProgress
656        ceremony.start().unwrap();
657        assert_eq!(ceremony.state(), CeremonyState::InProgress);
658
659        // InProgress -> Completed
660        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        // Try to add same participant again
704        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}