commonware_cryptography/bls12381/primitives/ops/
threshold.rs

1//! Threshold signature operations for BLS12-381.
2//!
3//! This module provides functions for threshold signature schemes, including:
4//! - Partial signing and verification
5//! - Signature recovery from partial signatures
6//! - Batch verification of partial signatures
7//!
8//! Threshold signatures allow a group of participants to collectively sign a message
9//! where at least `t` out of `n` participants must contribute partial signatures.
10
11use super::{
12    super::{
13        group::Share,
14        sharing::Sharing,
15        variant::{PartialSignature, Variant},
16        Error,
17    },
18    batch,
19};
20#[cfg(not(feature = "std"))]
21use alloc::{vec, vec::Vec};
22use commonware_codec::Encode;
23use commonware_parallel::Strategy;
24use commonware_utils::{ordered::Map, union_unique, Faults, Participant};
25use rand_core::CryptoRngCore;
26
27/// Prepares partial signature evaluations for threshold recovery.
28fn prepare_evaluations<'a, V: Variant>(
29    threshold: u32,
30    partials: impl IntoIterator<Item = &'a PartialSignature<V>>,
31) -> Result<Map<Participant, V::Signature>, Error> {
32    let mut out = Map::from_iter_dedup(partials.into_iter().map(|eval| (eval.index, eval.value)));
33    let t = threshold as usize;
34    out.truncate(t);
35    if out.len() < t {
36        return Err(Error::NotEnoughPartialSignatures(t, out.len()));
37    }
38    Ok(out)
39}
40
41/// Signs the provided message with the key share.
42pub fn sign_message<V: Variant>(
43    share: &Share,
44    namespace: &[u8],
45    message: &[u8],
46) -> PartialSignature<V> {
47    let sig = super::sign_message::<V>(&share.private, namespace, message);
48
49    PartialSignature {
50        value: sig,
51        index: share.index,
52    }
53}
54
55/// Generates a proof of possession for the private key share.
56///
57/// This signs the *threshold* public key (not the share's individual public key)
58/// so that partial signatures can be recovered into a threshold signature
59/// verifiable with `ops::verify_proof_of_possession`.
60pub fn sign_proof_of_possession<V: Variant>(
61    sharing: &Sharing<V>,
62    share: &Share,
63    namespace: &[u8],
64) -> PartialSignature<V> {
65    let sig = super::sign::<V>(
66        &share.private,
67        V::PROOF_OF_POSSESSION,
68        &union_unique(namespace, &sharing.public().encode()),
69    );
70
71    PartialSignature {
72        value: sig,
73        index: share.index,
74    }
75}
76
77/// Verifies the partial signature against the public polynomial.
78///
79/// # Warning
80///
81/// This function assumes a group check was already performed on `signature`.
82pub fn verify_message<V: Variant>(
83    sharing: &Sharing<V>,
84    namespace: &[u8],
85    message: &[u8],
86    partial: &PartialSignature<V>,
87) -> Result<(), Error> {
88    super::verify_message::<V>(
89        &sharing.partial_public(partial.index)?,
90        namespace,
91        message,
92        &partial.value,
93    )
94}
95
96/// Verifies the proof of possession for the provided public polynomial.
97///
98/// # Warning
99///
100/// This function assumes a group check was already performed on `signature`.
101pub fn verify_proof_of_possession<V: Variant>(
102    sharing: &Sharing<V>,
103    namespace: &[u8],
104    partial: &PartialSignature<V>,
105) -> Result<(), Error> {
106    super::verify::<V>(
107        &sharing.partial_public(partial.index)?,
108        V::PROOF_OF_POSSESSION,
109        &union_unique(namespace, &sharing.public().encode()),
110        &partial.value,
111    )
112}
113
114/// Verifies multiple partial signatures over multiple messages from a single signer.
115///
116/// Randomness ensures batch verification returns the same result as checking each signature
117/// individually.
118///
119/// Each entry is a tuple of (namespace, message, partial_signature).
120///
121/// # Warning
122///
123/// This function assumes a group check was already performed on each `signature`.
124/// Duplicate messages are safe because random scalar weights ensure each
125/// (message, signature) pair is verified independently.
126pub fn batch_verify_same_signer<'a, R, V, I>(
127    rng: &mut R,
128    sharing: &Sharing<V>,
129    index: Participant,
130    entries: I,
131    strategy: &impl Strategy,
132) -> Result<(), Error>
133where
134    R: CryptoRngCore,
135    V: Variant,
136    I: IntoIterator<Item = &'a (&'a [u8], &'a [u8], PartialSignature<V>)>,
137{
138    // Verify all signatures have the correct index and build combined entries
139    let combined: Vec<_> = entries
140        .into_iter()
141        .map(|(ns, msg, ps)| {
142            if ps.index != index {
143                Err(Error::InvalidSignature)
144            } else {
145                Ok((*ns, *msg, ps.value))
146            }
147        })
148        .collect::<Result<_, _>>()?;
149
150    let public = sharing.partial_public(index)?;
151
152    batch::verify_same_signer::<_, V, _>(rng, &public, &combined, strategy)
153}
154
155/// Verify a list of [PartialSignature]s over the same message from different signers,
156/// ensuring each individual signature is valid (see [`batch`] for more details on how
157/// this works).
158///
159/// Returns the indices of any invalid signatures found.
160///
161/// # Performance
162///
163/// Uses bisection to identify which signatures are invalid. In the worst case, this can require
164/// more verifications than checking each signature individually. If an invalid signer is detected,
165/// consider blocking them from participating in future batches to better amortize the cost of this
166/// search.
167fn batch_verify_same_message_bisect<'a, R, V>(
168    rng: &mut R,
169    pending: &[(V::Public, &'a PartialSignature<V>)],
170    namespace: &[u8],
171    message: &[u8],
172    strategy: &impl Strategy,
173) -> Vec<&'a PartialSignature<V>>
174where
175    R: CryptoRngCore,
176    V: Variant,
177{
178    // Convert to the format expected by verify_same_message
179    let entries: Vec<(V::Public, V::Signature)> = pending
180        .iter()
181        .map(|(pk, partial)| (*pk, partial.value))
182        .collect();
183
184    // Use the generic verification function
185    let invalid_indices =
186        batch::verify_same_message::<_, V>(rng, namespace, message, &entries, strategy);
187
188    // Map indices back to PartialSignature references
189    invalid_indices
190        .into_iter()
191        .map(|idx| pending[idx].1)
192        .collect()
193}
194
195/// Batch verifies multiple [PartialSignature]s over the same message, returning
196/// any invalid signatures found.
197///
198/// Randomness ensures batch verification returns the same result as checking each signature
199/// individually.
200///
201/// # Warning
202///
203/// This function assumes a group check was already performed on each `signature`.
204/// Duplicate signers are safe because random scalar weights ensure each
205/// (public key, signature) pair is verified independently.
206pub fn batch_verify_same_message<'a, R, V, I>(
207    rng: &mut R,
208    sharing: &Sharing<V>,
209    namespace: &[u8],
210    message: &[u8],
211    partials: I,
212    strategy: &impl Strategy,
213) -> Result<(), Vec<&'a PartialSignature<V>>>
214where
215    R: CryptoRngCore,
216    V: Variant,
217    I: IntoIterator<Item = &'a PartialSignature<V>>,
218{
219    let partials = partials.into_iter();
220    let mut pending = Vec::with_capacity(partials.size_hint().0);
221    let mut invalid = Vec::new();
222    for partial in partials {
223        match sharing.partial_public(partial.index) {
224            Ok(p) => pending.push((p, partial)),
225            Err(_) => invalid.push(partial),
226        }
227    }
228
229    // Find any invalid partial signatures
230    let bad = batch_verify_same_message_bisect::<_, V>(
231        rng,
232        pending.as_slice(),
233        namespace,
234        message,
235        strategy,
236    );
237    invalid.extend(bad);
238
239    if invalid.is_empty() {
240        Ok(())
241    } else {
242        Err(invalid)
243    }
244}
245
246/// Recovers a signature from at least `threshold` partial signatures.
247///
248/// # Determinism
249///
250/// Signatures recovered by this function are deterministic and are safe
251/// to use in a consensus-critical context.
252///
253/// # Warning
254///
255/// This function assumes that each partial signature is unique.
256pub fn recover<'a, V, I, M>(
257    sharing: &Sharing<V>,
258    partials: I,
259    strategy: &impl Strategy,
260) -> Result<V::Signature, Error>
261where
262    V: Variant,
263    I: IntoIterator<Item = &'a PartialSignature<V>>,
264    V::Signature: 'a,
265    M: Faults,
266{
267    let evals = prepare_evaluations::<V>(sharing.required::<M>(), partials)?;
268    sharing
269        .interpolator(evals.keys())?
270        .interpolate(&evals, strategy)
271        .ok_or(Error::InvalidRecovery)
272}
273
274/// Recovers multiple signatures from multiple sets of at least `threshold`
275/// partial signatures.
276///
277/// # Determinism
278///
279/// Signatures recovered by this function are deterministic and are safe
280/// to use in a consensus-critical context.
281///
282/// # Warning
283///
284/// This function assumes that each partial signature is unique and that
285/// each set of partial signatures has the same indices.
286pub fn recover_multiple<'a, V, I, M>(
287    sharing: &Sharing<V>,
288    many_evals: Vec<I>,
289    strategy: &impl Strategy,
290) -> Result<Vec<V::Signature>, Error>
291where
292    V: Variant,
293    I: IntoIterator<Item = &'a PartialSignature<V>>,
294    V::Signature: 'a,
295    M: Faults,
296{
297    let prepared_evals = many_evals
298        .into_iter()
299        .map(|evals| prepare_evaluations::<V>(sharing.required::<M>(), evals))
300        .collect::<Result<Vec<_>, _>>()?;
301    let Some(first_eval) = prepared_evals.first() else {
302        return Ok(Vec::new());
303    };
304    if !prepared_evals
305        .iter()
306        .skip(1)
307        .all(|other_eval| other_eval.keys() == first_eval.keys())
308    {
309        return Err(Error::InvalidIndex);
310    }
311
312    let interpolator = sharing.interpolator(first_eval.keys())?;
313    let results: Vec<_> = strategy.map_init_collect_vec(
314        &prepared_evals,
315        || &interpolator,
316        |interpolator, evals| {
317            interpolator
318                .interpolate(evals, strategy)
319                .ok_or(Error::InvalidRecovery)
320        },
321    );
322    results.into_iter().collect()
323}
324
325/// Recovers a pair of signatures from two sets of at least `threshold` partial signatures.
326///
327/// This is just a wrapper around `recover_multiple`.
328pub fn recover_pair<'a, V, I, M>(
329    sharing: &Sharing<V>,
330    first: I,
331    second: I,
332    strategy: &impl Strategy,
333) -> Result<(V::Signature, V::Signature), Error>
334where
335    V: Variant,
336    I: IntoIterator<Item = &'a PartialSignature<V>>,
337    V::Signature: 'a,
338    M: Faults,
339{
340    let mut sigs = recover_multiple::<V, _, M>(sharing, vec![first, second], strategy)?;
341    let second_sig = sigs.pop().unwrap();
342    let first_sig = sigs.pop().unwrap();
343    Ok((first_sig, second_sig))
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349    use crate::bls12381::{
350        dkg,
351        primitives::{
352            group::{Private, Scalar, G1_MESSAGE, G2_MESSAGE},
353            ops::{self, hash_with_namespace},
354            variant::{MinPk, MinSig},
355        },
356    };
357    use blst::BLST_ERROR;
358    use commonware_codec::Encode;
359    use commonware_math::algebra::{CryptoGroup, Field as _, Random, Ring, Space};
360    use commonware_parallel::{Rayon, Sequential};
361    use commonware_utils::{test_rng, union_unique, Faults, N3f1, NZUsize, NZU32};
362
363    fn blst_verify_proof_of_possession<V: Variant>(
364        public: &V::Public,
365        namespace: &[u8],
366        signature: &V::Signature,
367    ) -> Result<(), BLST_ERROR> {
368        let msg = union_unique(namespace, &public.encode());
369        match V::MESSAGE {
370            G1_MESSAGE => {
371                let public = blst::min_sig::PublicKey::from_bytes(&public.encode()).unwrap();
372                let signature = blst::min_sig::Signature::from_bytes(&signature.encode()).unwrap();
373                match signature.verify(true, &msg, V::PROOF_OF_POSSESSION, &[], &public, true) {
374                    BLST_ERROR::BLST_SUCCESS => Ok(()),
375                    e => Err(e),
376                }
377            }
378            G2_MESSAGE => {
379                let public = blst::min_pk::PublicKey::from_bytes(&public.encode()).unwrap();
380                let signature = blst::min_pk::Signature::from_bytes(&signature.encode()).unwrap();
381                match signature.verify(true, &msg, V::PROOF_OF_POSSESSION, &[], &public, true) {
382                    BLST_ERROR::BLST_SUCCESS => Ok(()),
383                    e => Err(e),
384                }
385            }
386            _ => panic!("Unsupported Variant"),
387        }
388    }
389
390    fn threshold_proof_of_possession<V: Variant>() {
391        let n = 5;
392        let mut rng = test_rng();
393        let namespace = b"test";
394        let (sharing, shares) =
395            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(n));
396        let partials: Vec<_> = shares
397            .iter()
398            .map(|s| sign_proof_of_possession::<V>(&sharing, s, namespace))
399            .collect();
400        for p in &partials {
401            verify_proof_of_possession::<V>(&sharing, namespace, p)
402                .expect("signature should be valid");
403        }
404        let threshold_sig = recover::<V, _, N3f1>(&sharing, &partials, &Sequential).unwrap();
405        let threshold_pub = sharing.public();
406
407        ops::verify_proof_of_possession::<V>(threshold_pub, namespace, &threshold_sig)
408            .expect("signature should be valid");
409
410        blst_verify_proof_of_possession::<V>(threshold_pub, namespace, &threshold_sig)
411            .expect("signature should be valid");
412    }
413
414    #[test]
415    fn test_threshold_proof_of_possession() {
416        threshold_proof_of_possession::<MinPk>();
417        threshold_proof_of_possession::<MinSig>();
418    }
419
420    fn blst_verify_message<V: Variant>(
421        public: &V::Public,
422        msg: &[u8],
423        signature: &V::Signature,
424    ) -> Result<(), BLST_ERROR> {
425        match V::MESSAGE {
426            G1_MESSAGE => {
427                let public = blst::min_sig::PublicKey::from_bytes(&public.encode()).unwrap();
428                let signature = blst::min_sig::Signature::from_bytes(&signature.encode()).unwrap();
429                match signature.verify(true, msg, V::MESSAGE, &[], &public, true) {
430                    BLST_ERROR::BLST_SUCCESS => Ok(()),
431                    e => Err(e),
432                }
433            }
434            G2_MESSAGE => {
435                let public = blst::min_pk::PublicKey::from_bytes(&public.encode()).unwrap();
436                let signature = blst::min_pk::Signature::from_bytes(&signature.encode()).unwrap();
437                match signature.verify(true, msg, V::MESSAGE, &[], &public, true) {
438                    BLST_ERROR::BLST_SUCCESS => Ok(()),
439                    e => Err(e),
440                }
441            }
442            _ => panic!("Unsupported Variant"),
443        }
444    }
445
446    fn threshold_message<V: Variant>() {
447        let n = 5;
448        let mut rng = test_rng();
449        let (sharing, shares) =
450            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(n));
451        let msg = &[1, 9, 6, 9];
452        let namespace = b"test";
453        let partials: Vec<_> = shares
454            .iter()
455            .map(|s| sign_message::<V>(s, namespace, msg))
456            .collect();
457        for p in &partials {
458            verify_message::<V>(&sharing, namespace, msg, p).expect("signature should be valid");
459        }
460        let threshold_sig = recover::<V, _, N3f1>(&sharing, &partials, &Sequential).unwrap();
461        let threshold_pub = sharing.public();
462
463        ops::verify_message::<V>(threshold_pub, namespace, msg, &threshold_sig)
464            .expect("signature should be valid");
465
466        let payload = union_unique(namespace, msg);
467        blst_verify_message::<V>(threshold_pub, &payload, &threshold_sig)
468            .expect("signature should be valid");
469    }
470
471    #[test]
472    fn test_threshold_message() {
473        threshold_message::<MinPk>();
474        threshold_message::<MinSig>();
475    }
476
477    fn batch_verify_same_signer_correct<V: Variant>() {
478        let mut rng = test_rng();
479        let n = 5;
480        let (public, shares) =
481            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(n));
482
483        let signer = &shares[0];
484
485        let messages: &[(&[u8], &[u8])] = &[(b"ns", b"msg1"), (b"ns", b"msg2"), (b"ns", b"msg3")];
486        let entries: Vec<_> = messages
487            .iter()
488            .map(|(ns, msg)| (*ns, *msg, sign_message::<V>(signer, ns, msg)))
489            .collect();
490        batch_verify_same_signer::<_, V, _>(&mut rng, &public, signer.index, &entries, &Sequential)
491            .expect("Verification with namespaced messages should succeed");
492
493        let strategy = Rayon::new(NZUsize!(4)).unwrap();
494        batch_verify_same_signer::<_, V, _>(&mut rng, &public, signer.index, &entries, &strategy)
495            .expect("Verification with parallel strategy should succeed");
496
497        let messages_alt_ns: &[(&[u8], &[u8])] =
498            &[(b"alt", b"msg1"), (b"alt", b"msg2"), (b"alt", b"msg3")];
499        let entries_alt_ns: Vec<_> = messages_alt_ns
500            .iter()
501            .map(|(ns, msg)| (*ns, *msg, sign_message::<V>(signer, ns, msg)))
502            .collect();
503        batch_verify_same_signer::<_, V, _>(
504            &mut rng,
505            &public,
506            signer.index,
507            &entries_alt_ns,
508            &Sequential,
509        )
510        .expect("Verification with alternate namespace messages should succeed");
511
512        let messages_mixed: &[(&[u8], &[u8])] =
513            &[(b"ns1", b"msg1"), (b"ns2", b"msg2"), (b"ns3", b"msg3")];
514        let entries_mixed: Vec<_> = messages_mixed
515            .iter()
516            .map(|(ns, msg)| (*ns, *msg, sign_message::<V>(signer, ns, msg)))
517            .collect();
518        batch_verify_same_signer::<_, V, _>(
519            &mut rng,
520            &public,
521            signer.index,
522            &entries_mixed,
523            &Sequential,
524        )
525        .expect("Verification with mixed namespaces should succeed");
526
527        assert!(matches!(
528            batch_verify_same_signer::<_, V, _>(
529                &mut rng,
530                &public,
531                Participant::new(1),
532                &entries,
533                &Sequential
534            ),
535            Err(Error::InvalidSignature)
536        ));
537
538        let mut entries_swapped = entries.clone();
539        let temp_sig = entries_swapped[0].2.clone();
540        entries_swapped[0].2 = entries_swapped[1].2.clone();
541        entries_swapped[1].2 = temp_sig;
542        assert!(
543            batch_verify_same_signer::<_, V, _>(
544                &mut rng,
545                &public,
546                signer.index,
547                &entries_swapped,
548                &Sequential,
549            )
550            .is_err(),
551            "Verification with swapped signatures should fail"
552        );
553
554        let signer2 = &shares[1];
555        let partial2 = sign_message::<V>(signer2, messages[0].0, messages[0].1);
556        let mut entries_mixed_signers = entries;
557        entries_mixed_signers[0].2 = partial2;
558        assert!(matches!(
559            batch_verify_same_signer::<_, V, _>(
560                &mut rng,
561                &public,
562                signer.index,
563                &entries_mixed_signers,
564                &Sequential,
565            ),
566            Err(Error::InvalidSignature)
567        ));
568    }
569
570    #[test]
571    fn test_batch_verify_same_signer() {
572        batch_verify_same_signer_correct::<MinPk>();
573        batch_verify_same_signer_correct::<MinSig>();
574    }
575
576    fn recover_with_weights_correct<V: Variant>() {
577        let mut rng = test_rng();
578        let (n, t) = (6, N3f1::quorum(6));
579        let (sharing, shares) =
580            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(n));
581
582        let partials: Vec<_> = shares
583            .iter()
584            .take(t as usize)
585            .map(|s| sign_message::<V>(s, b"test", b"payload"))
586            .collect();
587
588        let sig1 = recover::<V, _, N3f1>(&sharing, &partials, &Sequential).unwrap();
589
590        ops::verify_message::<V>(sharing.public(), b"test", b"payload", &sig1).unwrap();
591    }
592
593    #[test]
594    fn test_recover_with_weights() {
595        recover_with_weights_correct::<MinPk>();
596        recover_with_weights_correct::<MinSig>();
597    }
598
599    fn recover_multiple_test<V: Variant>() {
600        let mut rng = test_rng();
601        let (n, t) = (6, N3f1::quorum(6));
602        let (sharing, shares) =
603            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(n));
604
605        let partials_1: Vec<_> = shares
606            .iter()
607            .take(t as usize)
608            .map(|s| sign_message::<V>(s, b"test", b"payload1"))
609            .collect();
610        let partials_2: Vec<_> = shares
611            .iter()
612            .take(t as usize)
613            .map(|s| sign_message::<V>(s, b"test", b"payload2"))
614            .collect();
615
616        let (sig_1, sig_2) =
617            recover_pair::<V, _, N3f1>(&sharing, &partials_1, &partials_2, &Sequential).unwrap();
618
619        ops::verify_message::<V>(sharing.public(), b"test", b"payload1", &sig_1).unwrap();
620        ops::verify_message::<V>(sharing.public(), b"test", b"payload2", &sig_2).unwrap();
621
622        let parallel = Rayon::new(NZUsize!(4)).unwrap();
623        let (sig_1_par, sig_2_par) =
624            recover_pair::<V, _, N3f1>(&sharing, &partials_1, &partials_2, &parallel).unwrap();
625
626        assert_eq!(sig_1, sig_1_par);
627        assert_eq!(sig_2, sig_2_par);
628    }
629
630    #[test]
631    fn test_recover_multiple() {
632        recover_multiple_test::<MinPk>();
633        recover_multiple_test::<MinSig>();
634    }
635
636    fn recover_with_verification<V: Variant>() {
637        let (n, _) = (5, 4);
638        let mut rng = test_rng();
639
640        let (sharing, shares) =
641            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(n));
642
643        let namespace = b"test";
644        let msg = b"hello";
645        let partials = shares
646            .iter()
647            .map(|s| sign_message::<V>(s, namespace, msg))
648            .collect::<Vec<_>>();
649
650        partials.iter().for_each(|partial| {
651            verify_message::<V>(&sharing, namespace, msg, partial).unwrap();
652        });
653
654        let threshold_sig = recover::<V, _, N3f1>(&sharing, &partials, &Sequential).unwrap();
655        ops::verify_message::<V>(sharing.public(), namespace, msg, &threshold_sig).unwrap();
656    }
657
658    #[test]
659    fn test_recover_with_verification() {
660        recover_with_verification::<MinPk>();
661        recover_with_verification::<MinSig>();
662    }
663
664    fn recover_bad_namespace<V: Variant>() {
665        let n = 5;
666        let mut rng = test_rng();
667
668        let (sharing, shares) =
669            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(n));
670
671        let namespace = b"test";
672        let msg = b"hello";
673        let partials = shares
674            .iter()
675            .map(|s| sign_message::<V>(s, namespace, msg))
676            .collect::<Vec<_>>();
677
678        let namespace = b"bad";
679        partials.iter().for_each(|partial| {
680            assert!(matches!(
681                verify_message::<V>(&sharing, namespace, msg, partial).unwrap_err(),
682                Error::InvalidSignature
683            ));
684        });
685
686        let threshold_sig = recover::<V, _, N3f1>(&sharing, &partials, &Sequential).unwrap();
687        assert!(matches!(
688            ops::verify_message::<V>(sharing.public(), namespace, msg, &threshold_sig).unwrap_err(),
689            Error::InvalidSignature
690        ));
691    }
692
693    #[test]
694    fn test_recover_bad_namespace() {
695        recover_bad_namespace::<MinPk>();
696        recover_bad_namespace::<MinSig>();
697    }
698
699    fn recover_insufficient<V: Variant>() {
700        let (n, t) = (5, 4);
701        let mut rng = test_rng();
702
703        let (group, shares) =
704            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(n));
705
706        let shares = shares.into_iter().take(t as usize - 1).collect::<Vec<_>>();
707
708        let namespace = b"test";
709        let msg = b"hello";
710        let partials = shares
711            .iter()
712            .map(|s| sign_message::<V>(s, namespace, msg))
713            .collect::<Vec<_>>();
714
715        partials.iter().for_each(|partial| {
716            verify_message::<V>(&group, namespace, msg, partial).unwrap();
717        });
718
719        assert!(matches!(
720            recover::<V, _, N3f1>(&group, &partials, &Sequential).unwrap_err(),
721            Error::NotEnoughPartialSignatures(4, 3)
722        ));
723    }
724
725    #[test]
726    fn test_recover_insufficient() {
727        recover_insufficient::<MinPk>();
728        recover_insufficient::<MinSig>();
729    }
730
731    fn recover_bad_share<V: Variant>() {
732        let n = 5;
733        let mut rng = test_rng();
734
735        let (sharing, mut shares) =
736            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(n));
737
738        let share = shares.get_mut(3).unwrap();
739        share.private = Private::random(&mut rng);
740
741        let namespace = b"test";
742        let msg = b"hello";
743        let partials = shares
744            .iter()
745            .map(|s| sign_message::<V>(s, namespace, msg))
746            .collect::<Vec<_>>();
747
748        partials.iter().for_each(|partial| {
749            verify_message::<V>(&sharing, namespace, msg, partial).unwrap();
750        });
751
752        let threshold_sig = recover::<V, _, N3f1>(&sharing, &partials, &Sequential).unwrap();
753        ops::verify_message::<V>(sharing.public(), namespace, msg, &threshold_sig).unwrap();
754    }
755
756    #[test]
757    #[should_panic(expected = "InvalidSignature")]
758    fn test_recover_bad_share() {
759        recover_bad_share::<MinPk>();
760        recover_bad_share::<MinSig>();
761    }
762
763    #[test]
764    fn test_batch_verify_same_message() {
765        let mut rng = test_rng();
766        let n = 5;
767        let (sharing, shares) =
768            dkg::deal_anonymous::<MinSig, N3f1>(&mut rng, Default::default(), NZU32!(n));
769        let namespace = b"test";
770        let msg = b"hello";
771
772        let partials: Vec<_> = shares
773            .iter()
774            .map(|s| sign_message::<MinSig>(s, namespace, msg))
775            .collect();
776        sharing.precompute_partial_publics();
777
778        batch_verify_same_message::<_, MinSig, _>(
779            &mut rng,
780            &sharing,
781            namespace,
782            msg,
783            &partials,
784            &Sequential,
785        )
786        .expect("all signatures should be valid");
787    }
788
789    #[test]
790    fn test_batch_verify_same_message_one_invalid() {
791        let mut rng = test_rng();
792        let n = 5;
793        let (sharing, mut shares) =
794            dkg::deal_anonymous::<MinSig, N3f1>(&mut rng, Default::default(), NZU32!(n));
795        let namespace = b"test";
796        let msg = b"hello";
797
798        let corrupted_index = 1;
799        shares[corrupted_index].private = Private::random(&mut rng);
800
801        let partials: Vec<_> = shares
802            .iter()
803            .map(|s| sign_message::<MinSig>(s, namespace, msg))
804            .collect();
805
806        sharing.precompute_partial_publics();
807        let result = batch_verify_same_message::<_, MinSig, _>(
808            &mut rng,
809            &sharing,
810            namespace,
811            msg,
812            &partials,
813            &Sequential,
814        );
815        match result {
816            Err(invalid_sigs) => {
817                assert_eq!(
818                    invalid_sigs.len(),
819                    1,
820                    "Exactly one signature should be invalid"
821                );
822                assert_eq!(
823                    invalid_sigs[0].index,
824                    Participant::from_usize(corrupted_index),
825                    "The invalid signature should match the corrupted share's index"
826                );
827            }
828            _ => panic!("Expected an error with invalid signatures"),
829        }
830    }
831
832    #[test]
833    fn test_batch_verify_same_message_many_invalid() {
834        let mut rng = test_rng();
835        let n = 6;
836        let (sharing, mut shares) =
837            dkg::deal_anonymous::<MinSig, N3f1>(&mut rng, Default::default(), NZU32!(n));
838        let namespace = b"test";
839        let msg = b"hello";
840
841        let corrupted_indices = vec![1, 3];
842        for &idx in &corrupted_indices {
843            shares[idx].private = Private::random(&mut rng);
844        }
845
846        let partials: Vec<_> = shares
847            .iter()
848            .map(|s| sign_message::<MinSig>(s, namespace, msg))
849            .collect();
850        sharing.precompute_partial_publics();
851
852        let result = batch_verify_same_message::<_, MinSig, _>(
853            &mut rng,
854            &sharing,
855            namespace,
856            msg,
857            &partials,
858            &Sequential,
859        );
860        match result {
861            Err(invalid_sigs) => {
862                assert_eq!(
863                    invalid_sigs.len(),
864                    corrupted_indices.len(),
865                    "Number of invalid signatures should match number of corrupted shares"
866                );
867                let invalid_indices: Vec<Participant> =
868                    invalid_sigs.iter().map(|sig| sig.index).collect();
869                let expected_indices: Vec<Participant> = corrupted_indices
870                    .iter()
871                    .map(|&i| Participant::from_usize(i))
872                    .collect();
873                assert_eq!(
874                    invalid_indices, expected_indices,
875                    "Invalid signature indices should match corrupted share indices"
876                );
877            }
878            _ => panic!("Expected an error with invalid signatures"),
879        }
880    }
881
882    #[test]
883    fn test_batch_verify_same_message_out_of_range() {
884        let mut rng = test_rng();
885        let n = 5;
886        let (sharing, shares) =
887            dkg::deal_anonymous::<MinSig, N3f1>(&mut rng, Default::default(), NZU32!(n));
888        let namespace = b"test";
889        let msg = b"hello";
890
891        let mut partials: Vec<_> = shares
892            .iter()
893            .map(|s| sign_message::<MinSig>(s, namespace, msg))
894            .collect();
895
896        partials[0].index = Participant::new(100);
897
898        sharing.precompute_partial_publics();
899        let result = batch_verify_same_message::<_, MinSig, _>(
900            &mut rng,
901            &sharing,
902            namespace,
903            msg,
904            &partials,
905            &Sequential,
906        );
907        match result {
908            Err(invalid_sigs) => {
909                assert_eq!(
910                    invalid_sigs.len(),
911                    1,
912                    "Exactly one signature should be invalid"
913                );
914                assert_eq!(
915                    invalid_sigs[0].index,
916                    Participant::new(100),
917                    "The invalid signature should match the corrupted index"
918                );
919            }
920            _ => panic!("Expected an error with invalid signatures"),
921        }
922    }
923
924    #[test]
925    fn test_batch_verify_same_message_single() {
926        let mut rng = test_rng();
927        let (sharing, shares) =
928            dkg::deal_anonymous::<MinSig, N3f1>(&mut rng, Default::default(), NZU32!(1));
929        let namespace = b"test";
930        let msg = b"hello";
931
932        let partials: Vec<_> = shares
933            .iter()
934            .map(|s| sign_message::<MinSig>(s, namespace, msg))
935            .collect();
936
937        batch_verify_same_message::<_, MinSig, _>(
938            &mut rng,
939            &sharing,
940            namespace,
941            msg,
942            &partials,
943            &Sequential,
944        )
945        .expect("signature should be valid");
946    }
947
948    #[test]
949    fn test_batch_verify_same_message_single_invalid() {
950        let mut rng = test_rng();
951        let (sharing, mut shares) =
952            dkg::deal_anonymous::<MinSig, N3f1>(&mut rng, Default::default(), NZU32!(1));
953        let namespace = b"test";
954        let msg = b"hello";
955
956        shares[0].private = Private::random(&mut rng);
957
958        let partials: Vec<_> = shares
959            .iter()
960            .map(|s| sign_message::<MinSig>(s, namespace, msg))
961            .collect();
962
963        let result = batch_verify_same_message::<_, MinSig, _>(
964            &mut rng,
965            &sharing,
966            namespace,
967            msg,
968            &partials,
969            &Sequential,
970        );
971        match result {
972            Err(invalid_sigs) => {
973                assert_eq!(invalid_sigs.len(), 1);
974                assert_eq!(invalid_sigs[0].index, Participant::new(0));
975            }
976            _ => panic!("Expected an error with invalid signatures"),
977        }
978    }
979
980    #[test]
981    fn test_batch_verify_same_message_last_invalid() {
982        let mut rng = test_rng();
983        let n = 5;
984        let (sharing, mut shares) =
985            dkg::deal_anonymous::<MinSig, N3f1>(&mut rng, Default::default(), NZU32!(n));
986        let namespace = b"test";
987        let msg = b"hello";
988
989        let corrupted_index = n - 1;
990        shares[corrupted_index as usize].private = Private::random(&mut rng);
991
992        let partials: Vec<_> = shares
993            .iter()
994            .map(|s| sign_message::<MinSig>(s, namespace, msg))
995            .collect();
996
997        let result = batch_verify_same_message::<_, MinSig, _>(
998            &mut rng,
999            &sharing,
1000            namespace,
1001            msg,
1002            &partials,
1003            &Sequential,
1004        );
1005        match result {
1006            Err(invalid_sigs) => {
1007                assert_eq!(invalid_sigs.len(), 1);
1008                assert_eq!(invalid_sigs[0].index, Participant::new(corrupted_index));
1009            }
1010            _ => panic!("Expected an error with invalid signatures"),
1011        }
1012    }
1013
1014    fn threshold_derive_missing_partials<V: Variant>() {
1015        fn lagrange_coeff(
1016            scalars: &[Scalar],
1017            eval_x: Participant,
1018            i_x: Participant,
1019            x_coords: &[Participant],
1020        ) -> Scalar {
1021            let mut num = Scalar::one();
1022            let mut den = Scalar::one();
1023
1024            let eval_x = scalars[usize::from(eval_x)].clone();
1025            let xi = scalars[usize::from(i_x)].clone();
1026
1027            for &j_x in x_coords {
1028                if i_x == j_x {
1029                    continue;
1030                }
1031
1032                let xj = scalars[usize::from(j_x)].clone();
1033
1034                let mut term = eval_x.clone();
1035                term -= &xj;
1036                num *= &term;
1037
1038                let mut diff = xi.clone();
1039                diff -= &xj;
1040                den *= &diff;
1041            }
1042
1043            num *= &den.inv();
1044            num
1045        }
1046
1047        let mut rng = test_rng();
1048        let (n, t) = (NZU32!(5), N3f1::quorum(5));
1049        let (public, shares) = dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), n);
1050        let scalars = public.mode().all_scalars(n).collect::<Vec<_>>();
1051
1052        let namespace = b"test";
1053        let msg = b"hello";
1054        let all_partials: Vec<_> = shares
1055            .iter()
1056            .map(|s| sign_message::<V>(s, namespace, msg))
1057            .collect();
1058
1059        let recovery_partials: Vec<_> = all_partials.iter().take(t as usize).collect();
1060        let recovery_indices: Vec<Participant> =
1061            recovery_partials.iter().map(|p| p.index).collect();
1062
1063        for target in &shares {
1064            let target = target.index;
1065
1066            let weights: Vec<Scalar> = recovery_indices
1067                .iter()
1068                .map(|&recovery_index| {
1069                    lagrange_coeff(&scalars, target, recovery_index, &recovery_indices)
1070                })
1071                .collect();
1072
1073            let points: Vec<_> = recovery_partials.iter().map(|p| p.value).collect();
1074            let derived =
1075                <<V as Variant>::Signature as Space<Scalar>>::msm(&points, &weights, &Sequential);
1076            let derived = PartialSignature {
1077                index: target,
1078                value: derived,
1079            };
1080
1081            verify_message::<V>(&public, namespace, msg, &derived)
1082                .expect("derived signature should be valid");
1083
1084            let original = all_partials.iter().find(|p| p.index == target).unwrap();
1085            assert_eq!(derived.value, original.value);
1086        }
1087    }
1088
1089    #[test]
1090    fn test_threshold_derive_missing_partials() {
1091        threshold_derive_missing_partials::<MinPk>();
1092        threshold_derive_missing_partials::<MinSig>();
1093    }
1094
1095    fn batch_verify_same_message_rejects_malleability<V: Variant>() {
1096        let mut rng = test_rng();
1097        let n = 5;
1098        let (sharing, shares) =
1099            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(n));
1100        let namespace = b"test";
1101        let msg = b"message";
1102
1103        let partial1 = sign_message::<V>(&shares[0], namespace, msg);
1104        let partial2 = sign_message::<V>(&shares[1], namespace, msg);
1105
1106        verify_message::<V>(&sharing, namespace, msg, &partial1).expect("partial1 should be valid");
1107        verify_message::<V>(&sharing, namespace, msg, &partial2).expect("partial2 should be valid");
1108
1109        let random_scalar = Scalar::random(&mut rng);
1110        let delta = V::Signature::generator() * &random_scalar;
1111        let forged_partial1 = PartialSignature {
1112            index: partial1.index,
1113            value: partial1.value - &delta,
1114        };
1115        let forged_partial2 = PartialSignature {
1116            index: partial2.index,
1117            value: partial2.value + &delta,
1118        };
1119
1120        assert!(
1121            verify_message::<V>(&sharing, namespace, msg, &forged_partial1).is_err(),
1122            "forged partial1 should be invalid individually"
1123        );
1124        assert!(
1125            verify_message::<V>(&sharing, namespace, msg, &forged_partial2).is_err(),
1126            "forged partial2 should be invalid individually"
1127        );
1128
1129        let forged_sum = forged_partial1.value + &forged_partial2.value;
1130        let valid_sum = partial1.value + &partial2.value;
1131        assert_eq!(
1132            forged_sum, valid_sum,
1133            "signature value sums should be equal"
1134        );
1135
1136        let pk1 = sharing.partial_public(partial1.index).unwrap();
1137        let pk2 = sharing.partial_public(partial2.index).unwrap();
1138        let pk_sum = pk1 + &pk2;
1139        let hm = hash_with_namespace::<V>(V::MESSAGE, namespace, msg);
1140        V::verify(&pk_sum, &hm, &forged_sum)
1141            .expect("vulnerable naive verification accepts forged aggregate");
1142
1143        let forged_partials = [forged_partial1, forged_partial2];
1144        let result = batch_verify_same_message::<_, V, _>(
1145            &mut rng,
1146            &sharing,
1147            namespace,
1148            msg,
1149            &forged_partials,
1150            &Sequential,
1151        );
1152        assert!(
1153            result.is_err(),
1154            "secure function should reject forged partial signatures"
1155        );
1156
1157        let valid_partials = [partial1, partial2];
1158        batch_verify_same_message::<_, V, _>(
1159            &mut rng,
1160            &sharing,
1161            namespace,
1162            msg,
1163            &valid_partials,
1164            &Sequential,
1165        )
1166        .expect("secure function should accept valid partial signatures");
1167    }
1168
1169    #[test]
1170    fn test_batch_verify_same_message_rejects_malleability() {
1171        batch_verify_same_message_rejects_malleability::<MinPk>();
1172        batch_verify_same_message_rejects_malleability::<MinSig>();
1173    }
1174
1175    fn batch_verify_same_signer_rejects_malleability<V: Variant>() {
1176        let mut rng = test_rng();
1177        let n = 5;
1178        let (sharing, shares) =
1179            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(n));
1180        let namespace: &[u8] = b"test";
1181        let msg1: &[u8] = b"message 1";
1182        let msg2: &[u8] = b"message 2";
1183
1184        let signer = &shares[0];
1185        let partial1 = sign_message::<V>(signer, namespace, msg1);
1186        let partial2 = sign_message::<V>(signer, namespace, msg2);
1187
1188        verify_message::<V>(&sharing, namespace, msg1, &partial1)
1189            .expect("partial1 should be valid");
1190        verify_message::<V>(&sharing, namespace, msg2, &partial2)
1191            .expect("partial2 should be valid");
1192
1193        let random_scalar = Scalar::random(&mut rng);
1194        let delta = V::Signature::generator() * &random_scalar;
1195        let forged_partial1 = PartialSignature {
1196            index: partial1.index,
1197            value: partial1.value - &delta,
1198        };
1199        let forged_partial2 = PartialSignature {
1200            index: partial2.index,
1201            value: partial2.value + &delta,
1202        };
1203
1204        assert!(
1205            verify_message::<V>(&sharing, namespace, msg1, &forged_partial1).is_err(),
1206            "forged partial1 should be invalid individually"
1207        );
1208        assert!(
1209            verify_message::<V>(&sharing, namespace, msg2, &forged_partial2).is_err(),
1210            "forged partial2 should be invalid individually"
1211        );
1212
1213        let forged_sum = forged_partial1.value + &forged_partial2.value;
1214        let valid_sum = partial1.value + &partial2.value;
1215        assert_eq!(
1216            forged_sum, valid_sum,
1217            "signature value sums should be equal"
1218        );
1219
1220        let pk = sharing.partial_public(signer.index).unwrap();
1221        let hm1 = hash_with_namespace::<V>(V::MESSAGE, namespace, msg1);
1222        let hm2 = hash_with_namespace::<V>(V::MESSAGE, namespace, msg2);
1223        let hm_sum = hm1 + &hm2;
1224        V::verify(&pk, &hm_sum, &forged_sum)
1225            .expect("vulnerable naive verification accepts forged aggregate");
1226
1227        let forged_entries = vec![
1228            (namespace, msg1, forged_partial1),
1229            (namespace, msg2, forged_partial2),
1230        ];
1231        let result = batch_verify_same_signer::<_, V, _>(
1232            &mut rng,
1233            &sharing,
1234            signer.index,
1235            &forged_entries,
1236            &Sequential,
1237        );
1238        assert!(
1239            result.is_err(),
1240            "secure function should reject forged partial signatures"
1241        );
1242
1243        let valid_entries = vec![(namespace, msg1, partial1), (namespace, msg2, partial2)];
1244        batch_verify_same_signer::<_, V, _>(
1245            &mut rng,
1246            &sharing,
1247            signer.index,
1248            &valid_entries,
1249            &Sequential,
1250        )
1251        .expect("secure function should accept valid partial signatures");
1252    }
1253
1254    #[test]
1255    fn test_batch_verify_same_signer_rejects_malleability() {
1256        batch_verify_same_signer_rejects_malleability::<MinPk>();
1257        batch_verify_same_signer_rejects_malleability::<MinSig>();
1258    }
1259}