Skip to main content

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::{self, batch, threshold},
18        sharing::Sharing,
19        variant::{PartialSignature, Variant},
20    },
21    certificate::{Attestation, Namespace, Scheme, Subject, Verification},
22    Digest, PublicKey,
23};
24#[cfg(not(feature = "std"))]
25use alloc::{collections::BTreeSet, vec::Vec};
26use bytes::{Buf, BufMut};
27use commonware_codec::{types::lazy::Lazy, Error, FixedSize, Read, ReadExt, Write};
28use commonware_parallel::Strategy;
29use commonware_utils::{ordered::Set, Faults, Participant};
30use core::fmt::Debug;
31use rand_core::CryptoRng;
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, N: Namespace> {
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        /// Pre-computed namespace(s) for this subject type.
54        namespace: N,
55    },
56    Verifier {
57        /// Participants in the committee.
58        participants: Set<P>,
59        /// The public polynomial, used for the group identity, and partial signatures.
60        polynomial: Sharing<V>,
61        /// Pre-computed namespace(s) for this subject type.
62        namespace: N,
63    },
64    CertificateVerifier {
65        /// Public identity of the committee (constant across reshares).
66        identity: V::Public,
67        /// Pre-computed namespace(s) for this subject type.
68        namespace: N,
69    },
70}
71
72impl<P: PublicKey, V: Variant, N: Namespace> Generic<P, V, N> {
73    /// Constructs a signer instance with a private share and evaluated public polynomial.
74    ///
75    /// The participant identity keys are used for committee ordering and indexing.
76    /// The polynomial can be evaluated to obtain public verification keys for partial
77    /// signatures produced by committee members.
78    ///
79    /// Returns `None` if the share's public key does not match any participant.
80    ///
81    /// * `namespace` - base namespace for domain separation
82    /// * `participants` - ordered set of participant identity keys
83    /// * `polynomial` - public polynomial for threshold verification
84    /// * `share` - local threshold share for signing
85    pub fn signer(
86        namespace: &[u8],
87        participants: Set<P>,
88        polynomial: Sharing<V>,
89        share: Share,
90    ) -> Option<Self> {
91        assert_eq!(
92            polynomial.total().get() as usize,
93            participants.len(),
94            "polynomial total must equal participant len"
95        );
96        #[cfg(feature = "std")]
97        polynomial.precompute_partial_publics();
98        let partial_public = polynomial
99            .partial_public(share.index)
100            .expect("share index must match participant indices");
101        if partial_public == share.public::<V>() {
102            Some(Self::Signer {
103                participants,
104                polynomial,
105                share,
106                namespace: N::derive(namespace),
107            })
108        } else {
109            None
110        }
111    }
112
113    /// Produces a verifier that can authenticate signatures but does not hold signing state.
114    ///
115    /// The participant identity keys are used for committee ordering and indexing.
116    /// The polynomial can be evaluated to obtain public verification keys for partial
117    /// signatures produced by committee members.
118    ///
119    /// * `namespace` - base namespace for domain separation
120    /// * `participants` - ordered set of participant identity keys
121    /// * `polynomial` - public polynomial for threshold verification
122    pub fn verifier(namespace: &[u8], participants: Set<P>, polynomial: Sharing<V>) -> Self {
123        assert_eq!(
124            polynomial.total().get() as usize,
125            participants.len(),
126            "polynomial total must equal participant len"
127        );
128        #[cfg(feature = "std")]
129        polynomial.precompute_partial_publics();
130
131        Self::Verifier {
132            participants,
133            polynomial,
134            namespace: N::derive(namespace),
135        }
136    }
137
138    /// Creates a verifier that only checks recovered certificates.
139    ///
140    /// This lightweight verifier can authenticate recovered threshold certificates but cannot
141    /// verify individual signatures or partial signatures.
142    ///
143    /// * `namespace` - base namespace for domain separation
144    /// * `identity` - public identity of the committee (constant across reshares)
145    pub fn certificate_verifier(namespace: &[u8], identity: V::Public) -> Self {
146        Self::CertificateVerifier {
147            identity,
148            namespace: N::derive(namespace),
149        }
150    }
151
152    /// Returns the ordered set of participant public identity keys in the committee.
153    pub fn participants(&self) -> &Set<P> {
154        match self {
155            Self::Signer { participants, .. } => participants,
156            Self::Verifier { participants, .. } => participants,
157            _ => panic!("can only be called for signer and verifier"),
158        }
159    }
160
161    /// Returns the public identity of the committee (constant across reshares).
162    pub fn identity(&self) -> &V::Public {
163        match self {
164            Self::Signer { polynomial, .. } => polynomial.public(),
165            Self::Verifier { polynomial, .. } => polynomial.public(),
166            Self::CertificateVerifier { identity, .. } => identity,
167        }
168    }
169
170    /// Returns the local share if this instance can generate partial signatures.
171    pub const fn share(&self) -> Option<&Share> {
172        match self {
173            Self::Signer { share, .. } => Some(share),
174            _ => None,
175        }
176    }
177
178    /// Returns the evaluated public polynomial for validating partial signatures produced by committee members.
179    fn polynomial(&self) -> &Sharing<V> {
180        match self {
181            Self::Signer { polynomial, .. } => polynomial,
182            Self::Verifier { polynomial, .. } => polynomial,
183            _ => panic!("can only be called for signer and verifier"),
184        }
185    }
186
187    /// Returns the pre-computed namespace.
188    const fn namespace(&self) -> &N {
189        match self {
190            Self::Signer { namespace, .. } => namespace,
191            Self::Verifier { namespace, .. } => namespace,
192            Self::CertificateVerifier { namespace, .. } => namespace,
193        }
194    }
195
196    /// Returns the index of "self" in the participant set, if available.
197    pub const fn me(&self) -> Option<Participant> {
198        match self {
199            Self::Signer { share, .. } => Some(share.index),
200            _ => None,
201        }
202    }
203
204    /// Signs a subject and returns the attestation.
205    pub fn sign<'a, S, D>(&self, subject: S::Subject<'a, D>) -> Option<Attestation<S>>
206    where
207        S: Scheme<Signature = V::Signature>,
208        S::Subject<'a, D>: Subject<Namespace = N>,
209        D: Digest,
210    {
211        let share = self.share()?;
212
213        let signature = threshold::sign_message::<V>(
214            share,
215            subject.namespace(self.namespace()),
216            &subject.message(),
217        )
218        .value;
219
220        Some(Attestation {
221            signer: share.index,
222            signature: signature.into(),
223        })
224    }
225
226    /// Verifies a single attestation from a signer.
227    pub fn verify_attestation<'a, S, D>(
228        &self,
229        subject: S::Subject<'a, D>,
230        attestation: &Attestation<S>,
231    ) -> bool
232    where
233        S: Scheme<Signature = V::Signature>,
234        S::Subject<'a, D>: Subject<Namespace = N>,
235        D: Digest,
236    {
237        let Ok(evaluated) = self.polynomial().partial_public(attestation.signer) else {
238            return false;
239        };
240        let Some(signature) = attestation.signature.get() else {
241            return false;
242        };
243
244        ops::verify_message::<V>(
245            &evaluated,
246            subject.namespace(self.namespace()),
247            &subject.message(),
248            signature,
249        )
250        .is_ok()
251    }
252
253    /// Batch-verifies attestations and returns verified attestations and invalid signers.
254    pub fn verify_attestations<'a, S, R, D, I, T>(
255        &self,
256        rng: &mut R,
257        subject: S::Subject<'a, D>,
258        attestations: I,
259        strategy: &T,
260    ) -> Verification<S>
261    where
262        S: Scheme<Signature = V::Signature>,
263        S::Subject<'a, D>: Subject<Namespace = N>,
264        R: CryptoRng,
265        D: Digest,
266        I: IntoIterator<Item = Attestation<S>>,
267        I::IntoIter: Send,
268        T: Strategy,
269    {
270        let (partials, failures) =
271            strategy.map_partition_collect_vec(attestations.into_iter(), |attestation| {
272                let index = attestation.signer;
273                let partial = attestation
274                    .signature
275                    .get()
276                    .map(|&value| PartialSignature::<V> { index, value });
277                (index, partial)
278            });
279        let mut invalid: BTreeSet<_> = failures.into_iter().collect();
280        let polynomial = self.polynomial();
281        if let Err(errs) = threshold::batch_verify_same_message::<_, V, _>(
282            rng,
283            polynomial,
284            subject.namespace(self.namespace()),
285            &subject.message(),
286            partials.iter(),
287            strategy,
288        ) {
289            for partial in errs {
290                invalid.insert(partial.index);
291            }
292        }
293
294        let verified = partials
295            .into_iter()
296            .filter(|partial| !invalid.contains(&partial.index))
297            .map(|partial| Attestation {
298                signer: partial.index,
299                signature: partial.value.into(),
300            })
301            .collect();
302
303        Verification::new(verified, invalid.into_iter().collect())
304    }
305
306    /// Assembles a certificate from a collection of attestations.
307    pub fn assemble<S, I, T, M>(&self, attestations: I, strategy: &T) -> Option<Certificate<V>>
308    where
309        S: Scheme<Signature = V::Signature>,
310        I: IntoIterator<Item = Attestation<S>>,
311        I::IntoIter: Send,
312        T: Strategy,
313        M: Faults,
314    {
315        let (partials, failures) =
316            strategy.map_partition_collect_vec(attestations.into_iter(), |attestation| {
317                let index = attestation.signer;
318                let value = attestation
319                    .signature
320                    .get()
321                    .map(|&sig| PartialSignature::<V> { index, value: sig });
322                (index, value)
323            });
324        if !failures.is_empty() {
325            return None;
326        }
327
328        let quorum = self.polynomial();
329        if partials.len() < quorum.required::<M>() as usize {
330            return None;
331        }
332
333        threshold::recover::<V, _, M>(quorum, partials.iter(), strategy)
334            .ok()
335            .map(Certificate::new)
336    }
337
338    /// Verifies a certificate.
339    pub fn verify_certificate<'a, S, R, D, M>(
340        &self,
341        _rng: &mut R,
342        subject: S::Subject<'a, D>,
343        certificate: &Certificate<V>,
344    ) -> bool
345    where
346        S: Scheme,
347        S::Subject<'a, D>: Subject<Namespace = N>,
348        R: CryptoRng,
349        D: Digest,
350        M: Faults,
351    {
352        let Some(signature) = certificate.get() else {
353            return false;
354        };
355        ops::verify_message::<V>(
356            self.identity(),
357            subject.namespace(self.namespace()),
358            &subject.message(),
359            signature,
360        )
361        .is_ok()
362    }
363
364    /// Verifies multiple certificates in a batch.
365    pub fn verify_certificates<'a, S, R, D, I, T, M>(
366        &self,
367        rng: &mut R,
368        certificates: I,
369        strategy: &T,
370    ) -> bool
371    where
372        S: Scheme,
373        S::Subject<'a, D>: Subject<Namespace = N>,
374        R: CryptoRng,
375        D: Digest,
376        I: Iterator<Item = (S::Subject<'a, D>, &'a Certificate<V>)>,
377        T: Strategy,
378        M: Faults,
379    {
380        let mut entries: Vec<_> = Vec::new();
381
382        for (subject, certificate) in certificates {
383            let Some(signature) = certificate.get() else {
384                return false;
385            };
386            let namespace = subject.namespace(self.namespace());
387            let message = subject.message();
388            entries.push((namespace.to_vec(), message.to_vec(), *signature));
389        }
390
391        if entries.is_empty() {
392            return true;
393        }
394
395        let entries_refs: Vec<_> = entries
396            .iter()
397            .map(|(ns, msg, sig)| (ns.as_ref(), msg.as_ref(), *sig))
398            .collect();
399
400        batch::verify_same_signer::<_, V, _>(rng, self.identity(), &entries_refs, strategy).is_ok()
401    }
402
403    pub const fn is_attributable() -> bool {
404        false
405    }
406
407    pub const fn is_batchable() -> bool {
408        true
409    }
410
411    pub const fn certificate_codec_config(&self) {}
412
413    pub const fn certificate_codec_config_unbounded() {}
414}
415
416/// Certificate for BLS12-381 threshold signatures.
417#[derive(Clone, Debug, PartialEq, Eq, Hash)]
418pub struct Certificate<V: Variant> {
419    /// The recovered threshold signature.
420    pub signature: Lazy<V::Signature>,
421}
422
423impl<V: Variant> Certificate<V> {
424    /// Creates a new certificate from a recovered signature.
425    pub fn new(signature: V::Signature) -> Self {
426        Self {
427            signature: Lazy::from(signature),
428        }
429    }
430
431    /// Attempts to get the decoded signature.
432    ///
433    /// Returns `None` if the signature fails to decode.
434    // `Lazy::get` is only `const` under some feature combinations, so this
435    // cannot be `const` without breaking the default build.
436    #[allow(clippy::missing_const_for_fn)]
437    pub fn get(&self) -> Option<&V::Signature> {
438        self.signature.get()
439    }
440}
441
442impl<V: Variant> Write for Certificate<V> {
443    fn write(&self, writer: &mut impl BufMut) {
444        self.signature.write(writer);
445    }
446}
447
448impl<V: Variant> Read for Certificate<V> {
449    type Cfg = ();
450
451    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
452        let signature = Lazy::<V::Signature>::read(reader)?;
453        Ok(Self { signature })
454    }
455}
456
457impl<V: Variant> FixedSize for Certificate<V> {
458    const SIZE: usize = V::Signature::SIZE;
459}
460
461#[cfg(feature = "arbitrary")]
462impl<V: Variant> arbitrary::Arbitrary<'_> for Certificate<V>
463where
464    V::Signature: for<'a> arbitrary::Arbitrary<'a>,
465{
466    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
467        Ok(Self {
468            signature: Lazy::from(u.arbitrary::<V::Signature>()?),
469        })
470    }
471}
472
473/// Generates a BLS12-381 threshold signing scheme wrapper for a specific protocol.
474///
475/// This macro creates a complete wrapper struct with constructors, `Scheme` trait
476/// implementation, and a `fixture` function for testing.
477///
478/// # Parameters
479///
480/// - `$subject`: The subject type used as `Scheme::Subject<'a, D>`. Use `'a` and `D`
481///   in the subject type to bind to the GAT lifetime and digest type parameters.
482///
483/// - `$namespace`: The namespace type that implements [`Namespace`].
484///   This type pre-computes and stores any protocol-specific namespace bytes derived from
485///   a base namespace. The scheme calls `$namespace::derive(base)` at construction time
486///   to create the namespace, then passes it to `Subject::namespace()` during signing
487///   and verification. For simple protocols with only a base namespace, `Vec<u8>` can be used directly.
488///   For protocols with multiple message types, a custom struct can pre-compute all variants.
489///
490/// # Example
491/// ```ignore
492/// // For non-generic subject types with a single namespace:
493/// impl_certificate_bls12381_threshold!(MySubject, Vec<u8>);
494///
495/// // For protocols with generic subject types:
496/// impl_certificate_bls12381_threshold!(Subject<'a, D>, Namespace);
497/// ```
498#[macro_export]
499macro_rules! impl_certificate_bls12381_threshold {
500    ($subject:ty, $namespace:ty) => {
501        /// Generates a test fixture with Ed25519 identities and BLS12-381 threshold schemes.
502        ///
503        /// Returns a [`commonware_cryptography::certificate::mocks::Fixture`] whose keys and
504        /// scheme instances share a consistent ordering.
505        #[cfg(feature = "mocks")]
506        #[allow(dead_code)]
507        pub fn fixture<V, R>(
508            rng: &mut R,
509            namespace: &[u8],
510            n: u32,
511        ) -> $crate::certificate::mocks::Fixture<Scheme<$crate::ed25519::PublicKey, V>>
512        where
513            V: $crate::bls12381::primitives::variant::Variant,
514            R: rand_core::CryptoRng,
515        {
516            $crate::bls12381::certificate::threshold::mocks::fixture::<_, V, _>(
517                rng,
518                namespace,
519                n,
520                Scheme::signer,
521                Scheme::verifier,
522            )
523        }
524
525        /// BLS12-381 threshold signature scheme wrapper.
526        #[derive(Clone, Debug)]
527        pub struct Scheme<
528            P: $crate::PublicKey,
529            V: $crate::bls12381::primitives::variant::Variant,
530        > {
531            generic: $crate::bls12381::certificate::threshold::Generic<P, V, $namespace>,
532        }
533
534        impl<
535            P: $crate::PublicKey,
536            V: $crate::bls12381::primitives::variant::Variant,
537        > Scheme<P, V> {
538            /// Creates a new signer instance with a private share and evaluated public polynomial.
539            pub fn signer(
540                namespace: &[u8],
541                participants: commonware_utils::ordered::Set<P>,
542                polynomial: $crate::bls12381::primitives::sharing::Sharing<V>,
543                share: $crate::bls12381::primitives::group::Share,
544            ) -> Option<Self> {
545                Some(Self {
546                    generic: $crate::bls12381::certificate::threshold::Generic::signer(
547                        namespace,
548                        participants,
549                        polynomial,
550                        share,
551                    )?,
552                })
553            }
554
555            /// Creates a verifier that can authenticate partial signatures.
556            pub fn verifier(
557                namespace: &[u8],
558                participants: commonware_utils::ordered::Set<P>,
559                polynomial: $crate::bls12381::primitives::sharing::Sharing<V>,
560            ) -> Self {
561                Self {
562                    generic: $crate::bls12381::certificate::threshold::Generic::verifier(
563                        namespace,
564                        participants,
565                        polynomial,
566                    ),
567                }
568            }
569
570            /// Creates a lightweight verifier that only checks recovered certificates.
571            pub fn certificate_verifier(namespace: &[u8], identity: V::Public) -> Self {
572                Self {
573                    generic: $crate::bls12381::certificate::threshold::Generic::certificate_verifier(
574                        namespace,
575                        identity,
576                    ),
577                }
578            }
579
580            /// Returns the public identity of the committee (constant across reshares).
581            pub fn identity(&self) -> &V::Public {
582                self.generic.identity()
583            }
584
585            /// Returns the local share if this instance can generate partial signatures.
586            pub const fn share(&self) -> Option<&$crate::bls12381::primitives::group::Share> {
587                self.generic.share()
588            }
589        }
590
591        impl<
592            P: $crate::PublicKey,
593            V: $crate::bls12381::primitives::variant::Variant,
594        > $crate::certificate::Verifier for Scheme<P, V> {
595            type Subject<'a, D: $crate::Digest> = $subject;
596            type PublicKey = P;
597            type Certificate = $crate::bls12381::certificate::threshold::Certificate<V>;
598
599            fn verify_certificate<R, D, M>(
600                &self,
601                rng: &mut R,
602                subject: Self::Subject<'_, D>,
603                certificate: &Self::Certificate,
604                _strategy: &impl commonware_parallel::Strategy,
605            ) -> bool
606            where
607                R: rand_core::CryptoRng,
608                D: $crate::Digest,
609                M: commonware_utils::Faults,
610            {
611                self.generic
612                    .verify_certificate::<Self, _, D, M>(rng, subject, certificate)
613            }
614
615            fn verify_certificates<'a, R, D, I, M>(
616                &self,
617                rng: &mut R,
618                certificates: I,
619                strategy: &impl commonware_parallel::Strategy,
620            ) -> bool
621            where
622                R: rand_core::CryptoRng,
623                D: $crate::Digest,
624                I: Iterator<Item = (Self::Subject<'a, D>, &'a Self::Certificate)>,
625                M: commonware_utils::Faults,
626            {
627                self.generic
628                    .verify_certificates::<Self, _, D, _, _, M>(rng, certificates, strategy)
629            }
630
631            fn is_batchable() -> bool {
632                $crate::bls12381::certificate::threshold::Generic::<P, V, $namespace>::is_batchable()
633            }
634
635            fn certificate_codec_config(
636                &self,
637            ) -> <Self::Certificate as commonware_codec::Read>::Cfg {
638                self.generic.certificate_codec_config()
639            }
640
641            fn certificate_codec_config_unbounded(
642            ) -> <Self::Certificate as commonware_codec::Read>::Cfg {
643                $crate::bls12381::certificate::threshold::Generic::<P, V, $namespace>::certificate_codec_config_unbounded()
644            }
645        }
646
647        impl<
648            P: $crate::PublicKey,
649            V: $crate::bls12381::primitives::variant::Variant,
650        > $crate::certificate::Scheme for Scheme<P, V> {
651            type Signature = V::Signature;
652
653            fn me(&self) -> Option<commonware_utils::Participant> {
654                self.generic.me()
655            }
656
657            fn participants(&self) -> &commonware_utils::ordered::Set<Self::PublicKey> {
658                self.generic.participants()
659            }
660
661            fn sign<D: $crate::Digest>(
662                &self,
663                subject: Self::Subject<'_, D>,
664            ) -> Option<$crate::certificate::Attestation<Self>> {
665                self.generic.sign::<_, D>(subject)
666            }
667
668            fn verify_attestation<R, D>(
669                &self,
670                _rng: &mut R,
671                subject: Self::Subject<'_, D>,
672                attestation: &$crate::certificate::Attestation<Self>,
673                _strategy: &impl commonware_parallel::Strategy,
674            ) -> bool
675            where
676                R: rand_core::CryptoRng,
677                D: $crate::Digest,
678            {
679                self.generic
680                    .verify_attestation::<_, D>(subject, attestation)
681            }
682
683            fn verify_attestations<R, D, I>(
684                &self,
685                rng: &mut R,
686                subject: Self::Subject<'_, D>,
687                attestations: I,
688                strategy: &impl commonware_parallel::Strategy,
689            ) -> $crate::certificate::Verification<Self>
690            where
691                R: rand_core::CryptoRng,
692                D: $crate::Digest,
693                I: IntoIterator<Item = $crate::certificate::Attestation<Self>>,
694                I::IntoIter: Send
695            {
696                self.generic
697                    .verify_attestations::<_, _, D, _, _>(rng, subject, attestations, strategy)
698            }
699
700            fn assemble<I, M>(
701                &self,
702                attestations: I,
703                strategy: &impl commonware_parallel::Strategy,
704            ) -> Option<Self::Certificate>
705            where
706                I: IntoIterator<Item = $crate::certificate::Attestation<Self>>,
707                I::IntoIter: Send,
708                M: commonware_utils::Faults,
709            {
710                self.generic.assemble::<Self, _, _, M>(attestations, strategy)
711            }
712
713            fn is_attributable() -> bool {
714                $crate::bls12381::certificate::threshold::Generic::<P, V, $namespace>::is_attributable()
715            }
716        }
717    };
718}
719
720#[cfg(test)]
721mod tests {
722    use super::*;
723    use crate::{
724        bls12381::{
725            dkg::feldman_desmedt as dkg,
726            primitives::{
727                ops::threshold::sign_message,
728                variant::{MinPk, MinSig, Variant},
729            },
730        },
731        certificate::{Scheme as _, Verifier as _},
732        ed25519::{self, PrivateKey as Ed25519PrivateKey},
733        sha256::Digest as Sha256Digest,
734        Signer as _,
735    };
736    use bytes::Bytes;
737    use commonware_codec::{DecodeExt, Encode};
738    use commonware_math::algebra::{Additive, Random};
739    use commonware_parallel::Sequential;
740    use commonware_utils::{ordered::Set, test_rng, Faults, N3f1, TryCollect, NZU32};
741
742    const NAMESPACE: &[u8] = b"test-bls12381-threshold";
743    const MESSAGE: &[u8] = b"test message";
744
745    /// Test context type for generic scheme tests.
746    #[derive(Clone, Debug)]
747    pub struct TestSubject {
748        pub message: Bytes,
749    }
750
751    impl Subject for TestSubject {
752        type Namespace = Vec<u8>;
753
754        fn namespace<'a>(&self, derived: &'a Self::Namespace) -> &'a [u8] {
755            derived
756        }
757
758        fn message(&self) -> Bytes {
759            self.message.clone()
760        }
761    }
762
763    // Use the macro to generate the test scheme
764    impl_certificate_bls12381_threshold!(TestSubject, Vec<u8>);
765
766    #[allow(clippy::type_complexity)]
767    fn setup_signers<V: Variant>(
768        rng: &mut impl CryptoRng,
769        n: u32,
770    ) -> (
771        Vec<Scheme<ed25519::PublicKey, V>>,
772        Scheme<ed25519::PublicKey, V>,
773        Sharing<V>,
774    ) {
775        // Generate identity keys (ed25519)
776        let identity_keys: Vec<_> = (0..n)
777            .map(|_| Ed25519PrivateKey::random(&mut *rng))
778            .collect();
779        let participants: Set<ed25519::PublicKey> = identity_keys
780            .iter()
781            .map(|sk| sk.public_key())
782            .try_collect()
783            .unwrap();
784
785        // Generate threshold polynomial and shares using DKG
786        let (polynomial, shares) =
787            dkg::deal_anonymous::<V, N3f1>(&mut *rng, Default::default(), NZU32!(n));
788
789        let signers = shares
790            .into_iter()
791            .map(|share| {
792                Scheme::signer(NAMESPACE, participants.clone(), polynomial.clone(), share).unwrap()
793            })
794            .collect();
795
796        let verifier = Scheme::verifier(NAMESPACE, participants, polynomial.clone());
797
798        (signers, verifier, polynomial)
799    }
800
801    fn test_sign_vote_roundtrip<V: Variant>() {
802        let mut rng = test_rng();
803        let (schemes, _, _) = setup_signers::<V>(&mut rng, 4);
804        let scheme = &schemes[0];
805
806        let attestation = scheme
807            .sign::<Sha256Digest>(TestSubject {
808                message: Bytes::from_static(MESSAGE),
809            })
810            .unwrap();
811        assert!(scheme.verify_attestation::<_, Sha256Digest>(
812            &mut rng,
813            TestSubject {
814                message: Bytes::from_static(MESSAGE),
815            },
816            &attestation,
817            &Sequential,
818        ));
819    }
820
821    #[test]
822    fn test_sign_vote_roundtrip_variants() {
823        test_sign_vote_roundtrip::<MinPk>();
824        test_sign_vote_roundtrip::<MinSig>();
825    }
826
827    fn test_verifier_cannot_sign<V: Variant>() {
828        let mut rng = test_rng();
829        let (_, verifier, _) = setup_signers::<V>(&mut rng, 4);
830        assert!(verifier
831            .sign::<Sha256Digest>(TestSubject {
832                message: Bytes::from_static(MESSAGE),
833            })
834            .is_none());
835    }
836
837    #[test]
838    fn test_verifier_cannot_sign_variants() {
839        test_verifier_cannot_sign::<MinPk>();
840        test_verifier_cannot_sign::<MinSig>();
841    }
842
843    fn test_verify_attestations_filters_invalid<V: Variant>() {
844        let mut rng = test_rng();
845        let (schemes, _, _) = setup_signers::<V>(&mut rng, 5);
846        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
847
848        let attestations: Vec<_> = schemes
849            .iter()
850            .take(quorum)
851            .map(|s| {
852                s.sign::<Sha256Digest>(TestSubject {
853                    message: Bytes::from_static(MESSAGE),
854                })
855                .unwrap()
856            })
857            .collect();
858
859        let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
860            &mut rng,
861            TestSubject {
862                message: Bytes::from_static(MESSAGE),
863            },
864            attestations.clone(),
865            &Sequential,
866        );
867        assert!(result.invalid.is_empty());
868        assert_eq!(result.verified.len(), quorum);
869
870        // Test: Corrupt one attestation - invalid signer index
871        let mut attestations_corrupted = attestations.clone();
872        attestations_corrupted[0].signer = Participant::new(999);
873        let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
874            &mut rng,
875            TestSubject {
876                message: Bytes::from_static(MESSAGE),
877            },
878            attestations_corrupted,
879            &Sequential,
880        );
881        assert_eq!(result.invalid, vec![Participant::new(999)]);
882        assert_eq!(result.verified.len(), quorum - 1);
883
884        // Test: Corrupt one attestation - invalid signature
885        let mut attestations_corrupted = attestations;
886        attestations_corrupted[0].signature = attestations_corrupted[1].signature.clone();
887        let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
888            &mut rng,
889            TestSubject {
890                message: Bytes::from_static(MESSAGE),
891            },
892            attestations_corrupted,
893            &Sequential,
894        );
895        assert_eq!(result.invalid.len(), 1);
896        assert_eq!(result.verified.len(), quorum - 1);
897    }
898
899    #[test]
900    fn test_verify_attestations_filters_invalid_variants() {
901        test_verify_attestations_filters_invalid::<MinPk>();
902        test_verify_attestations_filters_invalid::<MinSig>();
903    }
904
905    fn test_assemble_certificate<V: Variant>() {
906        let mut rng = test_rng();
907        let (schemes, verifier, _) = setup_signers::<V>(&mut rng, 4);
908        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
909
910        let attestations: Vec<_> = schemes
911            .iter()
912            .take(quorum)
913            .map(|s| {
914                s.sign::<Sha256Digest>(TestSubject {
915                    message: Bytes::from_static(MESSAGE),
916                })
917                .unwrap()
918            })
919            .collect();
920
921        let certificate = schemes[0]
922            .assemble::<_, N3f1>(attestations, &Sequential)
923            .unwrap();
924
925        // Verify the assembled certificate
926        assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
927            &mut rng,
928            TestSubject {
929                message: Bytes::from_static(MESSAGE),
930            },
931            &certificate,
932            &Sequential,
933        ));
934    }
935
936    #[test]
937    fn test_assemble_certificate_variants() {
938        test_assemble_certificate::<MinPk>();
939        test_assemble_certificate::<MinSig>();
940    }
941
942    fn test_verify_certificate<V: Variant>() {
943        let mut rng = test_rng();
944        let (schemes, verifier, _) = setup_signers::<V>(&mut rng, 4);
945        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
946
947        let attestations: Vec<_> = schemes
948            .iter()
949            .take(quorum)
950            .map(|s| {
951                s.sign::<Sha256Digest>(TestSubject {
952                    message: Bytes::from_static(MESSAGE),
953                })
954                .unwrap()
955            })
956            .collect();
957
958        let certificate = schemes[0]
959            .assemble::<_, N3f1>(attestations, &Sequential)
960            .unwrap();
961
962        assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
963            &mut rng,
964            TestSubject {
965                message: Bytes::from_static(MESSAGE),
966            },
967            &certificate,
968            &Sequential,
969        ));
970    }
971
972    #[test]
973    fn test_verify_certificate_variants() {
974        test_verify_certificate::<MinPk>();
975        test_verify_certificate::<MinSig>();
976    }
977
978    fn test_verify_certificate_detects_corruption<V: Variant>() {
979        let mut rng = test_rng();
980        let (schemes, verifier, _) = setup_signers::<V>(&mut rng, 4);
981        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
982
983        let attestations: Vec<_> = schemes
984            .iter()
985            .take(quorum)
986            .map(|s| {
987                s.sign::<Sha256Digest>(TestSubject {
988                    message: Bytes::from_static(MESSAGE),
989                })
990                .unwrap()
991            })
992            .collect();
993
994        let certificate = schemes[0]
995            .assemble::<_, N3f1>(attestations, &Sequential)
996            .unwrap();
997
998        // Valid certificate passes
999        assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1000            &mut rng,
1001            TestSubject {
1002                message: Bytes::from_static(MESSAGE),
1003            },
1004            &certificate,
1005            &Sequential,
1006        ));
1007
1008        // Corrupted certificate fails
1009        let corrupted = Certificate::new(V::Signature::zero());
1010        assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1011            &mut rng,
1012            TestSubject {
1013                message: Bytes::from_static(MESSAGE),
1014            },
1015            &corrupted,
1016            &Sequential,
1017        ));
1018    }
1019
1020    #[test]
1021    fn test_verify_certificate_detects_corruption_variants() {
1022        test_verify_certificate_detects_corruption::<MinPk>();
1023        test_verify_certificate_detects_corruption::<MinSig>();
1024    }
1025
1026    fn test_certificate_codec_roundtrip<V: Variant>() {
1027        let mut rng = test_rng();
1028        let (schemes, _, _) = setup_signers::<V>(&mut rng, 4);
1029        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
1030
1031        let attestations: Vec<_> = schemes
1032            .iter()
1033            .take(quorum)
1034            .map(|s| {
1035                s.sign::<Sha256Digest>(TestSubject {
1036                    message: Bytes::from_static(MESSAGE),
1037                })
1038                .unwrap()
1039            })
1040            .collect();
1041
1042        let certificate = schemes[0]
1043            .assemble::<_, N3f1>(attestations, &Sequential)
1044            .unwrap();
1045        let encoded = certificate.encode();
1046        let decoded = Certificate::<V>::decode(encoded).expect("decode certificate");
1047        assert_eq!(decoded, certificate);
1048    }
1049
1050    #[test]
1051    fn test_certificate_codec_roundtrip_variants() {
1052        test_certificate_codec_roundtrip::<MinPk>();
1053        test_certificate_codec_roundtrip::<MinSig>();
1054    }
1055
1056    fn test_certificate_rejects_sub_quorum<V: Variant>() {
1057        let mut rng = test_rng();
1058        let (schemes, _, _) = setup_signers::<V>(&mut rng, 4);
1059        let sub_quorum = 2; // Less than quorum (3)
1060
1061        let attestations: Vec<_> = schemes
1062            .iter()
1063            .take(sub_quorum)
1064            .map(|s| {
1065                s.sign::<Sha256Digest>(TestSubject {
1066                    message: Bytes::from_static(MESSAGE),
1067                })
1068                .unwrap()
1069            })
1070            .collect();
1071
1072        assert!(schemes[0]
1073            .assemble::<_, N3f1>(attestations, &Sequential)
1074            .is_none());
1075    }
1076
1077    #[test]
1078    fn test_certificate_rejects_sub_quorum_variants() {
1079        test_certificate_rejects_sub_quorum::<MinPk>();
1080        test_certificate_rejects_sub_quorum::<MinSig>();
1081    }
1082
1083    fn test_verify_certificates_batch<V: Variant>() {
1084        let mut rng = test_rng();
1085        let (schemes, verifier, _) = setup_signers::<V>(&mut rng, 4);
1086        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
1087
1088        let messages: [Bytes; 3] = [
1089            Bytes::from_static(b"msg1"),
1090            Bytes::from_static(b"msg2"),
1091            Bytes::from_static(b"msg3"),
1092        ];
1093        let mut certificates = Vec::new();
1094
1095        for msg in &messages {
1096            let attestations: Vec<_> = schemes
1097                .iter()
1098                .take(quorum)
1099                .map(|s| {
1100                    s.sign::<Sha256Digest>(TestSubject {
1101                        message: msg.clone(),
1102                    })
1103                    .unwrap()
1104                })
1105                .collect();
1106            certificates.push(
1107                schemes[0]
1108                    .assemble::<_, N3f1>(attestations, &Sequential)
1109                    .unwrap(),
1110            );
1111        }
1112
1113        let certs_iter = messages.iter().zip(&certificates).map(|(msg, cert)| {
1114            (
1115                TestSubject {
1116                    message: msg.clone(),
1117                },
1118                cert,
1119            )
1120        });
1121
1122        assert!(verifier.verify_certificates::<_, Sha256Digest, _, N3f1>(
1123            &mut rng,
1124            certs_iter,
1125            &Sequential
1126        ));
1127    }
1128
1129    #[test]
1130    fn test_verify_certificates_batch_variants() {
1131        test_verify_certificates_batch::<MinPk>();
1132        test_verify_certificates_batch::<MinSig>();
1133    }
1134
1135    fn test_verify_certificates_batch_detects_failure<V: Variant>() {
1136        let mut rng = test_rng();
1137        let (schemes, verifier, _) = setup_signers::<V>(&mut rng, 4);
1138        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
1139
1140        let messages: [Bytes; 2] = [Bytes::from_static(b"msg1"), Bytes::from_static(b"msg2")];
1141        let mut certificates = Vec::new();
1142
1143        for msg in &messages {
1144            let attestations: Vec<_> = schemes
1145                .iter()
1146                .take(quorum)
1147                .map(|s| {
1148                    s.sign::<Sha256Digest>(TestSubject {
1149                        message: msg.clone(),
1150                    })
1151                    .unwrap()
1152                })
1153                .collect();
1154            certificates.push(
1155                schemes[0]
1156                    .assemble::<_, N3f1>(attestations, &Sequential)
1157                    .unwrap(),
1158            );
1159        }
1160
1161        // Corrupt second certificate
1162        certificates[1] = Certificate::new(V::Signature::zero());
1163
1164        let certs_iter = messages.iter().zip(&certificates).map(|(msg, cert)| {
1165            (
1166                TestSubject {
1167                    message: msg.clone(),
1168                },
1169                cert,
1170            )
1171        });
1172
1173        assert!(!verifier.verify_certificates::<_, Sha256Digest, _, N3f1>(
1174            &mut rng,
1175            certs_iter,
1176            &Sequential
1177        ));
1178    }
1179
1180    #[test]
1181    fn test_verify_certificates_batch_detects_failure_variants() {
1182        test_verify_certificates_batch_detects_failure::<MinPk>();
1183        test_verify_certificates_batch_detects_failure::<MinSig>();
1184    }
1185
1186    fn test_certificate_verifier<V: Variant>() {
1187        let mut rng = test_rng();
1188        let (schemes, _, polynomial) = setup_signers::<V>(&mut rng, 4);
1189        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
1190
1191        let attestations: Vec<_> = schemes
1192            .iter()
1193            .take(quorum)
1194            .map(|s| {
1195                s.sign::<Sha256Digest>(TestSubject {
1196                    message: Bytes::from_static(MESSAGE),
1197                })
1198                .unwrap()
1199            })
1200            .collect();
1201
1202        let certificate = schemes[0]
1203            .assemble::<_, N3f1>(attestations, &Sequential)
1204            .unwrap();
1205
1206        // Create a certificate-only verifier using the identity from the polynomial
1207        let identity = polynomial.public();
1208        let cert_verifier =
1209            Scheme::<ed25519::PublicKey, V>::certificate_verifier(NAMESPACE, *identity);
1210
1211        // Should be able to verify certificates
1212        assert!(cert_verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1213            &mut rng,
1214            TestSubject {
1215                message: Bytes::from_static(MESSAGE),
1216            },
1217            &certificate,
1218            &Sequential,
1219        ));
1220
1221        // Should not be able to sign
1222        assert!(cert_verifier
1223            .sign::<Sha256Digest>(TestSubject {
1224                message: Bytes::from_static(MESSAGE),
1225            })
1226            .is_none());
1227    }
1228
1229    #[test]
1230    fn test_certificate_verifier_variants() {
1231        test_certificate_verifier::<MinPk>();
1232        test_certificate_verifier::<MinSig>();
1233    }
1234
1235    #[test]
1236    fn test_is_not_attributable() {
1237        assert!(!Generic::<ed25519::PublicKey, MinPk, Vec<u8>>::is_attributable());
1238        assert!(!Scheme::<ed25519::PublicKey, MinPk>::is_attributable());
1239        assert!(!Generic::<ed25519::PublicKey, MinSig, Vec<u8>>::is_attributable());
1240        assert!(!Scheme::<ed25519::PublicKey, MinSig>::is_attributable());
1241    }
1242
1243    #[test]
1244    fn test_is_batchable() {
1245        assert!(Generic::<ed25519::PublicKey, MinPk, Vec<u8>>::is_batchable());
1246        assert!(Scheme::<ed25519::PublicKey, MinPk>::is_batchable());
1247        assert!(Generic::<ed25519::PublicKey, MinSig, Vec<u8>>::is_batchable());
1248        assert!(Scheme::<ed25519::PublicKey, MinSig>::is_batchable());
1249    }
1250
1251    fn test_verifier_accepts_votes<V: Variant>() {
1252        let mut rng = test_rng();
1253        let (schemes, verifier, _) = setup_signers::<V>(&mut rng, 4);
1254
1255        let vote = schemes[1]
1256            .sign::<Sha256Digest>(TestSubject {
1257                message: Bytes::from_static(MESSAGE),
1258            })
1259            .unwrap();
1260        assert!(verifier.verify_attestation::<_, Sha256Digest>(
1261            &mut rng,
1262            TestSubject {
1263                message: Bytes::from_static(MESSAGE),
1264            },
1265            &vote,
1266            &Sequential,
1267        ));
1268    }
1269
1270    #[test]
1271    fn test_verifier_accepts_votes_variants() {
1272        test_verifier_accepts_votes::<MinPk>();
1273        test_verifier_accepts_votes::<MinSig>();
1274    }
1275
1276    fn test_scheme_clone_and_verifier<V: Variant>() {
1277        let mut rng = test_rng();
1278        let (schemes, verifier, _) = setup_signers::<V>(&mut rng, 4);
1279
1280        // Clone a signer
1281        let signer = schemes[0].clone();
1282        assert!(
1283            signer
1284                .sign::<Sha256Digest>(TestSubject {
1285                    message: Bytes::from_static(MESSAGE),
1286                })
1287                .is_some(),
1288            "signer should produce votes"
1289        );
1290
1291        // A verifier cannot produce votes
1292        assert!(
1293            verifier
1294                .sign::<Sha256Digest>(TestSubject {
1295                    message: Bytes::from_static(MESSAGE),
1296                })
1297                .is_none(),
1298            "verifier should not produce votes"
1299        );
1300    }
1301
1302    #[test]
1303    fn test_scheme_clone_and_verifier_variants() {
1304        test_scheme_clone_and_verifier::<MinPk>();
1305        test_scheme_clone_and_verifier::<MinSig>();
1306    }
1307
1308    fn certificate_verifier_panics_on_vote<V: Variant>() {
1309        let mut rng = test_rng();
1310        let (schemes, _, _) = setup_signers::<V>(&mut rng, 4);
1311        let certificate_verifier = Scheme::<ed25519::PublicKey, V>::certificate_verifier(
1312            NAMESPACE,
1313            *schemes[0].identity(),
1314        );
1315
1316        let vote = schemes[1]
1317            .sign::<Sha256Digest>(TestSubject {
1318                message: Bytes::from_static(MESSAGE),
1319            })
1320            .unwrap();
1321
1322        // CertificateVerifier should panic when trying to verify a vote
1323        certificate_verifier.verify_attestation::<_, Sha256Digest>(
1324            &mut rng,
1325            TestSubject {
1326                message: Bytes::from_static(MESSAGE),
1327            },
1328            &vote,
1329            &Sequential,
1330        );
1331    }
1332
1333    #[test]
1334    #[should_panic(expected = "can only be called for signer and verifier")]
1335    fn test_certificate_verifier_panics_on_vote_min_pk() {
1336        certificate_verifier_panics_on_vote::<MinPk>();
1337    }
1338
1339    #[test]
1340    #[should_panic(expected = "can only be called for signer and verifier")]
1341    fn test_certificate_verifier_panics_on_vote_min_sig() {
1342        certificate_verifier_panics_on_vote::<MinSig>();
1343    }
1344
1345    fn signer_shares_must_match_participant_indices<V: Variant>() {
1346        let mut rng = test_rng();
1347
1348        // Generate identity keys (ed25519)
1349        let identity_keys: Vec<_> = (0..4)
1350            .map(|_| Ed25519PrivateKey::random(&mut rng))
1351            .collect();
1352        let participants: Set<ed25519::PublicKey> = identity_keys
1353            .iter()
1354            .map(|sk| sk.public_key())
1355            .try_collect()
1356            .unwrap();
1357
1358        let (polynomial, mut shares) =
1359            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(4));
1360        shares[0].index = Participant::new(999);
1361        Scheme::<ed25519::PublicKey, V>::signer(
1362            NAMESPACE,
1363            participants,
1364            polynomial,
1365            shares[0].clone(),
1366        );
1367    }
1368
1369    #[test]
1370    #[should_panic(expected = "share index must match participant indices")]
1371    fn test_signer_shares_must_match_participant_indices_min_pk() {
1372        signer_shares_must_match_participant_indices::<MinPk>();
1373    }
1374
1375    #[test]
1376    #[should_panic(expected = "share index must match participant indices")]
1377    fn test_signer_shares_must_match_participant_indices_min_sig() {
1378        signer_shares_must_match_participant_indices::<MinSig>();
1379    }
1380
1381    fn make_participants<R: rand_core::CryptoRng>(rng: &mut R, n: u32) -> Set<ed25519::PublicKey> {
1382        (0..n)
1383            .map(|_| Ed25519PrivateKey::random(&mut *rng).public_key())
1384            .try_collect()
1385            .expect("participants are unique")
1386    }
1387
1388    fn signer_polynomial_threshold_must_equal_quorum<V: Variant>() {
1389        let mut rng = test_rng();
1390        let participants = make_participants(&mut rng, 5);
1391        // Create a polynomial with threshold 4, but quorum of 5 participants is 4
1392        // so this should succeed. Let's use threshold 2 to make it fail.
1393        // quorum(5) = 4, but polynomial.required() = 2, so this should panic
1394        let (polynomial, shares) =
1395            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(2));
1396        Scheme::<ed25519::PublicKey, V>::signer(
1397            NAMESPACE,
1398            participants,
1399            polynomial,
1400            shares[0].clone(),
1401        );
1402    }
1403
1404    #[test]
1405    #[should_panic(expected = "polynomial total must equal participant len")]
1406    fn test_signer_polynomial_threshold_must_equal_quorum_min_pk() {
1407        signer_polynomial_threshold_must_equal_quorum::<MinPk>();
1408    }
1409
1410    #[test]
1411    #[should_panic(expected = "polynomial total must equal participant len")]
1412    fn test_signer_polynomial_threshold_must_equal_quorum_min_sig() {
1413        signer_polynomial_threshold_must_equal_quorum::<MinSig>();
1414    }
1415
1416    fn verifier_polynomial_threshold_must_equal_quorum<V: Variant>() {
1417        let mut rng = test_rng();
1418        let participants = make_participants(&mut rng, 5);
1419        // Create a polynomial with threshold 2, but quorum of 5 participants is 4
1420        // quorum(5) = 4, but polynomial.required() = 2, so this should panic
1421        let (polynomial, _) =
1422            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(2));
1423        Scheme::<ed25519::PublicKey, V>::verifier(NAMESPACE, participants, polynomial);
1424    }
1425
1426    #[test]
1427    #[should_panic(expected = "polynomial total must equal participant len")]
1428    fn test_verifier_polynomial_threshold_must_equal_quorum_min_pk() {
1429        verifier_polynomial_threshold_must_equal_quorum::<MinPk>();
1430    }
1431
1432    #[test]
1433    #[should_panic(expected = "polynomial total must equal participant len")]
1434    fn test_verifier_polynomial_threshold_must_equal_quorum_min_sig() {
1435        verifier_polynomial_threshold_must_equal_quorum::<MinSig>();
1436    }
1437
1438    fn certificate_decode_rejects_length_mismatch<V: Variant>() {
1439        let mut rng = test_rng();
1440        let (schemes, _, _) = setup_signers::<V>(&mut rng, 4);
1441        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
1442
1443        let attestations: Vec<_> = schemes
1444            .iter()
1445            .take(quorum)
1446            .map(|s| {
1447                s.sign::<Sha256Digest>(TestSubject {
1448                    message: Bytes::from_static(MESSAGE),
1449                })
1450                .unwrap()
1451            })
1452            .collect();
1453
1454        let certificate = schemes[0]
1455            .assemble::<_, N3f1>(attestations, &Sequential)
1456            .unwrap();
1457        let mut encoded = certificate.encode();
1458        encoded.truncate(encoded.len() - 1);
1459        assert!(V::Signature::decode(encoded).is_err());
1460    }
1461
1462    #[test]
1463    fn test_certificate_decode_rejects_length_mismatch_variants() {
1464        certificate_decode_rejects_length_mismatch::<MinPk>();
1465        certificate_decode_rejects_length_mismatch::<MinSig>();
1466    }
1467
1468    fn sign_vote_partial_matches_share<V: Variant>() {
1469        let mut rng = test_rng();
1470        let (schemes, _, _) = setup_signers::<V>(&mut rng, 4);
1471        let scheme = &schemes[0];
1472
1473        let signature = scheme
1474            .sign::<Sha256Digest>(TestSubject {
1475                message: Bytes::from_static(MESSAGE),
1476            })
1477            .unwrap();
1478
1479        // Verify the partial signature matches what we'd get from direct signing
1480        let share = scheme.share().expect("expected signer");
1481
1482        let expected = sign_message::<V>(share, NAMESPACE, MESSAGE);
1483
1484        assert_eq!(signature.signer, share.index);
1485        assert_eq!(signature.signature.get().unwrap(), &expected.value);
1486    }
1487
1488    #[test]
1489    fn test_sign_vote_partial_matches_share_variants() {
1490        sign_vote_partial_matches_share::<MinPk>();
1491        sign_vote_partial_matches_share::<MinSig>();
1492    }
1493
1494    #[cfg(feature = "arbitrary")]
1495    mod conformance {
1496        use super::*;
1497        use commonware_codec::conformance::CodecConformance;
1498
1499        commonware_conformance::conformance_tests! {
1500            CodecConformance<Certificate<MinSig>>,
1501        }
1502    }
1503}