commonware_cryptography/bls12381/certificate/threshold/
mod.rs

1//! BLS12-381 threshold signature scheme implementation.
2//!
3//! This module provides both the generic BLS12-381 threshold implementation and a macro to generate
4//! protocol-specific wrappers.
5//!
6//! Unlike multi-signature schemes, threshold signatures:
7//! - Use partial signatures that can be combined to form a threshold signature
8//! - Require a quorum of signatures to recover the full signature
9//! - Are **non-attributable**: partial signatures can be forged by holders of enough other partials
10
11#[cfg(feature = "mocks")]
12pub mod mocks;
13
14use crate::{
15    bls12381::primitives::{
16        group::Share,
17        ops::{
18            aggregate_signatures, aggregate_verify_multiple_messages, partial_sign_message,
19            partial_verify_multiple_public_keys, threshold_signature_recover, verify_message,
20        },
21        sharing::Sharing,
22        variant::{PartialSignature, Variant},
23    },
24    certificate::{Attestation, Scheme, Subject, Verification},
25    Digest, PublicKey,
26};
27#[cfg(not(feature = "std"))]
28use alloc::{collections::BTreeSet, vec::Vec};
29use commonware_utils::ordered::Set;
30use core::fmt::Debug;
31use rand::{CryptoRng, Rng};
32#[cfg(feature = "std")]
33use std::collections::BTreeSet;
34
35/// Generic BLS12-381 threshold signature implementation.
36///
37/// This enum contains the core cryptographic operations without protocol-specific
38/// context types. It can be reused across different protocols (simplex, aggregation, etc.)
39/// by wrapping it with protocol-specific trait implementations via the macro.
40///
41/// A node can play one of the following roles: a signer (with its share),
42/// a verifier (with evaluated public polynomial), or an external verifier that
43/// only checks recovered certificates.
44#[derive(Clone, Debug)]
45pub enum Generic<P: PublicKey, V: Variant> {
46    Signer {
47        /// Participants in the committee.
48        participants: Set<P>,
49        /// The public polynomial, used for the group identity, and partial signatures.
50        polynomial: Sharing<V>,
51        /// Local share used to generate partial signatures.
52        share: Share,
53    },
54    Verifier {
55        /// Participants in the committee.
56        participants: Set<P>,
57        /// The public polynomial, used for the group identity, and partial signatures.
58        polynomial: Sharing<V>,
59    },
60    CertificateVerifier {
61        /// Public identity of the committee (constant across reshares).
62        identity: V::Public,
63    },
64}
65
66impl<P: PublicKey, V: Variant> Generic<P, V> {
67    /// Constructs a signer instance with a private share and evaluated public polynomial.
68    ///
69    /// The participant identity keys are used for committee ordering and indexing.
70    /// The polynomial can be evaluated to obtain public verification keys for partial
71    /// signatures produced by committee members.
72    ///
73    /// Returns `None` if the share's public key does not match any participant.
74    ///
75    /// * `participants` - ordered set of participant identity keys
76    /// * `polynomial` - public polynomial for threshold verification
77    /// * `share` - local threshold share for signing
78    pub fn signer(participants: Set<P>, polynomial: Sharing<V>, share: Share) -> Option<Self> {
79        assert_eq!(
80            polynomial.total().get() as usize,
81            participants.len(),
82            "polynomial total must equal participant len"
83        );
84        #[cfg(feature = "std")]
85        polynomial.precompute_partial_publics();
86        let partial_public = polynomial
87            .partial_public(share.index)
88            .expect("share index must match participant indices");
89        if partial_public == share.public::<V>() {
90            Some(Self::Signer {
91                participants,
92                polynomial,
93                share,
94            })
95        } else {
96            None
97        }
98    }
99
100    /// Produces a verifier that can authenticate signatures but does not hold signing state.
101    ///
102    /// The participant identity keys are used for committee ordering and indexing.
103    /// The polynomial can be evaluated to obtain public verification keys for partial
104    /// signatures produced by committee members.
105    ///
106    /// * `participants` - ordered set of participant identity keys
107    /// * `polynomial` - public polynomial for threshold verification
108    pub fn verifier(participants: Set<P>, polynomial: Sharing<V>) -> Self {
109        assert_eq!(
110            polynomial.total().get() as usize,
111            participants.len(),
112            "polynomial total must equal participant len"
113        );
114        #[cfg(feature = "std")]
115        polynomial.precompute_partial_publics();
116
117        Self::Verifier {
118            participants,
119            polynomial,
120        }
121    }
122
123    /// Creates a verifier that only checks recovered certificates.
124    ///
125    /// This lightweight verifier can authenticate recovered threshold certificates but cannot
126    /// verify individual signatures or partial signatures.
127    ///
128    /// * `identity` - public identity of the committee (constant across reshares)
129    pub const fn certificate_verifier(identity: V::Public) -> Self {
130        Self::CertificateVerifier { identity }
131    }
132
133    /// Returns the ordered set of participant public identity keys in the committee.
134    pub fn participants(&self) -> &Set<P> {
135        match self {
136            Self::Signer { participants, .. } => participants,
137            Self::Verifier { participants, .. } => participants,
138            _ => panic!("can only be called for signer and verifier"),
139        }
140    }
141
142    /// Returns the public identity of the committee (constant across reshares).
143    pub fn identity(&self) -> &V::Public {
144        match self {
145            Self::Signer { polynomial, .. } => polynomial.public(),
146            Self::Verifier { polynomial, .. } => polynomial.public(),
147            Self::CertificateVerifier { identity, .. } => identity,
148        }
149    }
150
151    /// Returns the local share if this instance can generate partial signatures.
152    pub const fn share(&self) -> Option<&Share> {
153        match self {
154            Self::Signer { share, .. } => Some(share),
155            _ => None,
156        }
157    }
158
159    /// Returns the evaluated public polynomial for validating partial signatures produced by committee members.
160    fn polynomial(&self) -> &Sharing<V> {
161        match self {
162            Self::Signer { polynomial, .. } => polynomial,
163            Self::Verifier { polynomial, .. } => polynomial,
164            _ => panic!("can only be called for signer and verifier"),
165        }
166    }
167
168    /// Returns the index of "self" in the participant set, if available.
169    pub const fn me(&self) -> Option<u32> {
170        match self {
171            Self::Signer { share, .. } => Some(share.index),
172            _ => None,
173        }
174    }
175
176    /// Signs a subject and returns the attestation.
177    pub fn sign<S, D>(&self, namespace: &[u8], subject: S::Subject<'_, D>) -> Option<Attestation<S>>
178    where
179        S: Scheme<Signature = V::Signature>,
180        D: Digest,
181    {
182        let share = self.share()?;
183
184        let (namespace, message) = subject.namespace_and_message(namespace);
185        let signature =
186            partial_sign_message::<V>(share, Some(namespace.as_ref()), message.as_ref()).value;
187
188        Some(Attestation {
189            signer: share.index,
190            signature,
191        })
192    }
193
194    /// Verifies a single attestation from a signer.
195    pub fn verify_attestation<S, D>(
196        &self,
197        namespace: &[u8],
198        subject: S::Subject<'_, D>,
199        attestation: &Attestation<S>,
200    ) -> bool
201    where
202        S: Scheme<Signature = V::Signature>,
203        D: Digest,
204    {
205        let Ok(evaluated) = self.polynomial().partial_public(attestation.signer) else {
206            return false;
207        };
208
209        let (namespace, message) = subject.namespace_and_message(namespace);
210        verify_message::<V>(
211            &evaluated,
212            Some(namespace.as_ref()),
213            message.as_ref(),
214            &attestation.signature,
215        )
216        .is_ok()
217    }
218
219    /// Batch-verifies attestations and returns verified attestations and invalid signers.
220    pub fn verify_attestations<S, R, D, I>(
221        &self,
222        _rng: &mut R,
223        namespace: &[u8],
224        subject: S::Subject<'_, D>,
225        attestations: I,
226    ) -> Verification<S>
227    where
228        S: Scheme<Signature = V::Signature>,
229        R: Rng + CryptoRng,
230        D: Digest,
231        I: IntoIterator<Item = Attestation<S>>,
232    {
233        let mut invalid = BTreeSet::new();
234        let partials: Vec<_> = attestations
235            .into_iter()
236            .map(|attestation| PartialSignature::<V> {
237                index: attestation.signer,
238                value: attestation.signature,
239            })
240            .collect();
241
242        let polynomial = self.polynomial();
243        let (namespace, message) = subject.namespace_and_message(namespace);
244        if let Err(errs) = partial_verify_multiple_public_keys::<V, _>(
245            polynomial,
246            Some(namespace.as_ref()),
247            message.as_ref(),
248            partials.iter(),
249        ) {
250            for partial in errs {
251                invalid.insert(partial.index);
252            }
253        }
254
255        let verified = partials
256            .into_iter()
257            .filter(|partial| !invalid.contains(&partial.index))
258            .map(|partial| Attestation {
259                signer: partial.index,
260                signature: partial.value,
261            })
262            .collect();
263
264        Verification::new(verified, invalid.into_iter().collect())
265    }
266
267    /// Assembles a certificate from a collection of attestations.
268    pub fn assemble<S, I>(&self, attestations: I) -> Option<V::Signature>
269    where
270        S: Scheme<Signature = V::Signature>,
271        I: IntoIterator<Item = Attestation<S>>,
272    {
273        let partials: Vec<_> = attestations
274            .into_iter()
275            .map(|attestation| PartialSignature::<V> {
276                index: attestation.signer,
277                value: attestation.signature,
278            })
279            .collect();
280
281        let quorum = self.polynomial();
282        if partials.len() < quorum.required() as usize {
283            return None;
284        }
285
286        threshold_signature_recover::<V, _>(quorum, partials.iter()).ok()
287    }
288
289    /// Verifies a certificate.
290    pub fn verify_certificate<S, R, D>(
291        &self,
292        _rng: &mut R,
293        namespace: &[u8],
294        subject: S::Subject<'_, D>,
295        certificate: &V::Signature,
296    ) -> bool
297    where
298        S: Scheme,
299        R: Rng + CryptoRng,
300        D: Digest,
301    {
302        let identity = self.identity();
303        let (namespace, message) = subject.namespace_and_message(namespace);
304        verify_message::<V>(
305            identity,
306            Some(namespace.as_ref()),
307            message.as_ref(),
308            certificate,
309        )
310        .is_ok()
311    }
312
313    /// Verifies multiple certificates in a batch.
314    pub fn verify_certificates<'a, S, R, D, I>(
315        &self,
316        _rng: &mut R,
317        namespace: &[u8],
318        certificates: I,
319    ) -> bool
320    where
321        S: Scheme,
322        R: Rng + CryptoRng,
323        D: Digest,
324        I: Iterator<Item = (S::Subject<'a, D>, &'a V::Signature)>,
325    {
326        let identity = self.identity();
327
328        let mut messages = Vec::new();
329        let mut signatures = Vec::new();
330
331        for (subject, certificate) in certificates {
332            let (namespace, message) = subject.namespace_and_message(namespace);
333            messages.push((Some(namespace), message));
334            signatures.push(*certificate);
335        }
336
337        if messages.is_empty() {
338            return true;
339        }
340
341        let signature = aggregate_signatures::<V, _>(signatures.iter());
342        aggregate_verify_multiple_messages::<V, _>(
343            identity,
344            &messages
345                .iter()
346                .map(|(namespace, message)| (namespace.as_deref(), message.as_ref()))
347                .collect::<Vec<_>>(),
348            &signature,
349            1,
350        )
351        .is_ok()
352    }
353
354    pub const fn is_attributable(&self) -> bool {
355        false
356    }
357
358    pub const fn certificate_codec_config(&self) {}
359
360    pub const fn certificate_codec_config_unbounded() {}
361}
362
363mod macros {
364    /// Generates a BLS12-381 threshold signing scheme wrapper for a specific protocol.
365    ///
366    /// This macro creates a complete wrapper struct with constructors, `Scheme` trait
367    /// implementation, and a `fixture` function for testing.
368    /// The only required parameter is the `Subject` type, which varies per protocol.
369    ///
370    /// # Example
371    /// ```ignore
372    /// impl_certificate_bls12381_threshold!(VoteSubject<'a, D>);
373    /// ```
374    #[macro_export]
375    macro_rules! impl_certificate_bls12381_threshold {
376        ($subject:ty) => {
377            /// Generates a test fixture with Ed25519 identities and BLS12-381 threshold schemes.
378            ///
379            /// Returns a [`commonware_cryptography::certificate::mocks::Fixture`] whose keys and
380            /// scheme instances share a consistent ordering.
381            #[cfg(feature = "mocks")]
382            #[allow(dead_code)]
383            pub fn fixture<V, R>(
384                rng: &mut R,
385                n: u32,
386            ) -> $crate::certificate::mocks::Fixture<Scheme<$crate::ed25519::PublicKey, V>>
387            where
388                V: $crate::bls12381::primitives::variant::Variant,
389                R: rand::RngCore + rand::CryptoRng,
390            {
391                $crate::bls12381::certificate::threshold::mocks::fixture::<_, V, _>(
392                    rng,
393                    n,
394                    Scheme::signer,
395                    Scheme::verifier,
396                )
397            }
398
399            /// BLS12-381 threshold signature scheme wrapper.
400            #[derive(Clone, Debug)]
401            pub struct Scheme<
402                P: $crate::PublicKey,
403                V: $crate::bls12381::primitives::variant::Variant,
404            > {
405                generic: $crate::bls12381::certificate::threshold::Generic<P, V>,
406            }
407
408            impl<
409                P: $crate::PublicKey,
410                V: $crate::bls12381::primitives::variant::Variant,
411            > Scheme<P, V> {
412                /// Creates a new signer instance with a private share and evaluated public polynomial.
413                pub fn signer(
414                    participants: commonware_utils::ordered::Set<P>,
415                    polynomial: $crate::bls12381::primitives::sharing::Sharing<V>,
416                    share: $crate::bls12381::primitives::group::Share,
417                ) -> Option<Self> {
418                    Some(Self {
419                        generic: $crate::bls12381::certificate::threshold::Generic::signer(
420                            participants,
421                            polynomial,
422                            share,
423                        )?,
424                    })
425                }
426
427                /// Creates a verifier that can authenticate partial signatures.
428                pub fn verifier(
429                    participants: commonware_utils::ordered::Set<P>,
430                    polynomial: $crate::bls12381::primitives::sharing::Sharing<V>,
431                ) -> Self {
432                    Self {
433                        generic: $crate::bls12381::certificate::threshold::Generic::verifier(
434                            participants,
435                            polynomial,
436                        ),
437                    }
438                }
439
440                /// Creates a lightweight verifier that only checks recovered certificates.
441                pub const fn certificate_verifier(identity: V::Public) -> Self {
442                    Self {
443                        generic: $crate::bls12381::certificate::threshold::Generic::certificate_verifier(
444                            identity,
445                        ),
446                    }
447                }
448
449                /// Returns the public identity of the committee (constant across reshares).
450                pub fn identity(&self) -> &V::Public {
451                    self.generic.identity()
452                }
453
454                /// Returns the local share if this instance can generate partial signatures.
455                pub const fn share(&self) -> Option<&$crate::bls12381::primitives::group::Share> {
456                    self.generic.share()
457                }
458            }
459
460            impl<
461                P: $crate::PublicKey,
462                V: $crate::bls12381::primitives::variant::Variant + Send + Sync,
463            > $crate::certificate::Scheme for Scheme<P, V> {
464                type Subject<'a, D: $crate::Digest> = $subject;
465                type PublicKey = P;
466                type Signature = V::Signature;
467                type Certificate = V::Signature;
468
469                fn me(&self) -> Option<u32> {
470                    self.generic.me()
471                }
472
473                fn participants(&self) -> &commonware_utils::ordered::Set<Self::PublicKey> {
474                    self.generic.participants()
475                }
476
477                fn sign<D: $crate::Digest>(
478                    &self,
479                    namespace: &[u8],
480                    subject: Self::Subject<'_, D>,
481                ) -> Option<$crate::certificate::Attestation<Self>> {
482                    self.generic.sign::<_, D>(namespace, subject)
483                }
484
485                fn verify_attestation<D: $crate::Digest>(
486                    &self,
487                    namespace: &[u8],
488                    subject: Self::Subject<'_, D>,
489                    attestation: &$crate::certificate::Attestation<Self>,
490                ) -> bool {
491                    self.generic.verify_attestation::<_, D>(namespace, subject, attestation)
492                }
493
494                fn verify_attestations<R, D, I>(
495                    &self,
496                    rng: &mut R,
497                    namespace: &[u8],
498                    subject: Self::Subject<'_, D>,
499                    attestations: I,
500                ) -> $crate::certificate::Verification<Self>
501                where
502                    R: rand::Rng + rand::CryptoRng,
503                    D: $crate::Digest,
504                    I: IntoIterator<Item = $crate::certificate::Attestation<Self>>,
505                {
506                    self.generic.verify_attestations::<_, _, D, _>(rng, namespace, subject, attestations)
507                }
508
509                fn assemble<I>(&self, attestations: I) -> Option<Self::Certificate>
510                where
511                    I: IntoIterator<Item = $crate::certificate::Attestation<Self>>,
512                {
513                    self.generic.assemble(attestations)
514                }
515
516                fn verify_certificate<
517                    R: rand::Rng + rand::CryptoRng,
518                    D: $crate::Digest,
519                >(
520                    &self,
521                    rng: &mut R,
522                    namespace: &[u8],
523                    subject: Self::Subject<'_, D>,
524                    certificate: &Self::Certificate,
525                ) -> bool {
526                    self.generic.verify_certificate::<Self, _, D>(rng, namespace, subject, certificate)
527                }
528
529                fn verify_certificates<'a, R, D, I>(
530                    &self,
531                    rng: &mut R,
532                    namespace: &[u8],
533                    certificates: I,
534                ) -> bool
535                where
536                    R: rand::Rng + rand::CryptoRng,
537                    D: $crate::Digest,
538                    I: Iterator<Item = (Self::Subject<'a, D>, &'a Self::Certificate)>,
539                {
540                    self.generic.verify_certificates::<Self, _, D, _>(rng, namespace, certificates)
541                }
542
543                fn is_attributable(&self) -> bool {
544                    self.generic.is_attributable()
545                }
546
547                fn certificate_codec_config(
548                    &self,
549                ) -> <Self::Certificate as commonware_codec::Read>::Cfg {
550                    self.generic.certificate_codec_config()
551                }
552
553                fn certificate_codec_config_unbounded(
554                ) -> <Self::Certificate as commonware_codec::Read>::Cfg {
555                    $crate::bls12381::certificate::threshold::Generic::<P, V>::certificate_codec_config_unbounded()
556                }
557            }
558        };
559    }
560}
561
562#[cfg(test)]
563mod tests {
564    use super::*;
565    use crate::{
566        bls12381::{
567            dkg,
568            primitives::{
569                ops::partial_sign_message,
570                variant::{MinPk, MinSig, Variant},
571            },
572        },
573        certificate::Scheme as _,
574        ed25519::{self, PrivateKey as Ed25519PrivateKey},
575        impl_certificate_bls12381_threshold,
576        sha256::Digest as Sha256Digest,
577        Signer as _,
578    };
579    use bytes::Bytes;
580    use commonware_codec::{DecodeExt, Encode};
581    use commonware_math::algebra::{Additive, Random};
582    use commonware_utils::{ordered::Set, quorum, TryCollect, NZU32};
583    use rand::{rngs::StdRng, thread_rng, SeedableRng};
584
585    const NAMESPACE: &[u8] = b"test-bls12381-threshold";
586    const MESSAGE: &[u8] = b"test message";
587
588    /// Test context type for generic scheme tests.
589    #[derive(Clone, Debug)]
590    pub struct TestSubject<'a> {
591        pub message: &'a [u8],
592    }
593
594    impl<'a> Subject for TestSubject<'a> {
595        fn namespace_and_message(&self, namespace: &[u8]) -> (Bytes, Bytes) {
596            (namespace.to_vec().into(), self.message.to_vec().into())
597        }
598    }
599
600    // Use the macro to generate the test scheme
601    impl_certificate_bls12381_threshold!(TestSubject<'a>);
602
603    #[allow(clippy::type_complexity)]
604    fn setup_signers<V: Variant>(
605        n: u32,
606        seed: u64,
607    ) -> (
608        Vec<Scheme<ed25519::PublicKey, V>>,
609        Scheme<ed25519::PublicKey, V>,
610        Sharing<V>,
611    ) {
612        let mut rng = StdRng::seed_from_u64(seed);
613
614        // Generate identity keys (ed25519)
615        let identity_keys: Vec<_> = (0..n)
616            .map(|_| Ed25519PrivateKey::random(&mut rng))
617            .collect();
618        let participants: Set<ed25519::PublicKey> = identity_keys
619            .iter()
620            .map(|sk| sk.public_key())
621            .try_collect()
622            .unwrap();
623
624        // Generate threshold polynomial and shares using DKG
625        let (polynomial, shares) =
626            dkg::deal_anonymous::<V>(&mut rng, Default::default(), NZU32!(n));
627
628        let signers = shares
629            .into_iter()
630            .map(|share| Scheme::signer(participants.clone(), polynomial.clone(), share).unwrap())
631            .collect();
632
633        let verifier = Scheme::verifier(participants, polynomial.clone());
634
635        (signers, verifier, polynomial)
636    }
637
638    fn test_sign_vote_roundtrip<V: Variant + Send + Sync>() {
639        let (schemes, _, _) = setup_signers::<V>(4, 42);
640        let scheme = &schemes[0];
641
642        let attestation = scheme
643            .sign::<Sha256Digest>(NAMESPACE, TestSubject { message: MESSAGE })
644            .unwrap();
645        assert!(scheme.verify_attestation::<Sha256Digest>(
646            NAMESPACE,
647            TestSubject { message: MESSAGE },
648            &attestation
649        ));
650    }
651
652    #[test]
653    fn test_sign_vote_roundtrip_variants() {
654        test_sign_vote_roundtrip::<MinPk>();
655        test_sign_vote_roundtrip::<MinSig>();
656    }
657
658    fn test_verifier_cannot_sign<V: Variant + Send + Sync>() {
659        let (_, verifier, _) = setup_signers::<V>(4, 43);
660        assert!(verifier
661            .sign::<Sha256Digest>(NAMESPACE, TestSubject { message: MESSAGE })
662            .is_none());
663    }
664
665    #[test]
666    fn test_verifier_cannot_sign_variants() {
667        test_verifier_cannot_sign::<MinPk>();
668        test_verifier_cannot_sign::<MinSig>();
669    }
670
671    fn test_verify_attestations_filters_invalid<V: Variant + Send + Sync>() {
672        let (schemes, _, _) = setup_signers::<V>(5, 44);
673        let quorum = quorum(schemes.len() as u32) as usize;
674
675        let attestations: Vec<_> = schemes
676            .iter()
677            .take(quorum)
678            .map(|s| {
679                s.sign::<Sha256Digest>(NAMESPACE, TestSubject { message: MESSAGE })
680                    .unwrap()
681            })
682            .collect();
683
684        let mut rng = StdRng::seed_from_u64(45);
685        let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
686            &mut rng,
687            NAMESPACE,
688            TestSubject { message: MESSAGE },
689            attestations.clone(),
690        );
691        assert!(result.invalid.is_empty());
692        assert_eq!(result.verified.len(), quorum);
693
694        // Test: Corrupt one attestation - invalid signer index
695        let mut attestations_corrupted = attestations.clone();
696        attestations_corrupted[0].signer = 999;
697        let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
698            &mut rng,
699            NAMESPACE,
700            TestSubject { message: MESSAGE },
701            attestations_corrupted,
702        );
703        assert_eq!(result.invalid, vec![999]);
704        assert_eq!(result.verified.len(), quorum - 1);
705
706        // Test: Corrupt one attestation - invalid signature
707        let mut attestations_corrupted = attestations;
708        attestations_corrupted[0].signature = attestations_corrupted[1].signature;
709        let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
710            &mut rng,
711            NAMESPACE,
712            TestSubject { message: MESSAGE },
713            attestations_corrupted,
714        );
715        assert_eq!(result.invalid.len(), 1);
716        assert_eq!(result.verified.len(), quorum - 1);
717    }
718
719    #[test]
720    fn test_verify_attestations_filters_invalid_variants() {
721        test_verify_attestations_filters_invalid::<MinPk>();
722        test_verify_attestations_filters_invalid::<MinSig>();
723    }
724
725    fn test_assemble_certificate<V: Variant + Send + Sync>() {
726        let (schemes, verifier, _) = setup_signers::<V>(4, 46);
727        let quorum = quorum(schemes.len() as u32) as usize;
728
729        let attestations: Vec<_> = schemes
730            .iter()
731            .take(quorum)
732            .map(|s| {
733                s.sign::<Sha256Digest>(NAMESPACE, TestSubject { message: MESSAGE })
734                    .unwrap()
735            })
736            .collect();
737
738        let certificate = schemes[0].assemble(attestations).unwrap();
739
740        // Verify the assembled certificate
741        assert!(verifier.verify_certificate::<_, Sha256Digest>(
742            &mut thread_rng(),
743            NAMESPACE,
744            TestSubject { message: MESSAGE },
745            &certificate
746        ));
747    }
748
749    #[test]
750    fn test_assemble_certificate_variants() {
751        test_assemble_certificate::<MinPk>();
752        test_assemble_certificate::<MinSig>();
753    }
754
755    fn test_verify_certificate<V: Variant + Send + Sync>() {
756        let (schemes, verifier, _) = setup_signers::<V>(4, 48);
757        let quorum = quorum(schemes.len() as u32) as usize;
758
759        let attestations: Vec<_> = schemes
760            .iter()
761            .take(quorum)
762            .map(|s| {
763                s.sign::<Sha256Digest>(NAMESPACE, TestSubject { message: MESSAGE })
764                    .unwrap()
765            })
766            .collect();
767
768        let certificate = schemes[0].assemble(attestations).unwrap();
769
770        let mut rng = StdRng::seed_from_u64(49);
771        assert!(verifier.verify_certificate::<_, Sha256Digest>(
772            &mut rng,
773            NAMESPACE,
774            TestSubject { message: MESSAGE },
775            &certificate
776        ));
777    }
778
779    #[test]
780    fn test_verify_certificate_variants() {
781        test_verify_certificate::<MinPk>();
782        test_verify_certificate::<MinSig>();
783    }
784
785    fn test_verify_certificate_detects_corruption<V: Variant + Send + Sync>() {
786        let (schemes, verifier, _) = setup_signers::<V>(4, 50);
787        let quorum = quorum(schemes.len() as u32) as usize;
788
789        let attestations: Vec<_> = schemes
790            .iter()
791            .take(quorum)
792            .map(|s| {
793                s.sign::<Sha256Digest>(NAMESPACE, TestSubject { message: MESSAGE })
794                    .unwrap()
795            })
796            .collect();
797
798        let certificate = schemes[0].assemble(attestations).unwrap();
799
800        // Valid certificate passes
801        assert!(verifier.verify_certificate::<_, Sha256Digest>(
802            &mut thread_rng(),
803            NAMESPACE,
804            TestSubject { message: MESSAGE },
805            &certificate
806        ));
807
808        // Corrupted certificate fails
809        let corrupted = V::Signature::zero();
810        assert!(!verifier.verify_certificate::<_, Sha256Digest>(
811            &mut thread_rng(),
812            NAMESPACE,
813            TestSubject { message: MESSAGE },
814            &corrupted
815        ));
816    }
817
818    #[test]
819    fn test_verify_certificate_detects_corruption_variants() {
820        test_verify_certificate_detects_corruption::<MinPk>();
821        test_verify_certificate_detects_corruption::<MinSig>();
822    }
823
824    fn test_certificate_codec_roundtrip<V: Variant + Send + Sync>() {
825        let (schemes, _, _) = setup_signers::<V>(4, 51);
826        let quorum = quorum(schemes.len() as u32) as usize;
827
828        let attestations: Vec<_> = schemes
829            .iter()
830            .take(quorum)
831            .map(|s| {
832                s.sign::<Sha256Digest>(NAMESPACE, TestSubject { message: MESSAGE })
833                    .unwrap()
834            })
835            .collect();
836
837        let certificate = schemes[0].assemble(attestations).unwrap();
838        let encoded = certificate.encode();
839        let decoded = V::Signature::decode(encoded).expect("decode certificate");
840        assert_eq!(decoded, certificate);
841    }
842
843    #[test]
844    fn test_certificate_codec_roundtrip_variants() {
845        test_certificate_codec_roundtrip::<MinPk>();
846        test_certificate_codec_roundtrip::<MinSig>();
847    }
848
849    fn test_certificate_rejects_sub_quorum<V: Variant + Send + Sync>() {
850        let (schemes, _, _) = setup_signers::<V>(4, 52);
851        let sub_quorum = 2; // Less than quorum (3)
852
853        let attestations: Vec<_> = schemes
854            .iter()
855            .take(sub_quorum)
856            .map(|s| {
857                s.sign::<Sha256Digest>(NAMESPACE, TestSubject { message: MESSAGE })
858                    .unwrap()
859            })
860            .collect();
861
862        assert!(schemes[0].assemble(attestations).is_none());
863    }
864
865    #[test]
866    fn test_certificate_rejects_sub_quorum_variants() {
867        test_certificate_rejects_sub_quorum::<MinPk>();
868        test_certificate_rejects_sub_quorum::<MinSig>();
869    }
870
871    fn test_verify_certificates_batch<V: Variant + Send + Sync>() {
872        let (schemes, verifier, _) = setup_signers::<V>(4, 56);
873        let quorum = quorum(schemes.len() as u32) as usize;
874
875        let messages = [b"msg1".as_slice(), b"msg2".as_slice(), b"msg3".as_slice()];
876        let mut certificates = Vec::new();
877
878        for msg in &messages {
879            let attestations: Vec<_> = schemes
880                .iter()
881                .take(quorum)
882                .map(|s| {
883                    s.sign::<Sha256Digest>(NAMESPACE, TestSubject { message: msg })
884                        .unwrap()
885                })
886                .collect();
887            certificates.push(schemes[0].assemble(attestations).unwrap());
888        }
889
890        let certs_iter = messages
891            .iter()
892            .zip(&certificates)
893            .map(|(msg, cert)| (TestSubject { message: msg }, cert));
894
895        let mut rng = StdRng::seed_from_u64(57);
896        assert!(verifier.verify_certificates::<_, Sha256Digest, _>(&mut rng, NAMESPACE, certs_iter));
897    }
898
899    #[test]
900    fn test_verify_certificates_batch_variants() {
901        test_verify_certificates_batch::<MinPk>();
902        test_verify_certificates_batch::<MinSig>();
903    }
904
905    fn test_verify_certificates_batch_detects_failure<V: Variant + Send + Sync>() {
906        let (schemes, verifier, _) = setup_signers::<V>(4, 58);
907        let quorum = quorum(schemes.len() as u32) as usize;
908
909        let messages = [b"msg1".as_slice(), b"msg2".as_slice()];
910        let mut certificates = Vec::new();
911
912        for msg in &messages {
913            let attestations: Vec<_> = schemes
914                .iter()
915                .take(quorum)
916                .map(|s| {
917                    s.sign::<Sha256Digest>(NAMESPACE, TestSubject { message: msg })
918                        .unwrap()
919                })
920                .collect();
921            certificates.push(schemes[0].assemble(attestations).unwrap());
922        }
923
924        // Corrupt second certificate
925        certificates[1] = V::Signature::zero();
926
927        let certs_iter = messages
928            .iter()
929            .zip(&certificates)
930            .map(|(msg, cert)| (TestSubject { message: msg }, cert));
931
932        let mut rng = StdRng::seed_from_u64(59);
933        assert!(
934            !verifier.verify_certificates::<_, Sha256Digest, _>(&mut rng, NAMESPACE, certs_iter)
935        );
936    }
937
938    #[test]
939    fn test_verify_certificates_batch_detects_failure_variants() {
940        test_verify_certificates_batch_detects_failure::<MinPk>();
941        test_verify_certificates_batch_detects_failure::<MinSig>();
942    }
943
944    fn test_certificate_verifier<V: Variant + Send + Sync>() {
945        let (schemes, _, polynomial) = setup_signers::<V>(4, 60);
946        let quorum = quorum(schemes.len() as u32) as usize;
947
948        let attestations: Vec<_> = schemes
949            .iter()
950            .take(quorum)
951            .map(|s| {
952                s.sign::<Sha256Digest>(NAMESPACE, TestSubject { message: MESSAGE })
953                    .unwrap()
954            })
955            .collect();
956
957        let certificate = schemes[0].assemble(attestations).unwrap();
958
959        // Create a certificate-only verifier using the identity from the polynomial
960        let identity = polynomial.public();
961        let cert_verifier = Scheme::<ed25519::PublicKey, V>::certificate_verifier(*identity);
962
963        // Should be able to verify certificates
964        assert!(cert_verifier.verify_certificate::<_, Sha256Digest>(
965            &mut thread_rng(),
966            NAMESPACE,
967            TestSubject { message: MESSAGE },
968            &certificate
969        ));
970
971        // Should not be able to sign
972        assert!(cert_verifier
973            .sign::<Sha256Digest>(NAMESPACE, TestSubject { message: MESSAGE })
974            .is_none());
975    }
976
977    #[test]
978    fn test_certificate_verifier_variants() {
979        test_certificate_verifier::<MinPk>();
980        test_certificate_verifier::<MinSig>();
981    }
982
983    fn test_is_not_attributable<V: Variant + Send + Sync>() {
984        let (schemes, verifier, _) = setup_signers::<V>(4, 61);
985
986        // Threshold signatures are non-attributable
987        assert!(!schemes[0].is_attributable());
988        assert!(!verifier.is_attributable());
989    }
990
991    #[test]
992    fn test_is_not_attributable_variants() {
993        test_is_not_attributable::<MinPk>();
994        test_is_not_attributable::<MinSig>();
995    }
996
997    fn test_verifier_accepts_votes<V: Variant + Send + Sync>() {
998        let (schemes, verifier, _) = setup_signers::<V>(4, 62);
999
1000        let vote = schemes[1]
1001            .sign::<Sha256Digest>(NAMESPACE, TestSubject { message: MESSAGE })
1002            .unwrap();
1003        assert!(verifier.verify_attestation::<Sha256Digest>(
1004            NAMESPACE,
1005            TestSubject { message: MESSAGE },
1006            &vote
1007        ));
1008    }
1009
1010    #[test]
1011    fn test_verifier_accepts_votes_variants() {
1012        test_verifier_accepts_votes::<MinPk>();
1013        test_verifier_accepts_votes::<MinSig>();
1014    }
1015
1016    fn test_scheme_clone_and_verifier<V: Variant + Send + Sync>() {
1017        let (schemes, verifier, _) = setup_signers::<V>(4, 63);
1018
1019        // Clone a signer
1020        let signer = schemes[0].clone();
1021        assert!(
1022            signer
1023                .sign::<Sha256Digest>(NAMESPACE, TestSubject { message: MESSAGE })
1024                .is_some(),
1025            "signer should produce votes"
1026        );
1027
1028        // A verifier cannot produce votes
1029        assert!(
1030            verifier
1031                .sign::<Sha256Digest>(NAMESPACE, TestSubject { message: MESSAGE })
1032                .is_none(),
1033            "verifier should not produce votes"
1034        );
1035    }
1036
1037    #[test]
1038    fn test_scheme_clone_and_verifier_variants() {
1039        test_scheme_clone_and_verifier::<MinPk>();
1040        test_scheme_clone_and_verifier::<MinSig>();
1041    }
1042
1043    fn certificate_verifier_panics_on_vote<V: Variant + Send + Sync>() {
1044        let (schemes, _, _) = setup_signers::<V>(4, 37);
1045        let certificate_verifier =
1046            Scheme::<ed25519::PublicKey, V>::certificate_verifier(*schemes[0].identity());
1047
1048        let vote = schemes[1]
1049            .sign::<Sha256Digest>(NAMESPACE, TestSubject { message: MESSAGE })
1050            .unwrap();
1051
1052        // CertificateVerifier should panic when trying to verify a vote
1053        certificate_verifier.verify_attestation::<Sha256Digest>(
1054            NAMESPACE,
1055            TestSubject { message: MESSAGE },
1056            &vote,
1057        );
1058    }
1059
1060    #[test]
1061    #[should_panic(expected = "can only be called for signer and verifier")]
1062    fn test_certificate_verifier_panics_on_vote_min_pk() {
1063        certificate_verifier_panics_on_vote::<MinPk>();
1064    }
1065
1066    #[test]
1067    #[should_panic(expected = "can only be called for signer and verifier")]
1068    fn test_certificate_verifier_panics_on_vote_min_sig() {
1069        certificate_verifier_panics_on_vote::<MinSig>();
1070    }
1071
1072    fn signer_shares_must_match_participant_indices<V: Variant + Send + Sync>() {
1073        let mut rng = StdRng::seed_from_u64(64);
1074
1075        // Generate identity keys (ed25519)
1076        let identity_keys: Vec<_> = (0..4)
1077            .map(|_| Ed25519PrivateKey::random(&mut rng))
1078            .collect();
1079        let participants: Set<ed25519::PublicKey> = identity_keys
1080            .iter()
1081            .map(|sk| sk.public_key())
1082            .try_collect()
1083            .unwrap();
1084
1085        let (polynomial, mut shares) =
1086            dkg::deal_anonymous::<V>(&mut rng, Default::default(), NZU32!(4));
1087        shares[0].index = 999;
1088        Scheme::<ed25519::PublicKey, V>::signer(participants, polynomial, shares[0].clone());
1089    }
1090
1091    #[test]
1092    #[should_panic(expected = "share index must match participant indices")]
1093    fn test_signer_shares_must_match_participant_indices_min_pk() {
1094        signer_shares_must_match_participant_indices::<MinPk>();
1095    }
1096
1097    #[test]
1098    #[should_panic(expected = "share index must match participant indices")]
1099    fn test_signer_shares_must_match_participant_indices_min_sig() {
1100        signer_shares_must_match_participant_indices::<MinSig>();
1101    }
1102
1103    fn make_participants<R: rand::RngCore + rand::CryptoRng + Clone>(
1104        rng: &mut R,
1105        n: u32,
1106    ) -> Set<ed25519::PublicKey> {
1107        (0..n)
1108            .map(|_| Ed25519PrivateKey::random(&mut *rng).public_key())
1109            .try_collect()
1110            .expect("participants are unique")
1111    }
1112
1113    fn signer_polynomial_threshold_must_equal_quorum<V: Variant>() {
1114        let mut rng = StdRng::seed_from_u64(7);
1115        let participants = make_participants(&mut rng, 5);
1116        // Create a polynomial with threshold 4, but quorum of 5 participants is 4
1117        // so this should succeed. Let's use threshold 2 to make it fail.
1118        // quorum(5) = 4, but polynomial.required() = 2, so this should panic
1119        let (polynomial, shares) =
1120            dkg::deal_anonymous::<V>(&mut rng, Default::default(), NZU32!(2));
1121        Scheme::<ed25519::PublicKey, V>::signer(participants, polynomial, shares[0].clone());
1122    }
1123
1124    #[test]
1125    #[should_panic(expected = "polynomial total must equal participant len")]
1126    fn test_signer_polynomial_threshold_must_equal_quorum_min_pk() {
1127        signer_polynomial_threshold_must_equal_quorum::<MinPk>();
1128    }
1129
1130    #[test]
1131    #[should_panic(expected = "polynomial total must equal participant len")]
1132    fn test_signer_polynomial_threshold_must_equal_quorum_min_sig() {
1133        signer_polynomial_threshold_must_equal_quorum::<MinSig>();
1134    }
1135
1136    fn verifier_polynomial_threshold_must_equal_quorum<V: Variant>() {
1137        let mut rng = StdRng::seed_from_u64(7);
1138        let participants = make_participants(&mut rng, 5);
1139        // Create a polynomial with threshold 2, but quorum of 5 participants is 4
1140        // quorum(5) = 4, but polynomial.required() = 2, so this should panic
1141        let (polynomial, _) = dkg::deal_anonymous::<V>(&mut rng, Default::default(), NZU32!(2));
1142        Scheme::<ed25519::PublicKey, V>::verifier(participants, polynomial);
1143    }
1144
1145    #[test]
1146    #[should_panic(expected = "polynomial total must equal participant len")]
1147    fn test_verifier_polynomial_threshold_must_equal_quorum_min_pk() {
1148        verifier_polynomial_threshold_must_equal_quorum::<MinPk>();
1149    }
1150
1151    #[test]
1152    #[should_panic(expected = "polynomial total must equal participant len")]
1153    fn test_verifier_polynomial_threshold_must_equal_quorum_min_sig() {
1154        verifier_polynomial_threshold_must_equal_quorum::<MinSig>();
1155    }
1156
1157    fn certificate_decode_rejects_length_mismatch<V: Variant + Send + Sync>() {
1158        let (schemes, _, _) = setup_signers::<V>(4, 65);
1159        let quorum = quorum(schemes.len() as u32) as usize;
1160
1161        let attestations: Vec<_> = schemes
1162            .iter()
1163            .take(quorum)
1164            .map(|s| {
1165                s.sign::<Sha256Digest>(NAMESPACE, TestSubject { message: MESSAGE })
1166                    .unwrap()
1167            })
1168            .collect();
1169
1170        let certificate = schemes[0].assemble(attestations).unwrap();
1171        let mut encoded = certificate.encode();
1172        encoded.truncate(encoded.len() - 1);
1173        assert!(V::Signature::decode(encoded).is_err());
1174    }
1175
1176    #[test]
1177    fn test_certificate_decode_rejects_length_mismatch_variants() {
1178        certificate_decode_rejects_length_mismatch::<MinPk>();
1179        certificate_decode_rejects_length_mismatch::<MinSig>();
1180    }
1181
1182    fn sign_vote_partial_matches_share<V: Variant + Send + Sync>() {
1183        let (schemes, _, _) = setup_signers::<V>(4, 66);
1184        let scheme = &schemes[0];
1185
1186        let signature = scheme
1187            .sign::<Sha256Digest>(NAMESPACE, TestSubject { message: MESSAGE })
1188            .unwrap();
1189
1190        // Verify the partial signature matches what we'd get from direct signing
1191        let share = scheme.share().expect("expected signer");
1192
1193        let expected = partial_sign_message::<V>(share, Some(NAMESPACE), MESSAGE);
1194
1195        assert_eq!(signature.signer, share.index);
1196        assert_eq!(signature.signature, expected.value);
1197    }
1198
1199    #[test]
1200    fn test_sign_vote_partial_matches_share_variants() {
1201        sign_vote_partial_matches_share::<MinPk>();
1202        sign_vote_partial_matches_share::<MinSig>();
1203    }
1204}