1use crate::error::SignerError;
7use crate::threshold::frost::keygen::{derive_interpolating_value, KeyPackage};
8use k256::elliptic_curve::group::GroupEncoding;
10use k256::elliptic_curve::ops::Reduce;
11use k256::elliptic_curve::sec1::ToEncodedPoint;
12use k256::{AffinePoint, ProjectivePoint, Scalar};
13use sha2::{Digest, Sha256};
14use zeroize::Zeroizing;
15
16const CONTEXT_STRING: &[u8] = b"FROST-secp256k1-SHA256-v1";
18
19pub struct SigningNonces {
23 pub(crate) hiding: Zeroizing<Scalar>,
25 pub(crate) binding: Zeroizing<Scalar>,
27 pub commitments: SigningCommitments,
29}
30
31impl Drop for SigningNonces {
32 fn drop(&mut self) {
33 }
35}
36
37#[derive(Clone, Debug)]
39pub struct SigningCommitments {
40 pub identifier: u16,
42 pub hiding: AffinePoint,
44 pub binding: AffinePoint,
46}
47
48#[derive(Clone)]
50pub struct SignatureShare {
51 pub identifier: u16,
53 pub share: Scalar,
55}
56
57impl core::fmt::Debug for SignatureShare {
58 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
59 f.debug_struct("SignatureShare")
60 .field("identifier", &self.identifier)
61 .field("share", &"[REDACTED]")
62 .finish()
63 }
64}
65
66#[derive(Clone, Debug)]
68#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
69pub struct FrostSignature {
70 pub r_bytes: Vec<u8>,
72 pub s_bytes: [u8; 32],
74}
75
76impl FrostSignature {
77 pub fn to_bytes(&self) -> Vec<u8> {
79 let mut out = Vec::with_capacity(65);
80 out.extend_from_slice(&self.r_bytes);
81 out.extend_from_slice(&self.s_bytes);
82 out
83 }
84}
85
86fn h1(data: &[u8]) -> Scalar {
90 hash_to_scalar(b"rho", data)
91}
92
93fn h2(data: &[u8]) -> Scalar {
95 hash_to_scalar(b"chal", data)
96}
97
98#[allow(dead_code)]
103fn h3(data: &[u8]) -> Scalar {
104 hash_to_scalar(b"nonce", data)
105}
106
107fn h4(data: &[u8]) -> [u8; 32] {
109 hash_to_bytes(b"msg", data)
110}
111
112fn h5(data: &[u8]) -> [u8; 32] {
114 hash_to_bytes(b"com", data)
115}
116
117fn hash_to_scalar(tag: &[u8], data: &[u8]) -> Scalar {
121 let mut dst = Vec::with_capacity(CONTEXT_STRING.len() + tag.len());
123 dst.extend_from_slice(CONTEXT_STRING);
124 dst.extend_from_slice(tag);
125
126 let expanded = expand_message_xmd(data, &dst, 48);
128
129 let mut wide = [0u8; 48];
131 wide.copy_from_slice(&expanded);
132 scalar_from_wide(&wide)
133}
134
135fn hash_to_bytes(tag: &[u8], data: &[u8]) -> [u8; 32] {
137 let mut h = Sha256::new();
138 h.update(CONTEXT_STRING);
139 h.update(tag);
140 h.update(data);
141 let result = h.finalize();
142 let mut out = [0u8; 32];
143 out.copy_from_slice(&result);
144 out
145}
146
147fn expand_message_xmd(msg: &[u8], dst: &[u8], len_in_bytes: usize) -> Vec<u8> {
149 let b_in_bytes = 32usize; let ell = len_in_bytes.div_ceil(b_in_bytes);
151
152 let dst_prime_len = dst.len() as u8;
154
155 let z_pad = [0u8; 64];
157
158 let l_i_b = (len_in_bytes as u16).to_be_bytes();
160
161 let mut h0 = Sha256::new();
163 h0.update(z_pad);
164 h0.update(msg);
165 h0.update(l_i_b);
166 h0.update([0u8]);
167 h0.update(dst);
168 h0.update([dst_prime_len]);
169 let b_0 = h0.finalize();
170
171 let mut h1 = Sha256::new();
173 h1.update(b_0);
174 h1.update([1u8]);
175 h1.update(dst);
176 h1.update([dst_prime_len]);
177 let mut b_vals = vec![h1.finalize()];
178
179 for i in 2..=ell {
180 let prev = &b_vals[i - 2];
182 let mut xored = [0u8; 32];
183 for (j, byte) in xored.iter_mut().enumerate() {
184 *byte = b_0[j] ^ prev[j];
185 }
186 let mut hi = Sha256::new();
187 hi.update(xored);
188 hi.update([i as u8]);
189 hi.update(dst);
190 hi.update([dst_prime_len]);
191 b_vals.push(hi.finalize());
192 }
193
194 let mut output = Vec::with_capacity(len_in_bytes);
195 for b in &b_vals {
196 output.extend_from_slice(b);
197 }
198 output.truncate(len_in_bytes);
199 output
200}
201
202fn scalar_from_wide(bytes: &[u8; 48]) -> Scalar {
204 let mut u256_hi = [0u8; 32];
207 let mut u256_lo = [0u8; 32];
208 u256_hi[16..].copy_from_slice(&bytes[..16]);
209 u256_lo.copy_from_slice(&bytes[16..]);
210
211 let hi = k256::U256::from_be_slice(&u256_hi);
212 let lo = k256::U256::from_be_slice(&u256_lo);
213
214 let hi_scalar = <Scalar as Reduce<k256::U256>>::reduce(hi);
216 let lo_scalar = <Scalar as Reduce<k256::U256>>::reduce(lo);
217
218 let two_256_mod_n = {
220 let _bytes = [0u8; 32];
221 let max = k256::U256::from_be_hex(
224 "0000000000000000000000000000000100000000000000000000000000000000",
225 );
226 <Scalar as Reduce<k256::U256>>::reduce(max)
227 };
228
229 hi_scalar * two_256_mod_n + lo_scalar
230}
231
232pub fn commit(key_package: &KeyPackage) -> Result<SigningNonces, SignerError> {
239 let hiding = crate::threshold::frost::keygen::random_scalar()?;
240 let binding = crate::threshold::frost::keygen::random_scalar()?;
241
242 let hiding_commitment = (ProjectivePoint::GENERATOR * hiding).to_affine();
243 let binding_commitment = (ProjectivePoint::GENERATOR * binding).to_affine();
244
245 Ok(SigningNonces {
246 hiding: Zeroizing::new(hiding),
247 binding: Zeroizing::new(binding),
248 commitments: SigningCommitments {
249 identifier: key_package.identifier,
250 hiding: hiding_commitment,
251 binding: binding_commitment,
252 },
253 })
254}
255
256fn compute_binding_factor(
260 group_public_key: &AffinePoint,
261 commitments_list: &[SigningCommitments],
262 identifier: u16,
263 message: &[u8],
264) -> Scalar {
265 let mut commit_data = Vec::new();
267 for c in commitments_list {
268 let hiding_enc = ProjectivePoint::from(c.hiding)
269 .to_affine()
270 .to_encoded_point(true);
271 let binding_enc = ProjectivePoint::from(c.binding)
272 .to_affine()
273 .to_encoded_point(true);
274 commit_data.extend_from_slice(hiding_enc.as_bytes());
275 commit_data.extend_from_slice(binding_enc.as_bytes());
276 }
277 let encoded_commitments_hash = h5(&commit_data);
278
279 let msg_hash = h4(message);
281
282 let pk_enc = ProjectivePoint::from(*group_public_key)
284 .to_affine()
285 .to_encoded_point(true);
286 let mut input = Vec::new();
287 input.extend_from_slice(pk_enc.as_bytes());
288 input.extend_from_slice(&msg_hash);
289 input.extend_from_slice(&encoded_commitments_hash);
290
291 let mut id_bytes = [0u8; 32];
293 let id_be = (identifier as u64).to_be_bytes();
294 id_bytes[24..].copy_from_slice(&id_be);
295 input.extend_from_slice(&id_bytes);
296
297 h1(&input)
298}
299
300fn compute_group_commitment(
302 commitments_list: &[SigningCommitments],
303 binding_factors: &[(u16, Scalar)],
304) -> ProjectivePoint {
305 let mut r = ProjectivePoint::IDENTITY;
306
307 for c in commitments_list {
308 let rho = binding_factors
309 .iter()
310 .find(|(id, _)| *id == c.identifier)
311 .map(|(_, bf)| *bf)
312 .unwrap_or(Scalar::ZERO);
313
314 r += ProjectivePoint::from(c.hiding) + ProjectivePoint::from(c.binding) * rho;
315 }
316
317 r
318}
319
320pub fn sign(
327 key_package: &KeyPackage,
328 nonces: SigningNonces,
329 commitments_list: &[SigningCommitments],
330 message: &[u8],
331) -> Result<SignatureShare, SignerError> {
332 let mut binding_factors = Vec::new();
334 for c in commitments_list {
335 let bf = compute_binding_factor(
336 &key_package.group_public_key,
337 commitments_list,
338 c.identifier,
339 message,
340 );
341 binding_factors.push((c.identifier, bf));
342 }
343
344 let group_commitment = compute_group_commitment(commitments_list, &binding_factors);
346 let r_enc = group_commitment.to_affine().to_encoded_point(true);
347
348 let pk_enc = ProjectivePoint::from(key_package.group_public_key)
350 .to_affine()
351 .to_encoded_point(true);
352 let mut challenge_input = Vec::new();
353 challenge_input.extend_from_slice(r_enc.as_bytes());
354 challenge_input.extend_from_slice(pk_enc.as_bytes());
355 challenge_input.extend_from_slice(message);
356 let challenge = h2(&challenge_input);
357
358 let my_rho = binding_factors
360 .iter()
361 .find(|(id, _)| *id == key_package.identifier)
362 .map(|(_, bf)| *bf)
363 .ok_or_else(|| SignerError::SigningFailed("participant not in commitments list".into()))?;
364
365 let participant_ids: Vec<Scalar> = commitments_list
367 .iter()
368 .map(|c| Scalar::from(u64::from(c.identifier)))
369 .collect();
370 let lambda = derive_interpolating_value(
371 &Scalar::from(u64::from(key_package.identifier)),
372 &participant_ids,
373 )?;
374
375 let z = *nonces.hiding
377 + (*nonces.binding * my_rho)
378 + (lambda * *key_package.secret_share() * challenge);
379
380 Ok(SignatureShare {
381 identifier: key_package.identifier,
382 share: z,
383 })
384}
385
386pub fn aggregate(
393 commitments_list: &[SigningCommitments],
394 sig_shares: &[SignatureShare],
395 group_public_key: &AffinePoint,
396 message: &[u8],
397) -> Result<FrostSignature, SignerError> {
398 if sig_shares.len() < 2 {
399 return Err(SignerError::SigningFailed(
400 "need at least 2 signature shares".into(),
401 ));
402 }
403
404 let mut binding_factors = Vec::new();
406 for c in commitments_list {
407 let bf = compute_binding_factor(group_public_key, commitments_list, c.identifier, message);
408 binding_factors.push((c.identifier, bf));
409 }
410
411 let group_commitment = compute_group_commitment(commitments_list, &binding_factors);
413
414 let mut s = Scalar::ZERO;
416 for share in sig_shares {
417 s += share.share;
418 }
419
420 let r_enc = group_commitment.to_affine().to_encoded_point(true);
421
422 Ok(FrostSignature {
423 r_bytes: r_enc.as_bytes().to_vec(),
424 s_bytes: s.to_bytes().into(),
425 })
426}
427
428pub fn verify(
432 signature: &FrostSignature,
433 group_public_key: &AffinePoint,
434 message: &[u8],
435) -> Result<bool, SignerError> {
436 let r_ct = AffinePoint::from_bytes(signature.r_bytes.as_slice().into());
438 if !bool::from(r_ct.is_some()) {
439 return Ok(false);
440 }
441 #[allow(clippy::unwrap_used)]
443 let r_point = ProjectivePoint::from(r_ct.unwrap());
444
445 let s_wide = k256::U256::from_be_slice(&signature.s_bytes);
447 let s_scalar = <Scalar as Reduce<k256::U256>>::reduce(s_wide);
448
449 let r_enc = r_point.to_affine().to_encoded_point(true);
451 let pk_enc = ProjectivePoint::from(*group_public_key)
452 .to_affine()
453 .to_encoded_point(true);
454 let mut challenge_input = Vec::new();
455 challenge_input.extend_from_slice(r_enc.as_bytes());
456 challenge_input.extend_from_slice(pk_enc.as_bytes());
457 challenge_input.extend_from_slice(message);
458 let challenge = h2(&challenge_input);
459
460 let lhs = ProjectivePoint::GENERATOR * s_scalar;
462 let rhs = r_point + ProjectivePoint::from(*group_public_key) * challenge;
463
464 Ok(lhs == rhs)
465}
466
467pub fn verify_share(
471 share: &SignatureShare,
472 commitment: &SigningCommitments,
473 public_key_share: &AffinePoint,
474 group_public_key: &AffinePoint,
475 commitments_list: &[SigningCommitments],
476 message: &[u8],
477) -> Result<bool, SignerError> {
478 let binding_factor = compute_binding_factor(
480 group_public_key,
481 commitments_list,
482 share.identifier,
483 message,
484 );
485
486 let mut binding_factors = Vec::new();
488 for c in commitments_list {
489 let bf = compute_binding_factor(group_public_key, commitments_list, c.identifier, message);
490 binding_factors.push((c.identifier, bf));
491 }
492
493 let group_commitment = compute_group_commitment(commitments_list, &binding_factors);
495 let r_enc = group_commitment.to_affine().to_encoded_point(true);
496
497 let pk_enc = ProjectivePoint::from(*group_public_key)
499 .to_affine()
500 .to_encoded_point(true);
501 let mut challenge_input = Vec::new();
502 challenge_input.extend_from_slice(r_enc.as_bytes());
503 challenge_input.extend_from_slice(pk_enc.as_bytes());
504 challenge_input.extend_from_slice(message);
505 let challenge = h2(&challenge_input);
506
507 let participant_ids: Vec<Scalar> = commitments_list
509 .iter()
510 .map(|c| Scalar::from(u64::from(c.identifier)))
511 .collect();
512 let lambda =
513 derive_interpolating_value(&Scalar::from(u64::from(share.identifier)), &participant_ids)?;
514
515 let lhs = ProjectivePoint::GENERATOR * share.share;
517
518 let rhs = ProjectivePoint::from(commitment.hiding)
520 + ProjectivePoint::from(commitment.binding) * binding_factor
521 + ProjectivePoint::from(*public_key_share) * (lambda * challenge);
522
523 Ok(lhs == rhs)
524}
525
526pub fn identify_misbehaving(
542 sig_shares: &[SignatureShare],
543 commitments_list: &[SigningCommitments],
544 key_packages: &[KeyPackage],
545 group_public_key: &AffinePoint,
546 message: &[u8],
547) -> Result<Vec<u16>, SignerError> {
548 let mut cheaters = Vec::new();
549
550 for share in sig_shares {
551 let commitment = commitments_list
553 .iter()
554 .find(|c| c.identifier == share.identifier);
555
556 let commitment = match commitment {
557 Some(c) => c,
558 None => {
559 cheaters.push(share.identifier);
560 continue;
561 }
562 };
563
564 let key_package = key_packages
566 .iter()
567 .find(|kp| kp.identifier == share.identifier);
568
569 let key_package = match key_package {
570 Some(kp) => kp,
571 None => {
572 cheaters.push(share.identifier);
573 continue;
574 }
575 };
576
577 let pk_share = key_package.public_key();
578 let valid = verify_share(
579 share,
580 commitment,
581 &pk_share,
582 group_public_key,
583 commitments_list,
584 message,
585 )?;
586
587 if !valid {
588 cheaters.push(share.identifier);
589 }
590 }
591
592 Ok(cheaters)
593}
594
595#[cfg(test)]
596#[allow(clippy::unwrap_used, clippy::expect_used)]
597mod tests {
598 use super::*;
599 use crate::threshold::frost::keygen;
600
601 fn setup_2_of_3() -> (keygen::KeyGenOutput, AffinePoint) {
602 let secret = [0x42u8; 32];
603 let output = keygen::trusted_dealer_keygen(&secret, 2, 3).unwrap();
604 let group_pk = output.group_public_key;
605 (output, group_pk)
606 }
607
608 #[test]
611 fn test_frost_2_of_3_roundtrip() {
612 let (kgen, group_pk) = setup_2_of_3();
613 let msg = b"frost threshold message";
614
615 let nonce1 = commit(&kgen.key_packages[0]).unwrap();
617 let nonce2 = commit(&kgen.key_packages[1]).unwrap();
618 let commitments = vec![nonce1.commitments.clone(), nonce2.commitments.clone()];
619
620 let share1 = sign(&kgen.key_packages[0], nonce1, &commitments, msg).unwrap();
622 let share2 = sign(&kgen.key_packages[1], nonce2, &commitments, msg).unwrap();
623
624 let sig = aggregate(&commitments, &[share1, share2], &group_pk, msg).unwrap();
626 assert_eq!(sig.to_bytes().len(), 65); let valid = verify(&sig, &group_pk, msg).unwrap();
630 assert!(valid, "FROST 2-of-3 signature must verify");
631 }
632
633 #[test]
636 fn test_frost_different_participant_subsets() {
637 let (kgen, group_pk) = setup_2_of_3();
638 let msg = b"subset test";
639
640 let n1 = commit(&kgen.key_packages[0]).unwrap();
642 let n3 = commit(&kgen.key_packages[2]).unwrap();
643 let comms = vec![n1.commitments.clone(), n3.commitments.clone()];
644 let s1 = sign(&kgen.key_packages[0], n1, &comms, msg).unwrap();
645 let s3 = sign(&kgen.key_packages[2], n3, &comms, msg).unwrap();
646 let sig = aggregate(&comms, &[s1, s3], &group_pk, msg).unwrap();
647 assert!(
648 verify(&sig, &group_pk, msg).unwrap(),
649 "subset {{1,3}} must verify"
650 );
651 }
652
653 #[test]
656 fn test_frost_verify_share() {
657 let (kgen, group_pk) = setup_2_of_3();
658 let msg = b"share verify test";
659
660 let n1 = commit(&kgen.key_packages[0]).unwrap();
661 let n2 = commit(&kgen.key_packages[1]).unwrap();
662 let comms = vec![n1.commitments.clone(), n2.commitments.clone()];
663 let s1 = sign(&kgen.key_packages[0], n1, &comms, msg).unwrap();
664
665 let pk1 = kgen.key_packages[0].public_key();
667 let valid = verify_share(&s1, &comms[0], &pk1, &group_pk, &comms, msg).unwrap();
668 assert!(valid, "valid share must verify");
669 }
670
671 #[test]
674 fn test_frost_wrong_message_fails() {
675 let (kgen, group_pk) = setup_2_of_3();
676 let msg = b"correct msg";
677
678 let n1 = commit(&kgen.key_packages[0]).unwrap();
679 let n2 = commit(&kgen.key_packages[1]).unwrap();
680 let comms = vec![n1.commitments.clone(), n2.commitments.clone()];
681 let s1 = sign(&kgen.key_packages[0], n1, &comms, msg).unwrap();
682 let s2 = sign(&kgen.key_packages[1], n2, &comms, msg).unwrap();
683 let sig = aggregate(&comms, &[s1, s2], &group_pk, msg).unwrap();
684
685 let wrong = verify(&sig, &group_pk, b"wrong msg").unwrap();
686 assert!(!wrong, "wrong message must fail verification");
687 }
688
689 #[test]
692 fn test_frost_different_messages_different_sigs() {
693 let (kgen, group_pk) = setup_2_of_3();
694
695 let make_sig = |m: &[u8]| -> Vec<u8> {
696 let n1 = commit(&kgen.key_packages[0]).unwrap();
697 let n2 = commit(&kgen.key_packages[1]).unwrap();
698 let comms = vec![n1.commitments.clone(), n2.commitments.clone()];
699 let s1 = sign(&kgen.key_packages[0], n1, &comms, m).unwrap();
700 let s2 = sign(&kgen.key_packages[1], n2, &comms, m).unwrap();
701 aggregate(&comms, &[s1, s2], &group_pk, m)
702 .unwrap()
703 .to_bytes()
704 };
705
706 let sig_a = make_sig(b"message A");
707 let sig_b = make_sig(b"message B");
708 assert_ne!(sig_a, sig_b);
709 }
710
711 #[test]
714 fn test_frost_vss_commitments_verify() {
715 let (kgen, _) = setup_2_of_3();
716 for pkg in &kgen.key_packages {
717 assert!(
718 kgen.vss_commitments
719 .verify_share(pkg.identifier, pkg.secret_share()),
720 "VSS share must verify for participant {}",
721 pkg.identifier
722 );
723 }
724 }
725
726 #[test]
729 fn test_frost_aggregate_rejects_single_share() {
730 let (kgen, group_pk) = setup_2_of_3();
731 let msg = b"need 2";
732
733 let n1 = commit(&kgen.key_packages[0]).unwrap();
734 let comms = vec![n1.commitments.clone()];
735 let s1 = sign(&kgen.key_packages[0], n1, &comms, msg).unwrap();
736
737 assert!(aggregate(&comms, &[s1], &group_pk, msg).is_err());
739 }
740
741 #[test]
744 fn test_frost_keygen_deterministic() {
745 let secret = [0x42u8; 32];
746 let out1 = keygen::trusted_dealer_keygen(&secret, 2, 3).unwrap();
747 let out2 = keygen::trusted_dealer_keygen(&secret, 2, 3).unwrap();
748 assert_eq!(
749 out1.group_public_key.to_encoded_point(true).as_bytes(),
750 out2.group_public_key.to_encoded_point(true).as_bytes()
751 );
752 }
753
754 #[test]
757 fn test_identify_misbehaving_all_honest() {
758 let (kgen, group_pk) = setup_2_of_3();
759 let msg = b"identifiable abort honest";
760
761 let n1 = commit(&kgen.key_packages[0]).unwrap();
762 let n2 = commit(&kgen.key_packages[1]).unwrap();
763 let comms = vec![n1.commitments.clone(), n2.commitments.clone()];
764 let s1 = sign(&kgen.key_packages[0], n1, &comms, msg).unwrap();
765 let s2 = sign(&kgen.key_packages[1], n2, &comms, msg).unwrap();
766
767 let cheaters =
768 identify_misbehaving(&[s1, s2], &comms, &kgen.key_packages, &group_pk, msg).unwrap();
769 assert!(cheaters.is_empty(), "no cheaters expected");
770 }
771
772 #[test]
773 fn test_identify_misbehaving_tampered_share() {
774 let (kgen, group_pk) = setup_2_of_3();
775 let msg = b"identifiable abort tampered";
776
777 let n1 = commit(&kgen.key_packages[0]).unwrap();
778 let n2 = commit(&kgen.key_packages[1]).unwrap();
779 let comms = vec![n1.commitments.clone(), n2.commitments.clone()];
780 let s1 = sign(&kgen.key_packages[0], n1, &comms, msg).unwrap();
781 let s2 = sign(&kgen.key_packages[1], n2, &comms, msg).unwrap();
782
783 let tampered_s2 = SignatureShare {
785 identifier: s2.identifier,
786 share: s2.share + Scalar::ONE, };
788
789 let cheaters = identify_misbehaving(
790 &[s1, tampered_s2],
791 &comms,
792 &kgen.key_packages,
793 &group_pk,
794 msg,
795 )
796 .unwrap();
797 assert_eq!(
798 cheaters,
799 vec![2],
800 "participant 2 should be identified as cheater"
801 );
802 }
803
804 #[test]
805 fn test_identify_misbehaving_both_tampered() {
806 let (kgen, group_pk) = setup_2_of_3();
807 let msg = b"both bad";
808
809 let n1 = commit(&kgen.key_packages[0]).unwrap();
810 let n2 = commit(&kgen.key_packages[1]).unwrap();
811 let comms = vec![n1.commitments.clone(), n2.commitments.clone()];
812 let s1 = sign(&kgen.key_packages[0], n1, &comms, msg).unwrap();
813 let s2 = sign(&kgen.key_packages[1], n2, &comms, msg).unwrap();
814
815 let bad1 = SignatureShare {
816 identifier: s1.identifier,
817 share: Scalar::ZERO,
818 };
819 let bad2 = SignatureShare {
820 identifier: s2.identifier,
821 share: Scalar::ZERO,
822 };
823
824 let cheaters =
825 identify_misbehaving(&[bad1, bad2], &comms, &kgen.key_packages, &group_pk, msg)
826 .unwrap();
827 assert_eq!(cheaters.len(), 2, "both participants should be identified");
828 }
829}