1use 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#[derive(Clone, Serialize, Deserialize)]
115pub struct FrostSecretShare {
116 pub index: usize,
118 pub secret: Scalar,
120 pub verification_shares: Vec<RistrettoPoint>,
122}
123
124#[derive(Clone, Debug, Serialize, Deserialize)]
126pub struct FrostNonceCommitment {
127 pub hiding: RistrettoPoint,
129 pub binding: RistrettoPoint,
131}
132
133#[derive(Clone, Debug, Serialize, Deserialize)]
135pub struct PartialSignature {
136 pub signer_id: usize,
138 pub z: Scalar,
140}
141
142pub struct FrostKeygen {
144 threshold: usize,
145 num_participants: usize,
146 group_public_key: Option<PublicKey>,
147 shares: Vec<FrostSecretShare>,
148}
149
150impl FrostKeygen {
151 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 pub fn generate_shares(&mut self) -> Vec<FrostSecretShare> {
168 let params = DkgParams::new(self.num_participants, self.threshold);
170 let mut participants: Vec<_> = (0..self.num_participants)
171 .map(|i| DkgParticipant::new(¶ms, i))
172 .collect();
173
174 let commitments: Vec<_> = participants.iter().map(|p| p.get_commitments()).collect();
176
177 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 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 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 pub fn group_public_key(&self) -> PublicKey {
220 self.group_public_key.unwrap()
221 }
222}
223
224pub struct FrostSigner {
226 signer_id: usize,
228 secret_share: FrostSecretShare,
230 group_public_key: PublicKey,
232 nonce_pair: Option<(Scalar, Scalar)>,
234 nonce_commitment: Option<FrostNonceCommitment>,
236}
237
238impl FrostSigner {
239 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 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 pub fn get_nonce_commitment(&self) -> FrostNonceCommitment {
271 self.nonce_commitment
272 .clone()
273 .expect("Nonce not preprocessed")
274 }
275
276 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 let rho = compute_binding_value(message, commitments);
297
298 let group_commitment: RistrettoPoint =
300 commitments.iter().map(|c| c.hiding + rho * c.binding).sum();
301
302 let challenge = compute_challenge(&self.group_public_key, &group_commitment, message);
304
305 let lambda = compute_lagrange_coefficient(self.signer_id, signing_set);
307
308 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
318pub 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 let rho = compute_binding_value(message, commitments);
341
342 let group_commitment: RistrettoPoint =
344 commitments.iter().map(|c| c.hiding + rho * c.binding).sum();
345
346 let z: Scalar = partial_sigs.iter().map(|sig| sig.z).sum();
348
349 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
360fn 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
376fn 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
389fn 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
406pub fn verify_frost_signature(
413 public_key: &PublicKey,
414 message: &[u8],
415 signature: &SignatureBytes,
416) -> FrostResult<()> {
417 use curve25519_dalek::ristretto::CompressedRistretto;
418
419 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 let pk_point = CompressedRistretto(*public_key)
432 .decompress()
433 .ok_or(FrostError::InvalidSignatureShare)?;
434
435 let challenge = compute_challenge(public_key, &r_point, message);
437
438 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 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 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 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 let commitments: Vec<_> = signing_set
481 .iter()
482 .map(|&id| signers[id - 1].get_nonce_commitment())
483 .collect();
484
485 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 let signature =
494 aggregate_frost_signatures(message, &signing_set, &commitments, &partial_sigs).unwrap();
495
496 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 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 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 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 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 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 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 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 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 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 assert_ne!(pk1, pk2);
787 assert_ne!(shares1[0].secret, shares2[0].secret);
788 }
789
790 #[test]
791 fn test_lagrange_interpolation_property() {
792 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 let signing_sets = vec![vec![1, 2], vec![1, 3], vec![2, 3]];
802
803 for signing_set in signing_sets {
804 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 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}