saorsa_core/threshold/
frost.rs

1// Copyright 2024 Saorsa Labs Limited
2//
3// This software is dual-licensed under:
4// - GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later)
5// - Commercial License
6//
7// For AGPL-3.0 license, see LICENSE-AGPL-3.0
8// For commercial licensing, contact: saorsalabs@gmail.com
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under these licenses is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
14//! FROST (Flexible Round-Optimized Schnorr Threshold) signatures implementation
15//!
16//! Provides threshold signatures where t-of-n participants can create valid signatures
17
18use super::{Result, ThresholdError};
19use crate::quantum_crypto::types::*;
20// use frost_ed25519 as frost; // Temporarily disabled
21// Removed unused OsRng import
22use serde::{Deserialize, Serialize};
23use std::collections::HashMap;
24
25/// FROST signing session
26pub struct FrostSession {
27    /// Session identifier
28    pub session_id: [u8; 32],
29
30    /// Message to be signed
31    pub message: Vec<u8>,
32
33    /// Threshold value
34    pub threshold: u16,
35
36    /// Signing commitments from participants
37    pub commitments: HashMap<ParticipantId, SigningCommitments>,
38
39    /// Signing shares from participants
40    pub shares: HashMap<ParticipantId, SigningShare>,
41
42    /// Group public key
43    pub group_public_key: FrostGroupPublicKey,
44
45    /// Session state
46    pub state: SessionState,
47}
48
49/// Signing commitments from a participant
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct SigningCommitments {
52    pub hiding: Vec<u8>,
53    pub binding: Vec<u8>,
54}
55
56/// Signing share from a participant
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct SigningShare {
59    pub share: Vec<u8>,
60}
61
62/// FROST session state
63#[derive(Debug, Clone, PartialEq)]
64pub enum SessionState {
65    /// Collecting commitments
66    CollectingCommitments,
67
68    /// Collecting shares
69    CollectingShares,
70
71    /// Ready to aggregate
72    ReadyToAggregate,
73
74    /// Completed
75    Completed,
76
77    /// Failed
78    Failed(String),
79}
80
81/// FROST key generation result
82pub struct KeyGenerationResult {
83    /// Group public key
84    pub group_public_key: FrostGroupPublicKey,
85
86    /// Participant shares
87    pub shares: HashMap<ParticipantId, ParticipantShare>,
88
89    /// Public commitments for verification
90    pub commitments: Vec<Vec<u8>>,
91}
92
93/// Participant's share in the group
94#[derive(Clone)]
95pub struct ParticipantShare {
96    pub participant_id: ParticipantId,
97    pub signing_share: Vec<u8>, // Placeholder for frost::keys::SigningShare
98    pub verifying_share: Vec<u8>, // Placeholder for frost::keys::VerifyingShare
99}
100
101impl FrostSession {
102    /// Create new FROST signing session
103    pub fn new(message: Vec<u8>, threshold: u16, group_public_key: FrostGroupPublicKey) -> Self {
104        Self {
105            session_id: rand::random(),
106            message,
107            threshold,
108            commitments: HashMap::new(),
109            shares: HashMap::new(),
110            group_public_key,
111            state: SessionState::CollectingCommitments,
112        }
113    }
114
115    /// Add signing commitments from a participant
116    pub fn add_commitments(
117        &mut self,
118        participant_id: ParticipantId,
119        commitments: SigningCommitments,
120    ) -> Result<()> {
121        if self.state != SessionState::CollectingCommitments {
122            return Err(ThresholdError::InvalidShare(
123                "Not in commitment collection phase".to_string(),
124            ));
125        }
126
127        self.commitments.insert(participant_id, commitments);
128
129        // Check if we have enough commitments
130        if self.commitments.len() >= self.threshold as usize {
131            self.state = SessionState::CollectingShares;
132        }
133
134        Ok(())
135    }
136
137    /// Add signing share from a participant
138    pub fn add_share(&mut self, participant_id: ParticipantId, share: SigningShare) -> Result<()> {
139        if self.state != SessionState::CollectingShares {
140            return Err(ThresholdError::InvalidShare(
141                "Not in share collection phase".to_string(),
142            ));
143        }
144
145        // Verify participant provided commitments
146        if !self.commitments.contains_key(&participant_id) {
147            return Err(ThresholdError::InvalidShare(
148                "Participant did not provide commitments".to_string(),
149            ));
150        }
151
152        self.shares.insert(participant_id, share);
153
154        // Check if we have enough shares
155        if self.shares.len() >= self.threshold as usize {
156            self.state = SessionState::ReadyToAggregate;
157        }
158
159        Ok(())
160    }
161
162    /// Aggregate shares into final signature
163    pub fn aggregate(&mut self) -> Result<FrostSignature> {
164        if self.state != SessionState::ReadyToAggregate {
165            return Err(ThresholdError::AggregationFailed(
166                "Not ready to aggregate".to_string(),
167            ));
168        }
169
170        // Convert to FROST types and aggregate
171        // This is a simplified version - actual implementation would use frost crate
172
173        // For now, concatenate all shares as a simple aggregation
174        let mut aggregated = Vec::new();
175        for (participant_id, share) in &self.shares {
176            aggregated.extend_from_slice(&participant_id.0.to_be_bytes());
177            aggregated.extend_from_slice(&share.share);
178        }
179
180        self.state = SessionState::Completed;
181
182        Ok(FrostSignature(aggregated))
183    }
184
185    /// Check if session is complete
186    pub fn is_complete(&self) -> bool {
187        matches!(self.state, SessionState::Completed)
188    }
189
190    /// Get session progress
191    pub fn get_progress(&self) -> SessionProgress {
192        SessionProgress {
193            session_id: self.session_id,
194            state: self.state.clone(),
195            commitments_received: self.commitments.len() as u16,
196            shares_received: self.shares.len() as u16,
197            threshold: self.threshold,
198        }
199    }
200}
201
202/// Session progress information
203#[derive(Debug, Clone)]
204pub struct SessionProgress {
205    pub session_id: [u8; 32],
206    pub state: SessionState,
207    pub commitments_received: u16,
208    pub shares_received: u16,
209    pub threshold: u16,
210}
211
212/// FROST coordinator for managing signing sessions
213pub struct FrostCoordinator {
214    /// Active signing sessions
215    pub sessions: HashMap<[u8; 32], FrostSession>,
216
217    /// Group information
218    pub groups: HashMap<GroupId, GroupInfo>,
219}
220
221/// Group information for FROST
222pub struct GroupInfo {
223    pub group_public_key: FrostGroupPublicKey,
224    pub threshold: u16,
225    pub participants: Vec<ParticipantId>,
226}
227
228impl Default for FrostCoordinator {
229    fn default() -> Self {
230        Self::new()
231    }
232}
233
234impl FrostCoordinator {
235    /// Create new coordinator
236    pub fn new() -> Self {
237        Self {
238            sessions: HashMap::new(),
239            groups: HashMap::new(),
240        }
241    }
242
243    /// Register a group
244    pub fn register_group(
245        &mut self,
246        group_id: GroupId,
247        group_public_key: FrostGroupPublicKey,
248        threshold: u16,
249        participants: Vec<ParticipantId>,
250    ) {
251        self.groups.insert(
252            group_id,
253            GroupInfo {
254                group_public_key,
255                threshold,
256                participants,
257            },
258        );
259    }
260
261    /// Initiate signing session
262    pub fn initiate_signing(&mut self, group_id: &GroupId, message: Vec<u8>) -> Result<[u8; 32]> {
263        let group_info = self.groups.get(group_id).ok_or_else(|| {
264            ThresholdError::GroupOperationFailed("Group not registered".to_string())
265        })?;
266
267        let session = FrostSession::new(
268            message,
269            group_info.threshold,
270            group_info.group_public_key.clone(),
271        );
272
273        let session_id = session.session_id;
274        self.sessions.insert(session_id, session);
275
276        Ok(session_id)
277    }
278
279    /// Process signing commitment
280    pub fn process_commitment(
281        &mut self,
282        session_id: &[u8; 32],
283        participant_id: ParticipantId,
284        commitments: SigningCommitments,
285    ) -> Result<()> {
286        let session = self
287            .sessions
288            .get_mut(session_id)
289            .ok_or_else(|| ThresholdError::InvalidShare("Session not found".to_string()))?;
290
291        session.add_commitments(participant_id, commitments)
292    }
293
294    /// Process signing share
295    pub fn process_share(
296        &mut self,
297        session_id: &[u8; 32],
298        participant_id: ParticipantId,
299        share: SigningShare,
300    ) -> Result<()> {
301        let session = self
302            .sessions
303            .get_mut(session_id)
304            .ok_or_else(|| ThresholdError::InvalidShare("Session not found".to_string()))?;
305
306        session.add_share(participant_id, share)
307    }
308
309    /// Complete signing session
310    pub fn complete_signing(&mut self, session_id: &[u8; 32]) -> Result<FrostSignature> {
311        let session = self
312            .sessions
313            .get_mut(session_id)
314            .ok_or_else(|| ThresholdError::AggregationFailed("Session not found".to_string()))?;
315
316        let signature = session.aggregate()?;
317
318        // Clean up completed session after a delay
319        // In practice, would schedule cleanup
320
321        Ok(signature)
322    }
323
324    /// Get session status
325    pub fn get_session_status(&self, session_id: &[u8; 32]) -> Option<SessionProgress> {
326        self.sessions.get(session_id).map(|s| s.get_progress())
327    }
328
329    /// Clean up old sessions
330    pub fn cleanup_old_sessions(&mut self, _max_age: std::time::Duration) {
331        let _now = std::time::SystemTime::now();
332
333        self.sessions.retain(|_, session| {
334            // In practice, would check session creation time
335            !matches!(
336                session.state,
337                SessionState::Completed | SessionState::Failed(_)
338            )
339        });
340    }
341}
342
343/// Generate FROST key shares for a group
344pub async fn generate_key_shares(threshold: u16, participants: u16) -> Result<KeyGenerationResult> {
345    if threshold > participants {
346        return Err(ThresholdError::InvalidParameters(
347            "Threshold cannot exceed participants".to_string(),
348        ));
349    }
350
351    if threshold == 0 {
352        return Err(ThresholdError::InvalidParameters(
353            "Threshold must be at least 1".to_string(),
354        ));
355    }
356
357    // Placeholder implementation (frost crate disabled)
358    let mut participant_shares = HashMap::new();
359
360    for i in 0..participants {
361        let participant_id = ParticipantId(i);
362        participant_shares.insert(
363            participant_id.clone(),
364            ParticipantShare {
365                participant_id: participant_id.clone(),
366                signing_share: vec![i as u8; 32],   // Placeholder
367                verifying_share: vec![i as u8; 32], // Placeholder
368            },
369        );
370    }
371
372    // Placeholder group public key
373    let group_public_key = FrostGroupPublicKey(vec![0; 32]);
374
375    Ok(KeyGenerationResult {
376        group_public_key,
377        shares: participant_shares,
378        commitments: vec![], // Would include actual commitments
379    })
380}
381
382#[cfg(test)]
383mod tests {
384    use super::*;
385
386    #[test]
387    fn test_frost_session_lifecycle() {
388        let message = b"Test message".to_vec();
389        let group_key = FrostGroupPublicKey(vec![0; 32]);
390        let mut session = FrostSession::new(message, 2, group_key);
391
392        // Add commitments
393        assert_eq!(session.state, SessionState::CollectingCommitments);
394
395        session
396            .add_commitments(
397                ParticipantId(1),
398                SigningCommitments {
399                    hiding: vec![1; 32],
400                    binding: vec![2; 32],
401                },
402            )
403            .unwrap();
404
405        session
406            .add_commitments(
407                ParticipantId(2),
408                SigningCommitments {
409                    hiding: vec![3; 32],
410                    binding: vec![4; 32],
411                },
412            )
413            .unwrap();
414
415        // Should move to collecting shares
416        assert_eq!(session.state, SessionState::CollectingShares);
417
418        // Add shares
419        session
420            .add_share(ParticipantId(1), SigningShare { share: vec![5; 32] })
421            .unwrap();
422
423        session
424            .add_share(ParticipantId(2), SigningShare { share: vec![6; 32] })
425            .unwrap();
426
427        // Should be ready to aggregate
428        assert_eq!(session.state, SessionState::ReadyToAggregate);
429
430        // Aggregate
431        let _signature = session.aggregate().unwrap();
432        assert!(session.is_complete());
433    }
434
435    #[tokio::test]
436    async fn test_key_generation() {
437        let result = generate_key_shares(2, 3).await.unwrap();
438
439        assert_eq!(result.shares.len(), 3);
440        assert!(!result.group_public_key.0.is_empty());
441    }
442
443    #[test]
444    fn test_coordinator() {
445        let mut coordinator = FrostCoordinator::new();
446        let group_id = GroupId([1; 32]);
447        let group_key = FrostGroupPublicKey(vec![0; 32]);
448
449        // Register group
450        coordinator.register_group(
451            group_id.clone(),
452            group_key,
453            2,
454            vec![ParticipantId(1), ParticipantId(2), ParticipantId(3)],
455        );
456
457        // Initiate signing
458        let message = b"Test message".to_vec();
459        let session_id = coordinator.initiate_signing(&group_id, message).unwrap();
460
461        // Check session status
462        let status = coordinator.get_session_status(&session_id).unwrap();
463        assert_eq!(status.threshold, 2);
464        assert_eq!(status.commitments_received, 0);
465    }
466}