chie_crypto/
frost.rs

1//! FROST: Flexible Round-Optimized Schnorr Threshold Signatures
2//!
3//! FROST is a threshold signature scheme that produces standard Schnorr signatures
4//! with only 2 rounds of communication. It's significantly more efficient than
5//! threshold ECDSA while maintaining strong security properties.
6//!
7//! # Features
8//!
9//! - **2-round signing protocol**: More efficient than threshold ECDSA (3+ rounds)
10//! - **Standard Schnorr signatures**: Output is indistinguishable from single-party Schnorr
11//! - **Robust against rogue-key attacks**: Using proof-of-possession
12//! - **Supports any t-of-n threshold**: Flexible threshold configurations
13//! - **Dealer-free key generation**: Based on Pedersen DKG
14//!
15//! # Protocol Overview
16//!
17//! 1. **Key Generation Phase** (one-time setup):
18//!    - Run distributed key generation (DKG) to create shares
19//!    - Each participant gets a secret share and the group public key
20//!
21//! 2. **Preprocessing Phase** (can be done in advance):
22//!    - Each signer generates commitment pairs (d, e) and sends commitments
23//!    - Stores (d, e, D, E) for later use
24//!
25//! 3. **Signing Round 1**:
26//!    - Coordinator selects signing set and message
27//!    - Each signer reveals one commitment pair (D_i, E_i)
28//!
29//! 4. **Signing Round 2**:
30//!    - Coordinator computes binding value and challenge
31//!    - Each signer computes partial signature z_i
32//!    - Coordinator aggregates into full signature (R, z)
33//!
34//! # Example
35//!
36//! ```
37//! use chie_crypto::frost::{FrostKeygen, FrostSigner, aggregate_frost_signatures, verify_frost_signature};
38//!
39//! // Setup: 2-of-3 threshold
40//! let threshold = 2;
41//! let num_signers = 3;
42//!
43//! // Key generation (one-time setup)
44//! let mut keygen = FrostKeygen::new(threshold, num_signers);
45//! let shares = keygen.generate_shares();
46//!
47//! // Create signers
48//! let mut signers: Vec<_> = shares.iter().enumerate()
49//!     .map(|(i, share)| FrostSigner::new(i + 1, share.clone(), keygen.group_public_key()))
50//!     .collect();
51//!
52//! // Preprocessing: Generate nonce commitments
53//! for signer in &mut signers {
54//!     signer.preprocess();
55//! }
56//!
57//! let message = b"Transaction data";
58//!
59//! // Signing Round 1: Collect nonce commitments from threshold signers
60//! let signing_set = vec![1, 2]; // Use signers 1 and 2
61//! let commitments: Vec<_> = signing_set.iter()
62//!     .map(|&id| signers[id - 1].get_nonce_commitment())
63//!     .collect();
64//!
65//! // Signing Round 2: Generate partial signatures
66//! let partial_sigs: Vec<_> = signing_set.iter()
67//!     .map(|&id| signers[id - 1].sign(message, &signing_set, &commitments))
68//!     .collect::<Result<Vec<_>, _>>()
69//!     .unwrap();
70//!
71//! // Aggregate into final signature
72//! let signature = aggregate_frost_signatures(
73//!     message,
74//!     &signing_set,
75//!     &commitments,
76//!     &partial_sigs,
77//! ).unwrap();
78//!
79//! // Verify with group public key
80//! assert!(verify_frost_signature(&keygen.group_public_key(), message, &signature).is_ok());
81//! ```
82
83use crate::dkg::{DkgParams, DkgParticipant};
84use crate::signing::{PublicKey, SignatureBytes};
85use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
86use curve25519_dalek::ristretto::RistrettoPoint;
87use curve25519_dalek::scalar::Scalar;
88use serde::{Deserialize, Serialize};
89use thiserror::Error;
90
91#[derive(Error, Debug)]
92pub enum FrostError {
93    #[error("Invalid threshold: must be 1 <= t <= n")]
94    InvalidThreshold,
95    #[error("Insufficient signers: need at least {0} but got {1}")]
96    InsufficientSigners(usize, usize),
97    #[error("Invalid signer ID: {0}")]
98    InvalidSignerId(usize),
99    #[error("Duplicate signer ID: {0}")]
100    DuplicateSignerId(usize),
101    #[error("Missing nonce commitment for signer {0}")]
102    MissingFrostNonceCommitment(usize),
103    #[error("Nonce not preprocessed")]
104    NonceNotPreprocessed,
105    #[error("Invalid signature share")]
106    InvalidSignatureShare,
107    #[error("Serialization error: {0}")]
108    SerializationError(String),
109}
110
111pub type FrostResult<T> = Result<T, FrostError>;
112
113/// Secret share for a FROST participant
114#[derive(Clone, Serialize, Deserialize)]
115pub struct FrostSecretShare {
116    /// Participant index (1-indexed)
117    pub index: usize,
118    /// Secret share value
119    pub secret: Scalar,
120    /// Verification shares for other participants (for VSS verification)
121    pub verification_shares: Vec<RistrettoPoint>,
122}
123
124/// Nonce commitment pair for FROST preprocessing
125#[derive(Clone, Debug, Serialize, Deserialize)]
126pub struct FrostNonceCommitment {
127    /// Hiding nonce commitment D = d * G
128    pub hiding: RistrettoPoint,
129    /// Binding nonce commitment E = e * G
130    pub binding: RistrettoPoint,
131}
132
133/// Partial signature from a FROST signer
134#[derive(Clone, Debug, Serialize, Deserialize)]
135pub struct PartialSignature {
136    /// Signer index
137    pub signer_id: usize,
138    /// Signature share z_i
139    pub z: Scalar,
140}
141
142/// FROST key generation using Pedersen DKG
143pub struct FrostKeygen {
144    threshold: usize,
145    num_participants: usize,
146    group_public_key: Option<PublicKey>,
147    shares: Vec<FrostSecretShare>,
148}
149
150impl FrostKeygen {
151    /// Create a new FROST key generation instance
152    ///
153    /// # Arguments
154    ///
155    /// * `threshold` - Minimum number of signers required (t)
156    /// * `num_participants` - Total number of participants (n)
157    pub fn new(threshold: usize, num_participants: usize) -> Self {
158        Self {
159            threshold,
160            num_participants,
161            group_public_key: None,
162            shares: Vec::new(),
163        }
164    }
165
166    /// Generate secret shares using DKG
167    pub fn generate_shares(&mut self) -> Vec<FrostSecretShare> {
168        // Use DKG for key generation
169        let params = DkgParams::new(self.num_participants, self.threshold);
170        let mut participants: Vec<_> = (0..self.num_participants)
171            .map(|i| DkgParticipant::new(&params, i))
172            .collect();
173
174        // Round 1: Broadcast commitments
175        let commitments: Vec<_> = participants.iter().map(|p| p.get_commitments()).collect();
176
177        // Round 2: Distribute shares
178        for i in 0..self.num_participants {
179            for j in 0..self.num_participants {
180                if i != j {
181                    let share = participants[j].generate_share(i).unwrap();
182                    participants[i]
183                        .receive_share(j, share, &commitments[j])
184                        .unwrap();
185                }
186            }
187        }
188
189        // Compute final shares
190        self.shares = participants
191            .iter()
192            .enumerate()
193            .map(|(i, p)| {
194                let secret = p.get_secret_share().unwrap();
195                FrostSecretShare {
196                    index: i + 1,
197                    secret,
198                    verification_shares: commitments
199                        .iter()
200                        .map(|c| {
201                            use curve25519_dalek::ristretto::CompressedRistretto;
202                            let mut bytes = [0u8; 32];
203                            bytes.copy_from_slice(&c.commitments[0]);
204                            CompressedRistretto(bytes).decompress().unwrap()
205                        })
206                        .collect(),
207                }
208            })
209            .collect();
210
211        // Compute group public key using DKG aggregate function
212        let group_point = crate::dkg::aggregate_public_key(&commitments);
213        self.group_public_key = Some(group_point.compress().to_bytes());
214
215        self.shares.clone()
216    }
217
218    /// Get the group public key
219    pub fn group_public_key(&self) -> PublicKey {
220        self.group_public_key.unwrap()
221    }
222}
223
224/// FROST signer instance
225pub struct FrostSigner {
226    /// Signer index (1-indexed)
227    signer_id: usize,
228    /// Secret share
229    secret_share: FrostSecretShare,
230    /// Group public key
231    group_public_key: PublicKey,
232    /// Preprocessed nonce pair (d, e)
233    nonce_pair: Option<(Scalar, Scalar)>,
234    /// Nonce commitment (D, E)
235    nonce_commitment: Option<FrostNonceCommitment>,
236}
237
238impl FrostSigner {
239    /// Create a new FROST signer
240    pub fn new(
241        signer_id: usize,
242        secret_share: FrostSecretShare,
243        group_public_key: PublicKey,
244    ) -> Self {
245        Self {
246            signer_id,
247            secret_share,
248            group_public_key,
249            nonce_pair: None,
250            nonce_commitment: None,
251        }
252    }
253
254    /// Preprocess: Generate nonce commitment pair
255    ///
256    /// This can be done in advance before knowing the message to be signed.
257    /// Generates random nonces (d, e) and computes commitments (D, E).
258    pub fn preprocess(&mut self) {
259        let d = Scalar::from_bytes_mod_order(rand::random::<[u8; 32]>());
260        let e = Scalar::from_bytes_mod_order(rand::random::<[u8; 32]>());
261
262        let hiding = d * RISTRETTO_BASEPOINT_POINT;
263        let binding = e * RISTRETTO_BASEPOINT_POINT;
264
265        self.nonce_pair = Some((d, e));
266        self.nonce_commitment = Some(FrostNonceCommitment { hiding, binding });
267    }
268
269    /// Get nonce commitment for Round 1
270    pub fn get_nonce_commitment(&self) -> FrostNonceCommitment {
271        self.nonce_commitment
272            .clone()
273            .expect("Nonce not preprocessed")
274    }
275
276    /// Sign message in Round 2
277    ///
278    /// # Arguments
279    ///
280    /// * `message` - Message to sign
281    /// * `signing_set` - List of signer IDs participating in this signature
282    /// * `commitments` - Nonce commitments from all signers in signing_set
283    pub fn sign(
284        &self,
285        message: &[u8],
286        signing_set: &[usize],
287        commitments: &[FrostNonceCommitment],
288    ) -> FrostResult<PartialSignature> {
289        if self.nonce_pair.is_none() {
290            return Err(FrostError::NonceNotPreprocessed);
291        }
292
293        let (d, e) = self.nonce_pair.unwrap();
294
295        // Compute binding value rho = H(message, commitments)
296        let rho = compute_binding_value(message, commitments);
297
298        // Compute group commitment R = sum(D_i + rho * E_i)
299        let group_commitment: RistrettoPoint =
300            commitments.iter().map(|c| c.hiding + rho * c.binding).sum();
301
302        // Compute challenge c = H(group_public_key, R, message)
303        let challenge = compute_challenge(&self.group_public_key, &group_commitment, message);
304
305        // Compute Lagrange coefficient
306        let lambda = compute_lagrange_coefficient(self.signer_id, signing_set);
307
308        // Compute signature share: z_i = d_i + (rho * e_i) + (lambda_i * s_i * c)
309        let z = d + (rho * e) + (lambda * self.secret_share.secret * challenge);
310
311        Ok(PartialSignature {
312            signer_id: self.signer_id,
313            z,
314        })
315    }
316}
317
318/// Aggregate partial signatures into a full Schnorr signature
319///
320/// # Arguments
321///
322/// * `message` - Message that was signed
323/// * `signing_set` - List of signer IDs that participated
324/// * `commitments` - Nonce commitments from all signers
325/// * `partial_sigs` - Partial signatures from all signers
326pub fn aggregate_frost_signatures(
327    message: &[u8],
328    signing_set: &[usize],
329    commitments: &[FrostNonceCommitment],
330    partial_sigs: &[PartialSignature],
331) -> FrostResult<SignatureBytes> {
332    if partial_sigs.len() < signing_set.len() {
333        return Err(FrostError::InsufficientSigners(
334            signing_set.len(),
335            partial_sigs.len(),
336        ));
337    }
338
339    // Compute binding value
340    let rho = compute_binding_value(message, commitments);
341
342    // Compute group commitment R
343    let group_commitment: RistrettoPoint =
344        commitments.iter().map(|c| c.hiding + rho * c.binding).sum();
345
346    // Aggregate signature shares: z = sum(z_i)
347    let z: Scalar = partial_sigs.iter().map(|sig| sig.z).sum();
348
349    // Construct signature (R, z)
350    let r_bytes = group_commitment.compress().to_bytes();
351    let z_bytes = z.to_bytes();
352
353    let mut sig_bytes = [0u8; 64];
354    sig_bytes[..32].copy_from_slice(&r_bytes);
355    sig_bytes[32..].copy_from_slice(&z_bytes);
356
357    Ok(sig_bytes)
358}
359
360/// Compute binding value: rho = H(message, commitments)
361fn compute_binding_value(message: &[u8], commitments: &[FrostNonceCommitment]) -> Scalar {
362    use blake3::Hasher;
363
364    let mut hasher = Hasher::new();
365    hasher.update(message);
366
367    for commitment in commitments {
368        hasher.update(&commitment.hiding.compress().to_bytes());
369        hasher.update(&commitment.binding.compress().to_bytes());
370    }
371
372    let hash = hasher.finalize();
373    Scalar::from_bytes_mod_order(*hash.as_bytes())
374}
375
376/// Compute challenge: c = H(group_public_key, R, message)
377fn compute_challenge(group_pk: &PublicKey, r: &RistrettoPoint, message: &[u8]) -> Scalar {
378    use blake3::Hasher;
379
380    let mut hasher = Hasher::new();
381    hasher.update(group_pk);
382    hasher.update(&r.compress().to_bytes());
383    hasher.update(message);
384
385    let hash = hasher.finalize();
386    Scalar::from_bytes_mod_order(*hash.as_bytes())
387}
388
389/// Compute Lagrange coefficient for signer in signing set
390///
391/// lambda_i = prod_{j in S, j != i} (j / (j - i))
392fn compute_lagrange_coefficient(signer_id: usize, signing_set: &[usize]) -> Scalar {
393    let mut numerator = Scalar::ONE;
394    let mut denominator = Scalar::ONE;
395
396    for &j in signing_set {
397        if j != signer_id {
398            numerator *= Scalar::from(j as u64);
399            denominator *= Scalar::from(j as u64) - Scalar::from(signer_id as u64);
400        }
401    }
402
403    numerator * denominator.invert()
404}
405
406/// Verify a FROST signature
407///
408/// FROST signatures are standard Schnorr signatures, verified with the equation:
409/// z * G = R + c * PK
410///
411/// where c = H(PK, R, message)
412pub fn verify_frost_signature(
413    public_key: &PublicKey,
414    message: &[u8],
415    signature: &SignatureBytes,
416) -> FrostResult<()> {
417    use curve25519_dalek::ristretto::CompressedRistretto;
418
419    // Parse signature (R || z)
420    let mut r_bytes = [0u8; 32];
421    let mut z_bytes = [0u8; 32];
422    r_bytes.copy_from_slice(&signature[..32]);
423    z_bytes.copy_from_slice(&signature[32..]);
424
425    let r_point = CompressedRistretto(r_bytes)
426        .decompress()
427        .ok_or(FrostError::InvalidSignatureShare)?;
428    let z = Scalar::from_bytes_mod_order(z_bytes);
429
430    // Decompress public key
431    let pk_point = CompressedRistretto(*public_key)
432        .decompress()
433        .ok_or(FrostError::InvalidSignatureShare)?;
434
435    // Compute challenge: c = H(PK, R, message)
436    let challenge = compute_challenge(public_key, &r_point, message);
437
438    // Verify: z * G = R + c * PK
439    let lhs = z * RISTRETTO_BASEPOINT_POINT;
440    let rhs = r_point + (challenge * pk_point);
441
442    if lhs == rhs {
443        Ok(())
444    } else {
445        Err(FrostError::InvalidSignatureShare)
446    }
447}
448
449#[cfg(test)]
450mod tests {
451    use super::*;
452    use curve25519_dalek::traits::Identity;
453
454    #[test]
455    fn test_frost_2_of_3_basic() {
456        // 2-of-3 threshold setup
457        let threshold = 2;
458        let num_signers = 3;
459
460        let mut keygen = FrostKeygen::new(threshold, num_signers);
461        let shares = keygen.generate_shares();
462        let group_pk = keygen.group_public_key();
463
464        // Create signers
465        let mut signers: Vec<_> = shares
466            .iter()
467            .enumerate()
468            .map(|(i, share)| FrostSigner::new(i + 1, share.clone(), group_pk))
469            .collect();
470
471        // Preprocess
472        for signer in &mut signers {
473            signer.preprocess();
474        }
475
476        let message = b"FROST test message";
477        let signing_set = vec![1, 2];
478
479        // Round 1: Collect commitments
480        let commitments: Vec<_> = signing_set
481            .iter()
482            .map(|&id| signers[id - 1].get_nonce_commitment())
483            .collect();
484
485        // Round 2: Sign
486        let partial_sigs: Vec<_> = signing_set
487            .iter()
488            .map(|&id| signers[id - 1].sign(message, &signing_set, &commitments))
489            .collect::<Result<Vec<_>, _>>()
490            .unwrap();
491
492        // Aggregate
493        let signature =
494            aggregate_frost_signatures(message, &signing_set, &commitments, &partial_sigs).unwrap();
495
496        // Verify
497        assert!(verify_frost_signature(&group_pk, message, &signature).is_ok());
498    }
499
500    #[test]
501    fn test_frost_different_signing_sets() {
502        let threshold = 2;
503        let num_signers = 3;
504
505        let mut keygen = FrostKeygen::new(threshold, num_signers);
506        let shares = keygen.generate_shares();
507        let group_pk = keygen.group_public_key();
508
509        let message = b"Test message";
510
511        // Try different combinations
512        let signing_sets = vec![vec![1, 2], vec![1, 3], vec![2, 3]];
513
514        for signing_set in signing_sets {
515            let mut signers: Vec<_> = shares
516                .iter()
517                .enumerate()
518                .map(|(i, share)| FrostSigner::new(i + 1, share.clone(), group_pk))
519                .collect();
520
521            for signer in &mut signers {
522                signer.preprocess();
523            }
524
525            let commitments: Vec<_> = signing_set
526                .iter()
527                .map(|&id| signers[id - 1].get_nonce_commitment())
528                .collect();
529
530            let partial_sigs: Vec<_> = signing_set
531                .iter()
532                .map(|&id| signers[id - 1].sign(message, &signing_set, &commitments))
533                .collect::<Result<Vec<_>, _>>()
534                .unwrap();
535
536            let signature =
537                aggregate_frost_signatures(message, &signing_set, &commitments, &partial_sigs)
538                    .unwrap();
539
540            assert!(verify_frost_signature(&group_pk, message, &signature).is_ok());
541        }
542    }
543
544    #[test]
545    fn test_frost_3_of_5() {
546        let threshold = 3;
547        let num_signers = 5;
548
549        let mut keygen = FrostKeygen::new(threshold, num_signers);
550        let shares = keygen.generate_shares();
551        let group_pk = keygen.group_public_key();
552
553        let mut signers: Vec<_> = shares
554            .iter()
555            .enumerate()
556            .map(|(i, share)| FrostSigner::new(i + 1, share.clone(), group_pk))
557            .collect();
558
559        for signer in &mut signers {
560            signer.preprocess();
561        }
562
563        let message = b"3-of-5 threshold test";
564        let signing_set = vec![1, 3, 5];
565
566        let commitments: Vec<_> = signing_set
567            .iter()
568            .map(|&id| signers[id - 1].get_nonce_commitment())
569            .collect();
570
571        let partial_sigs: Vec<_> = signing_set
572            .iter()
573            .map(|&id| signers[id - 1].sign(message, &signing_set, &commitments))
574            .collect::<Result<Vec<_>, _>>()
575            .unwrap();
576
577        let signature =
578            aggregate_frost_signatures(message, &signing_set, &commitments, &partial_sigs).unwrap();
579
580        assert!(verify_frost_signature(&group_pk, message, &signature).is_ok());
581    }
582
583    #[test]
584    fn test_frost_wrong_message_fails() {
585        let threshold = 2;
586        let num_signers = 3;
587
588        let mut keygen = FrostKeygen::new(threshold, num_signers);
589        let shares = keygen.generate_shares();
590        let group_pk = keygen.group_public_key();
591
592        let mut signers: Vec<_> = shares
593            .iter()
594            .enumerate()
595            .map(|(i, share)| FrostSigner::new(i + 1, share.clone(), group_pk))
596            .collect();
597
598        for signer in &mut signers {
599            signer.preprocess();
600        }
601
602        let message = b"Original message";
603        let wrong_message = b"Wrong message";
604        let signing_set = vec![1, 2];
605
606        let commitments: Vec<_> = signing_set
607            .iter()
608            .map(|&id| signers[id - 1].get_nonce_commitment())
609            .collect();
610
611        let partial_sigs: Vec<_> = signing_set
612            .iter()
613            .map(|&id| signers[id - 1].sign(message, &signing_set, &commitments))
614            .collect::<Result<Vec<_>, _>>()
615            .unwrap();
616
617        let signature =
618            aggregate_frost_signatures(message, &signing_set, &commitments, &partial_sigs).unwrap();
619
620        // Should fail with wrong message
621        assert!(verify_frost_signature(&group_pk, wrong_message, &signature).is_err());
622    }
623
624    #[test]
625    fn test_frost_multiple_signatures_same_key() {
626        let threshold = 2;
627        let num_signers = 3;
628
629        let mut keygen = FrostKeygen::new(threshold, num_signers);
630        let shares = keygen.generate_shares();
631        let group_pk = keygen.group_public_key();
632
633        let messages = vec![b"Message 1".as_slice(), b"Message 2", b"Message 3"];
634
635        for message in messages {
636            let mut signers: Vec<_> = shares
637                .iter()
638                .enumerate()
639                .map(|(i, share)| FrostSigner::new(i + 1, share.clone(), group_pk))
640                .collect();
641
642            for signer in &mut signers {
643                signer.preprocess();
644            }
645
646            let signing_set = vec![1, 2];
647
648            let commitments: Vec<_> = signing_set
649                .iter()
650                .map(|&id| signers[id - 1].get_nonce_commitment())
651                .collect();
652
653            let partial_sigs: Vec<_> = signing_set
654                .iter()
655                .map(|&id| signers[id - 1].sign(message, &signing_set, &commitments))
656                .collect::<Result<Vec<_>, _>>()
657                .unwrap();
658
659            let signature =
660                aggregate_frost_signatures(message, &signing_set, &commitments, &partial_sigs)
661                    .unwrap();
662
663            assert!(verify_frost_signature(&group_pk, message, &signature).is_ok());
664        }
665    }
666
667    #[test]
668    fn test_frost_lagrange_coefficient() {
669        let signing_set = vec![1, 2, 3];
670
671        // Lagrange coefficients for a 3-of-3 setup
672        let lambda1 = compute_lagrange_coefficient(1, &signing_set);
673        let lambda2 = compute_lagrange_coefficient(2, &signing_set);
674        let lambda3 = compute_lagrange_coefficient(3, &signing_set);
675
676        // For a full set, coefficients should reconstruct to the secret
677        // This is a basic sanity check
678        assert_ne!(lambda1, Scalar::ZERO);
679        assert_ne!(lambda2, Scalar::ZERO);
680        assert_ne!(lambda3, Scalar::ZERO);
681    }
682
683    #[test]
684    fn test_frost_nonce_not_preprocessed() {
685        let threshold = 2;
686        let num_signers = 3;
687
688        let mut keygen = FrostKeygen::new(threshold, num_signers);
689        let shares = keygen.generate_shares();
690        let group_pk = keygen.group_public_key();
691
692        let signer = FrostSigner::new(1, shares[0].clone(), group_pk);
693
694        // Try to sign without preprocessing
695        let message = b"Test";
696        let signing_set = vec![1, 2];
697        let commitments = vec![];
698
699        let result = signer.sign(message, &signing_set, &commitments);
700        assert!(matches!(result, Err(FrostError::NonceNotPreprocessed)));
701    }
702
703    #[test]
704    fn test_frost_serialization() {
705        let threshold = 2;
706        let num_signers = 3;
707
708        let mut keygen = FrostKeygen::new(threshold, num_signers);
709        let shares = keygen.generate_shares();
710
711        // Test share serialization
712        let share_bytes = crate::codec::encode(&shares[0]).unwrap();
713        let deserialized_share: FrostSecretShare = crate::codec::decode(&share_bytes).unwrap();
714        assert_eq!(shares[0].index, deserialized_share.index);
715
716        // Test commitment serialization
717        let mut signer = FrostSigner::new(1, shares[0].clone(), keygen.group_public_key());
718        signer.preprocess();
719        let commitment = signer.get_nonce_commitment();
720
721        let commitment_bytes = crate::codec::encode(&commitment).unwrap();
722        let deserialized_commitment: FrostNonceCommitment =
723            crate::codec::decode(&commitment_bytes).unwrap();
724
725        assert_eq!(
726            commitment.hiding.compress().to_bytes(),
727            deserialized_commitment.hiding.compress().to_bytes()
728        );
729    }
730
731    #[test]
732    fn test_frost_all_participants() {
733        // Test with all participants (n-of-n)
734        let threshold = 3;
735        let num_signers = 3;
736
737        let mut keygen = FrostKeygen::new(threshold, num_signers);
738        let shares = keygen.generate_shares();
739        let group_pk = keygen.group_public_key();
740
741        let mut signers: Vec<_> = shares
742            .iter()
743            .enumerate()
744            .map(|(i, share)| FrostSigner::new(i + 1, share.clone(), group_pk))
745            .collect();
746
747        for signer in &mut signers {
748            signer.preprocess();
749        }
750
751        let message = b"All participants signing";
752        let signing_set = vec![1, 2, 3];
753
754        let commitments: Vec<_> = signing_set
755            .iter()
756            .map(|&id| signers[id - 1].get_nonce_commitment())
757            .collect();
758
759        let partial_sigs: Vec<_> = signing_set
760            .iter()
761            .map(|&id| signers[id - 1].sign(message, &signing_set, &commitments))
762            .collect::<Result<Vec<_>, _>>()
763            .unwrap();
764
765        let signature =
766            aggregate_frost_signatures(message, &signing_set, &commitments, &partial_sigs).unwrap();
767
768        assert!(verify_frost_signature(&group_pk, message, &signature).is_ok());
769    }
770
771    #[test]
772    fn test_frost_deterministic_keygen() {
773        // Multiple key generations should produce different keys (randomized)
774        let threshold = 2;
775        let num_signers = 3;
776
777        let mut keygen1 = FrostKeygen::new(threshold, num_signers);
778        let shares1 = keygen1.generate_shares();
779        let pk1 = keygen1.group_public_key();
780
781        let mut keygen2 = FrostKeygen::new(threshold, num_signers);
782        let shares2 = keygen2.generate_shares();
783        let pk2 = keygen2.group_public_key();
784
785        // Should be different (random generation)
786        assert_ne!(pk1, pk2);
787        assert_ne!(shares1[0].secret, shares2[0].secret);
788    }
789
790    #[test]
791    fn test_lagrange_interpolation_property() {
792        // Verify that Lagrange interpolation of shares gives the group public key
793        let threshold = 2;
794        let num_signers = 3;
795
796        let mut keygen = FrostKeygen::new(threshold, num_signers);
797        let shares = keygen.generate_shares();
798        let group_pk = keygen.group_public_key();
799
800        // Test with different signing sets
801        let signing_sets = vec![vec![1, 2], vec![1, 3], vec![2, 3]];
802
803        for signing_set in signing_sets {
804            // Compute sum(lambda_i * s_i * G)
805            let mut interpolated_pk = RistrettoPoint::identity();
806
807            for &signer_id in &signing_set {
808                let lambda = compute_lagrange_coefficient(signer_id, &signing_set);
809                let share_secret = shares[signer_id - 1].secret;
810                interpolated_pk += lambda * share_secret * RISTRETTO_BASEPOINT_POINT;
811            }
812
813            // This should equal the group public key
814            let interpolated_bytes = interpolated_pk.compress().to_bytes();
815
816            use curve25519_dalek::ristretto::CompressedRistretto;
817            let group_pk_point = CompressedRistretto(group_pk).decompress().unwrap();
818            let group_pk_bytes = group_pk_point.compress().to_bytes();
819
820            assert_eq!(
821                interpolated_bytes, group_pk_bytes,
822                "Lagrange interpolation failed for signing set {:?}",
823                signing_set
824            );
825        }
826    }
827}