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::CryptoRngCore;
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: CryptoRngCore,
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: CryptoRngCore,
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: CryptoRngCore,
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    pub fn get(&self) -> Option<&V::Signature> {
435        self.signature.get()
436    }
437}
438
439impl<V: Variant> Write for Certificate<V> {
440    fn write(&self, writer: &mut impl BufMut) {
441        self.signature.write(writer);
442    }
443}
444
445impl<V: Variant> Read for Certificate<V> {
446    type Cfg = ();
447
448    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
449        let signature = Lazy::<V::Signature>::read(reader)?;
450        Ok(Self { signature })
451    }
452}
453
454impl<V: Variant> FixedSize for Certificate<V> {
455    const SIZE: usize = V::Signature::SIZE;
456}
457
458#[cfg(feature = "arbitrary")]
459impl<V: Variant> arbitrary::Arbitrary<'_> for Certificate<V>
460where
461    V::Signature: for<'a> arbitrary::Arbitrary<'a>,
462{
463    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
464        Ok(Self {
465            signature: Lazy::from(u.arbitrary::<V::Signature>()?),
466        })
467    }
468}
469
470/// Generates a BLS12-381 threshold signing scheme wrapper for a specific protocol.
471///
472/// This macro creates a complete wrapper struct with constructors, `Scheme` trait
473/// implementation, and a `fixture` function for testing.
474///
475/// # Parameters
476///
477/// - `$subject`: The subject type used as `Scheme::Subject<'a, D>`. Use `'a` and `D`
478///   in the subject type to bind to the GAT lifetime and digest type parameters.
479///
480/// - `$namespace`: The namespace type that implements [`Namespace`].
481///   This type pre-computes and stores any protocol-specific namespace bytes derived from
482///   a base namespace. The scheme calls `$namespace::derive(base)` at construction time
483///   to create the namespace, then passes it to `Subject::namespace()` during signing
484///   and verification. For simple protocols with only a base namespace, `Vec<u8>` can be used directly.
485///   For protocols with multiple message types, a custom struct can pre-compute all variants.
486///
487/// # Example
488/// ```ignore
489/// // For non-generic subject types with a single namespace:
490/// impl_certificate_bls12381_threshold!(MySubject, Vec<u8>);
491///
492/// // For protocols with generic subject types:
493/// impl_certificate_bls12381_threshold!(Subject<'a, D>, Namespace);
494/// ```
495#[macro_export]
496macro_rules! impl_certificate_bls12381_threshold {
497    ($subject:ty, $namespace:ty) => {
498        /// Generates a test fixture with Ed25519 identities and BLS12-381 threshold schemes.
499        ///
500        /// Returns a [`commonware_cryptography::certificate::mocks::Fixture`] whose keys and
501        /// scheme instances share a consistent ordering.
502        #[cfg(feature = "mocks")]
503        #[allow(dead_code)]
504        pub fn fixture<V, R>(
505            rng: &mut R,
506            namespace: &[u8],
507            n: u32,
508        ) -> $crate::certificate::mocks::Fixture<Scheme<$crate::ed25519::PublicKey, V>>
509        where
510            V: $crate::bls12381::primitives::variant::Variant,
511            R: rand::RngCore + rand::CryptoRng,
512        {
513            $crate::bls12381::certificate::threshold::mocks::fixture::<_, V, _>(
514                rng,
515                namespace,
516                n,
517                Scheme::signer,
518                Scheme::verifier,
519            )
520        }
521
522        /// BLS12-381 threshold signature scheme wrapper.
523        #[derive(Clone, Debug)]
524        pub struct Scheme<
525            P: $crate::PublicKey,
526            V: $crate::bls12381::primitives::variant::Variant,
527        > {
528            generic: $crate::bls12381::certificate::threshold::Generic<P, V, $namespace>,
529        }
530
531        impl<
532            P: $crate::PublicKey,
533            V: $crate::bls12381::primitives::variant::Variant,
534        > Scheme<P, V> {
535            /// Creates a new signer instance with a private share and evaluated public polynomial.
536            pub fn signer(
537                namespace: &[u8],
538                participants: commonware_utils::ordered::Set<P>,
539                polynomial: $crate::bls12381::primitives::sharing::Sharing<V>,
540                share: $crate::bls12381::primitives::group::Share,
541            ) -> Option<Self> {
542                Some(Self {
543                    generic: $crate::bls12381::certificate::threshold::Generic::signer(
544                        namespace,
545                        participants,
546                        polynomial,
547                        share,
548                    )?,
549                })
550            }
551
552            /// Creates a verifier that can authenticate partial signatures.
553            pub fn verifier(
554                namespace: &[u8],
555                participants: commonware_utils::ordered::Set<P>,
556                polynomial: $crate::bls12381::primitives::sharing::Sharing<V>,
557            ) -> Self {
558                Self {
559                    generic: $crate::bls12381::certificate::threshold::Generic::verifier(
560                        namespace,
561                        participants,
562                        polynomial,
563                    ),
564                }
565            }
566
567            /// Creates a lightweight verifier that only checks recovered certificates.
568            pub fn certificate_verifier(namespace: &[u8], identity: V::Public) -> Self {
569                Self {
570                    generic: $crate::bls12381::certificate::threshold::Generic::certificate_verifier(
571                        namespace,
572                        identity,
573                    ),
574                }
575            }
576
577            /// Returns the public identity of the committee (constant across reshares).
578            pub fn identity(&self) -> &V::Public {
579                self.generic.identity()
580            }
581
582            /// Returns the local share if this instance can generate partial signatures.
583            pub const fn share(&self) -> Option<&$crate::bls12381::primitives::group::Share> {
584                self.generic.share()
585            }
586        }
587
588        impl<
589            P: $crate::PublicKey,
590            V: $crate::bls12381::primitives::variant::Variant,
591        > $crate::certificate::Scheme for Scheme<P, V> {
592            type Subject<'a, D: $crate::Digest> = $subject;
593            type PublicKey = P;
594            type Signature = V::Signature;
595            type Certificate = $crate::bls12381::certificate::threshold::Certificate<V>;
596
597            fn me(&self) -> Option<commonware_utils::Participant> {
598                self.generic.me()
599            }
600
601            fn participants(&self) -> &commonware_utils::ordered::Set<Self::PublicKey> {
602                self.generic.participants()
603            }
604
605            fn sign<D: $crate::Digest>(
606                &self,
607                subject: Self::Subject<'_, D>,
608            ) -> Option<$crate::certificate::Attestation<Self>> {
609                self.generic.sign::<_, D>(subject)
610            }
611
612            fn verify_attestation<R, D>(
613                &self,
614                _rng: &mut R,
615                subject: Self::Subject<'_, D>,
616                attestation: &$crate::certificate::Attestation<Self>,
617                _strategy: &impl commonware_parallel::Strategy,
618            ) -> bool
619            where
620                R: rand_core::CryptoRngCore,
621                D: $crate::Digest,
622            {
623                self.generic
624                    .verify_attestation::<_, D>(subject, attestation)
625            }
626
627            fn verify_attestations<R, D, I>(
628                &self,
629                rng: &mut R,
630                subject: Self::Subject<'_, D>,
631                attestations: I,
632                strategy: &impl commonware_parallel::Strategy,
633            ) -> $crate::certificate::Verification<Self>
634            where
635                R: rand_core::CryptoRngCore,
636                D: $crate::Digest,
637                I: IntoIterator<Item = $crate::certificate::Attestation<Self>>,
638                I::IntoIter: Send
639            {
640                self.generic
641                    .verify_attestations::<_, _, D, _, _>(rng, subject, attestations, strategy)
642            }
643
644            fn assemble<I, M>(
645                &self,
646                attestations: I,
647                strategy: &impl commonware_parallel::Strategy,
648            ) -> Option<Self::Certificate>
649            where
650                I: IntoIterator<Item = $crate::certificate::Attestation<Self>>,
651                I::IntoIter: Send,
652                M: commonware_utils::Faults,
653            {
654                self.generic.assemble::<Self, _, _, M>(attestations, strategy)
655            }
656
657            fn verify_certificate<R, D, M>(
658                &self,
659                rng: &mut R,
660                subject: Self::Subject<'_, D>,
661                certificate: &Self::Certificate,
662                _strategy: &impl commonware_parallel::Strategy,
663            ) -> bool
664            where
665                R: rand_core::CryptoRngCore,
666                D: $crate::Digest,
667                M: commonware_utils::Faults,
668            {
669                self.generic
670                    .verify_certificate::<Self, _, D, M>(rng, subject, certificate)
671            }
672
673            fn verify_certificates<'a, R, D, I, M>(
674                &self,
675                rng: &mut R,
676                certificates: I,
677                strategy: &impl commonware_parallel::Strategy,
678            ) -> bool
679            where
680                R: rand_core::CryptoRngCore,
681                D: $crate::Digest,
682                I: Iterator<Item = (Self::Subject<'a, D>, &'a Self::Certificate)>,
683                M: commonware_utils::Faults,
684            {
685                self.generic
686                    .verify_certificates::<Self, _, D, _, _, M>(rng, certificates, strategy)
687            }
688
689            fn is_attributable() -> bool {
690                $crate::bls12381::certificate::threshold::Generic::<P, V, $namespace>::is_attributable()
691            }
692
693            fn is_batchable() -> bool {
694                $crate::bls12381::certificate::threshold::Generic::<P, V, $namespace>::is_batchable()
695            }
696
697            fn certificate_codec_config(
698                &self,
699            ) -> <Self::Certificate as commonware_codec::Read>::Cfg {
700                self.generic.certificate_codec_config()
701            }
702
703            fn certificate_codec_config_unbounded(
704            ) -> <Self::Certificate as commonware_codec::Read>::Cfg {
705                $crate::bls12381::certificate::threshold::Generic::<P, V, $namespace>::certificate_codec_config_unbounded()
706            }
707        }
708    };
709}
710
711#[cfg(test)]
712mod tests {
713    use super::*;
714    use crate::{
715        bls12381::{
716            dkg,
717            primitives::{
718                ops::threshold::sign_message,
719                variant::{MinPk, MinSig, Variant},
720            },
721        },
722        certificate::Scheme as _,
723        ed25519::{self, PrivateKey as Ed25519PrivateKey},
724        sha256::Digest as Sha256Digest,
725        Signer as _,
726    };
727    use bytes::Bytes;
728    use commonware_codec::{DecodeExt, Encode};
729    use commonware_math::algebra::{Additive, Random};
730    use commonware_parallel::Sequential;
731    use commonware_utils::{ordered::Set, test_rng, Faults, N3f1, TryCollect, NZU32};
732
733    const NAMESPACE: &[u8] = b"test-bls12381-threshold";
734    const MESSAGE: &[u8] = b"test message";
735
736    /// Test context type for generic scheme tests.
737    #[derive(Clone, Debug)]
738    pub struct TestSubject {
739        pub message: Bytes,
740    }
741
742    impl Subject for TestSubject {
743        type Namespace = Vec<u8>;
744
745        fn namespace<'a>(&self, derived: &'a Self::Namespace) -> &'a [u8] {
746            derived
747        }
748
749        fn message(&self) -> Bytes {
750            self.message.clone()
751        }
752    }
753
754    // Use the macro to generate the test scheme
755    impl_certificate_bls12381_threshold!(TestSubject, Vec<u8>);
756
757    #[allow(clippy::type_complexity)]
758    fn setup_signers<V: Variant>(
759        rng: &mut impl CryptoRngCore,
760        n: u32,
761    ) -> (
762        Vec<Scheme<ed25519::PublicKey, V>>,
763        Scheme<ed25519::PublicKey, V>,
764        Sharing<V>,
765    ) {
766        // Generate identity keys (ed25519)
767        let identity_keys: Vec<_> = (0..n)
768            .map(|_| Ed25519PrivateKey::random(&mut *rng))
769            .collect();
770        let participants: Set<ed25519::PublicKey> = identity_keys
771            .iter()
772            .map(|sk| sk.public_key())
773            .try_collect()
774            .unwrap();
775
776        // Generate threshold polynomial and shares using DKG
777        let (polynomial, shares) =
778            dkg::deal_anonymous::<V, N3f1>(&mut *rng, Default::default(), NZU32!(n));
779
780        let signers = shares
781            .into_iter()
782            .map(|share| {
783                Scheme::signer(NAMESPACE, participants.clone(), polynomial.clone(), share).unwrap()
784            })
785            .collect();
786
787        let verifier = Scheme::verifier(NAMESPACE, participants, polynomial.clone());
788
789        (signers, verifier, polynomial)
790    }
791
792    fn test_sign_vote_roundtrip<V: Variant>() {
793        let mut rng = test_rng();
794        let (schemes, _, _) = setup_signers::<V>(&mut rng, 4);
795        let scheme = &schemes[0];
796
797        let attestation = scheme
798            .sign::<Sha256Digest>(TestSubject {
799                message: Bytes::from_static(MESSAGE),
800            })
801            .unwrap();
802        assert!(scheme.verify_attestation::<_, Sha256Digest>(
803            &mut rng,
804            TestSubject {
805                message: Bytes::from_static(MESSAGE),
806            },
807            &attestation,
808            &Sequential,
809        ));
810    }
811
812    #[test]
813    fn test_sign_vote_roundtrip_variants() {
814        test_sign_vote_roundtrip::<MinPk>();
815        test_sign_vote_roundtrip::<MinSig>();
816    }
817
818    fn test_verifier_cannot_sign<V: Variant>() {
819        let mut rng = test_rng();
820        let (_, verifier, _) = setup_signers::<V>(&mut rng, 4);
821        assert!(verifier
822            .sign::<Sha256Digest>(TestSubject {
823                message: Bytes::from_static(MESSAGE),
824            })
825            .is_none());
826    }
827
828    #[test]
829    fn test_verifier_cannot_sign_variants() {
830        test_verifier_cannot_sign::<MinPk>();
831        test_verifier_cannot_sign::<MinSig>();
832    }
833
834    fn test_verify_attestations_filters_invalid<V: Variant>() {
835        let mut rng = test_rng();
836        let (schemes, _, _) = setup_signers::<V>(&mut rng, 5);
837        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
838
839        let attestations: Vec<_> = schemes
840            .iter()
841            .take(quorum)
842            .map(|s| {
843                s.sign::<Sha256Digest>(TestSubject {
844                    message: Bytes::from_static(MESSAGE),
845                })
846                .unwrap()
847            })
848            .collect();
849
850        let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
851            &mut rng,
852            TestSubject {
853                message: Bytes::from_static(MESSAGE),
854            },
855            attestations.clone(),
856            &Sequential,
857        );
858        assert!(result.invalid.is_empty());
859        assert_eq!(result.verified.len(), quorum);
860
861        // Test: Corrupt one attestation - invalid signer index
862        let mut attestations_corrupted = attestations.clone();
863        attestations_corrupted[0].signer = Participant::new(999);
864        let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
865            &mut rng,
866            TestSubject {
867                message: Bytes::from_static(MESSAGE),
868            },
869            attestations_corrupted,
870            &Sequential,
871        );
872        assert_eq!(result.invalid, vec![Participant::new(999)]);
873        assert_eq!(result.verified.len(), quorum - 1);
874
875        // Test: Corrupt one attestation - invalid signature
876        let mut attestations_corrupted = attestations;
877        attestations_corrupted[0].signature = attestations_corrupted[1].signature.clone();
878        let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
879            &mut rng,
880            TestSubject {
881                message: Bytes::from_static(MESSAGE),
882            },
883            attestations_corrupted,
884            &Sequential,
885        );
886        assert_eq!(result.invalid.len(), 1);
887        assert_eq!(result.verified.len(), quorum - 1);
888    }
889
890    #[test]
891    fn test_verify_attestations_filters_invalid_variants() {
892        test_verify_attestations_filters_invalid::<MinPk>();
893        test_verify_attestations_filters_invalid::<MinSig>();
894    }
895
896    fn test_assemble_certificate<V: Variant>() {
897        let mut rng = test_rng();
898        let (schemes, verifier, _) = setup_signers::<V>(&mut rng, 4);
899        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
900
901        let attestations: Vec<_> = schemes
902            .iter()
903            .take(quorum)
904            .map(|s| {
905                s.sign::<Sha256Digest>(TestSubject {
906                    message: Bytes::from_static(MESSAGE),
907                })
908                .unwrap()
909            })
910            .collect();
911
912        let certificate = schemes[0]
913            .assemble::<_, N3f1>(attestations, &Sequential)
914            .unwrap();
915
916        // Verify the assembled certificate
917        assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
918            &mut rng,
919            TestSubject {
920                message: Bytes::from_static(MESSAGE),
921            },
922            &certificate,
923            &Sequential,
924        ));
925    }
926
927    #[test]
928    fn test_assemble_certificate_variants() {
929        test_assemble_certificate::<MinPk>();
930        test_assemble_certificate::<MinSig>();
931    }
932
933    fn test_verify_certificate<V: Variant>() {
934        let mut rng = test_rng();
935        let (schemes, verifier, _) = setup_signers::<V>(&mut rng, 4);
936        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
937
938        let attestations: Vec<_> = schemes
939            .iter()
940            .take(quorum)
941            .map(|s| {
942                s.sign::<Sha256Digest>(TestSubject {
943                    message: Bytes::from_static(MESSAGE),
944                })
945                .unwrap()
946            })
947            .collect();
948
949        let certificate = schemes[0]
950            .assemble::<_, N3f1>(attestations, &Sequential)
951            .unwrap();
952
953        assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
954            &mut rng,
955            TestSubject {
956                message: Bytes::from_static(MESSAGE),
957            },
958            &certificate,
959            &Sequential,
960        ));
961    }
962
963    #[test]
964    fn test_verify_certificate_variants() {
965        test_verify_certificate::<MinPk>();
966        test_verify_certificate::<MinSig>();
967    }
968
969    fn test_verify_certificate_detects_corruption<V: Variant>() {
970        let mut rng = test_rng();
971        let (schemes, verifier, _) = setup_signers::<V>(&mut rng, 4);
972        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
973
974        let attestations: Vec<_> = schemes
975            .iter()
976            .take(quorum)
977            .map(|s| {
978                s.sign::<Sha256Digest>(TestSubject {
979                    message: Bytes::from_static(MESSAGE),
980                })
981                .unwrap()
982            })
983            .collect();
984
985        let certificate = schemes[0]
986            .assemble::<_, N3f1>(attestations, &Sequential)
987            .unwrap();
988
989        // Valid certificate passes
990        assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
991            &mut rng,
992            TestSubject {
993                message: Bytes::from_static(MESSAGE),
994            },
995            &certificate,
996            &Sequential,
997        ));
998
999        // Corrupted certificate fails
1000        let corrupted = Certificate::new(V::Signature::zero());
1001        assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1002            &mut rng,
1003            TestSubject {
1004                message: Bytes::from_static(MESSAGE),
1005            },
1006            &corrupted,
1007            &Sequential,
1008        ));
1009    }
1010
1011    #[test]
1012    fn test_verify_certificate_detects_corruption_variants() {
1013        test_verify_certificate_detects_corruption::<MinPk>();
1014        test_verify_certificate_detects_corruption::<MinSig>();
1015    }
1016
1017    fn test_certificate_codec_roundtrip<V: Variant>() {
1018        let mut rng = test_rng();
1019        let (schemes, _, _) = setup_signers::<V>(&mut rng, 4);
1020        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
1021
1022        let attestations: Vec<_> = schemes
1023            .iter()
1024            .take(quorum)
1025            .map(|s| {
1026                s.sign::<Sha256Digest>(TestSubject {
1027                    message: Bytes::from_static(MESSAGE),
1028                })
1029                .unwrap()
1030            })
1031            .collect();
1032
1033        let certificate = schemes[0]
1034            .assemble::<_, N3f1>(attestations, &Sequential)
1035            .unwrap();
1036        let encoded = certificate.encode();
1037        let decoded = Certificate::<V>::decode(encoded).expect("decode certificate");
1038        assert_eq!(decoded, certificate);
1039    }
1040
1041    #[test]
1042    fn test_certificate_codec_roundtrip_variants() {
1043        test_certificate_codec_roundtrip::<MinPk>();
1044        test_certificate_codec_roundtrip::<MinSig>();
1045    }
1046
1047    fn test_certificate_rejects_sub_quorum<V: Variant>() {
1048        let mut rng = test_rng();
1049        let (schemes, _, _) = setup_signers::<V>(&mut rng, 4);
1050        let sub_quorum = 2; // Less than quorum (3)
1051
1052        let attestations: Vec<_> = schemes
1053            .iter()
1054            .take(sub_quorum)
1055            .map(|s| {
1056                s.sign::<Sha256Digest>(TestSubject {
1057                    message: Bytes::from_static(MESSAGE),
1058                })
1059                .unwrap()
1060            })
1061            .collect();
1062
1063        assert!(schemes[0]
1064            .assemble::<_, N3f1>(attestations, &Sequential)
1065            .is_none());
1066    }
1067
1068    #[test]
1069    fn test_certificate_rejects_sub_quorum_variants() {
1070        test_certificate_rejects_sub_quorum::<MinPk>();
1071        test_certificate_rejects_sub_quorum::<MinSig>();
1072    }
1073
1074    fn test_verify_certificates_batch<V: Variant>() {
1075        let mut rng = test_rng();
1076        let (schemes, verifier, _) = setup_signers::<V>(&mut rng, 4);
1077        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
1078
1079        let messages: [Bytes; 3] = [
1080            Bytes::from_static(b"msg1"),
1081            Bytes::from_static(b"msg2"),
1082            Bytes::from_static(b"msg3"),
1083        ];
1084        let mut certificates = Vec::new();
1085
1086        for msg in &messages {
1087            let attestations: Vec<_> = schemes
1088                .iter()
1089                .take(quorum)
1090                .map(|s| {
1091                    s.sign::<Sha256Digest>(TestSubject {
1092                        message: msg.clone(),
1093                    })
1094                    .unwrap()
1095                })
1096                .collect();
1097            certificates.push(
1098                schemes[0]
1099                    .assemble::<_, N3f1>(attestations, &Sequential)
1100                    .unwrap(),
1101            );
1102        }
1103
1104        let certs_iter = messages.iter().zip(&certificates).map(|(msg, cert)| {
1105            (
1106                TestSubject {
1107                    message: msg.clone(),
1108                },
1109                cert,
1110            )
1111        });
1112
1113        assert!(verifier.verify_certificates::<_, Sha256Digest, _, N3f1>(
1114            &mut rng,
1115            certs_iter,
1116            &Sequential
1117        ));
1118    }
1119
1120    #[test]
1121    fn test_verify_certificates_batch_variants() {
1122        test_verify_certificates_batch::<MinPk>();
1123        test_verify_certificates_batch::<MinSig>();
1124    }
1125
1126    fn test_verify_certificates_batch_detects_failure<V: Variant>() {
1127        let mut rng = test_rng();
1128        let (schemes, verifier, _) = setup_signers::<V>(&mut rng, 4);
1129        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
1130
1131        let messages: [Bytes; 2] = [Bytes::from_static(b"msg1"), Bytes::from_static(b"msg2")];
1132        let mut certificates = Vec::new();
1133
1134        for msg in &messages {
1135            let attestations: Vec<_> = schemes
1136                .iter()
1137                .take(quorum)
1138                .map(|s| {
1139                    s.sign::<Sha256Digest>(TestSubject {
1140                        message: msg.clone(),
1141                    })
1142                    .unwrap()
1143                })
1144                .collect();
1145            certificates.push(
1146                schemes[0]
1147                    .assemble::<_, N3f1>(attestations, &Sequential)
1148                    .unwrap(),
1149            );
1150        }
1151
1152        // Corrupt second certificate
1153        certificates[1] = Certificate::new(V::Signature::zero());
1154
1155        let certs_iter = messages.iter().zip(&certificates).map(|(msg, cert)| {
1156            (
1157                TestSubject {
1158                    message: msg.clone(),
1159                },
1160                cert,
1161            )
1162        });
1163
1164        assert!(!verifier.verify_certificates::<_, Sha256Digest, _, N3f1>(
1165            &mut rng,
1166            certs_iter,
1167            &Sequential
1168        ));
1169    }
1170
1171    #[test]
1172    fn test_verify_certificates_batch_detects_failure_variants() {
1173        test_verify_certificates_batch_detects_failure::<MinPk>();
1174        test_verify_certificates_batch_detects_failure::<MinSig>();
1175    }
1176
1177    fn test_certificate_verifier<V: Variant>() {
1178        let mut rng = test_rng();
1179        let (schemes, _, polynomial) = setup_signers::<V>(&mut rng, 4);
1180        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
1181
1182        let attestations: Vec<_> = schemes
1183            .iter()
1184            .take(quorum)
1185            .map(|s| {
1186                s.sign::<Sha256Digest>(TestSubject {
1187                    message: Bytes::from_static(MESSAGE),
1188                })
1189                .unwrap()
1190            })
1191            .collect();
1192
1193        let certificate = schemes[0]
1194            .assemble::<_, N3f1>(attestations, &Sequential)
1195            .unwrap();
1196
1197        // Create a certificate-only verifier using the identity from the polynomial
1198        let identity = polynomial.public();
1199        let cert_verifier =
1200            Scheme::<ed25519::PublicKey, V>::certificate_verifier(NAMESPACE, *identity);
1201
1202        // Should be able to verify certificates
1203        assert!(cert_verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1204            &mut rng,
1205            TestSubject {
1206                message: Bytes::from_static(MESSAGE),
1207            },
1208            &certificate,
1209            &Sequential,
1210        ));
1211
1212        // Should not be able to sign
1213        assert!(cert_verifier
1214            .sign::<Sha256Digest>(TestSubject {
1215                message: Bytes::from_static(MESSAGE),
1216            })
1217            .is_none());
1218    }
1219
1220    #[test]
1221    fn test_certificate_verifier_variants() {
1222        test_certificate_verifier::<MinPk>();
1223        test_certificate_verifier::<MinSig>();
1224    }
1225
1226    #[test]
1227    fn test_is_not_attributable() {
1228        assert!(!Generic::<ed25519::PublicKey, MinPk, Vec<u8>>::is_attributable());
1229        assert!(!Scheme::<ed25519::PublicKey, MinPk>::is_attributable());
1230        assert!(!Generic::<ed25519::PublicKey, MinSig, Vec<u8>>::is_attributable());
1231        assert!(!Scheme::<ed25519::PublicKey, MinSig>::is_attributable());
1232    }
1233
1234    #[test]
1235    fn test_is_batchable() {
1236        assert!(Generic::<ed25519::PublicKey, MinPk, Vec<u8>>::is_batchable());
1237        assert!(Scheme::<ed25519::PublicKey, MinPk>::is_batchable());
1238        assert!(Generic::<ed25519::PublicKey, MinSig, Vec<u8>>::is_batchable());
1239        assert!(Scheme::<ed25519::PublicKey, MinSig>::is_batchable());
1240    }
1241
1242    fn test_verifier_accepts_votes<V: Variant>() {
1243        let mut rng = test_rng();
1244        let (schemes, verifier, _) = setup_signers::<V>(&mut rng, 4);
1245
1246        let vote = schemes[1]
1247            .sign::<Sha256Digest>(TestSubject {
1248                message: Bytes::from_static(MESSAGE),
1249            })
1250            .unwrap();
1251        assert!(verifier.verify_attestation::<_, Sha256Digest>(
1252            &mut rng,
1253            TestSubject {
1254                message: Bytes::from_static(MESSAGE),
1255            },
1256            &vote,
1257            &Sequential,
1258        ));
1259    }
1260
1261    #[test]
1262    fn test_verifier_accepts_votes_variants() {
1263        test_verifier_accepts_votes::<MinPk>();
1264        test_verifier_accepts_votes::<MinSig>();
1265    }
1266
1267    fn test_scheme_clone_and_verifier<V: Variant>() {
1268        let mut rng = test_rng();
1269        let (schemes, verifier, _) = setup_signers::<V>(&mut rng, 4);
1270
1271        // Clone a signer
1272        let signer = schemes[0].clone();
1273        assert!(
1274            signer
1275                .sign::<Sha256Digest>(TestSubject {
1276                    message: Bytes::from_static(MESSAGE),
1277                })
1278                .is_some(),
1279            "signer should produce votes"
1280        );
1281
1282        // A verifier cannot produce votes
1283        assert!(
1284            verifier
1285                .sign::<Sha256Digest>(TestSubject {
1286                    message: Bytes::from_static(MESSAGE),
1287                })
1288                .is_none(),
1289            "verifier should not produce votes"
1290        );
1291    }
1292
1293    #[test]
1294    fn test_scheme_clone_and_verifier_variants() {
1295        test_scheme_clone_and_verifier::<MinPk>();
1296        test_scheme_clone_and_verifier::<MinSig>();
1297    }
1298
1299    fn certificate_verifier_panics_on_vote<V: Variant>() {
1300        let mut rng = test_rng();
1301        let (schemes, _, _) = setup_signers::<V>(&mut rng, 4);
1302        let certificate_verifier = Scheme::<ed25519::PublicKey, V>::certificate_verifier(
1303            NAMESPACE,
1304            *schemes[0].identity(),
1305        );
1306
1307        let vote = schemes[1]
1308            .sign::<Sha256Digest>(TestSubject {
1309                message: Bytes::from_static(MESSAGE),
1310            })
1311            .unwrap();
1312
1313        // CertificateVerifier should panic when trying to verify a vote
1314        certificate_verifier.verify_attestation::<_, Sha256Digest>(
1315            &mut rng,
1316            TestSubject {
1317                message: Bytes::from_static(MESSAGE),
1318            },
1319            &vote,
1320            &Sequential,
1321        );
1322    }
1323
1324    #[test]
1325    #[should_panic(expected = "can only be called for signer and verifier")]
1326    fn test_certificate_verifier_panics_on_vote_min_pk() {
1327        certificate_verifier_panics_on_vote::<MinPk>();
1328    }
1329
1330    #[test]
1331    #[should_panic(expected = "can only be called for signer and verifier")]
1332    fn test_certificate_verifier_panics_on_vote_min_sig() {
1333        certificate_verifier_panics_on_vote::<MinSig>();
1334    }
1335
1336    fn signer_shares_must_match_participant_indices<V: Variant>() {
1337        let mut rng = test_rng();
1338
1339        // Generate identity keys (ed25519)
1340        let identity_keys: Vec<_> = (0..4)
1341            .map(|_| Ed25519PrivateKey::random(&mut rng))
1342            .collect();
1343        let participants: Set<ed25519::PublicKey> = identity_keys
1344            .iter()
1345            .map(|sk| sk.public_key())
1346            .try_collect()
1347            .unwrap();
1348
1349        let (polynomial, mut shares) =
1350            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(4));
1351        shares[0].index = Participant::new(999);
1352        Scheme::<ed25519::PublicKey, V>::signer(
1353            NAMESPACE,
1354            participants,
1355            polynomial,
1356            shares[0].clone(),
1357        );
1358    }
1359
1360    #[test]
1361    #[should_panic(expected = "share index must match participant indices")]
1362    fn test_signer_shares_must_match_participant_indices_min_pk() {
1363        signer_shares_must_match_participant_indices::<MinPk>();
1364    }
1365
1366    #[test]
1367    #[should_panic(expected = "share index must match participant indices")]
1368    fn test_signer_shares_must_match_participant_indices_min_sig() {
1369        signer_shares_must_match_participant_indices::<MinSig>();
1370    }
1371
1372    fn make_participants<R: rand::RngCore + rand::CryptoRng + Clone>(
1373        rng: &mut R,
1374        n: u32,
1375    ) -> Set<ed25519::PublicKey> {
1376        (0..n)
1377            .map(|_| Ed25519PrivateKey::random(&mut *rng).public_key())
1378            .try_collect()
1379            .expect("participants are unique")
1380    }
1381
1382    fn signer_polynomial_threshold_must_equal_quorum<V: Variant>() {
1383        let mut rng = test_rng();
1384        let participants = make_participants(&mut rng, 5);
1385        // Create a polynomial with threshold 4, but quorum of 5 participants is 4
1386        // so this should succeed. Let's use threshold 2 to make it fail.
1387        // quorum(5) = 4, but polynomial.required() = 2, so this should panic
1388        let (polynomial, shares) =
1389            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(2));
1390        Scheme::<ed25519::PublicKey, V>::signer(
1391            NAMESPACE,
1392            participants,
1393            polynomial,
1394            shares[0].clone(),
1395        );
1396    }
1397
1398    #[test]
1399    #[should_panic(expected = "polynomial total must equal participant len")]
1400    fn test_signer_polynomial_threshold_must_equal_quorum_min_pk() {
1401        signer_polynomial_threshold_must_equal_quorum::<MinPk>();
1402    }
1403
1404    #[test]
1405    #[should_panic(expected = "polynomial total must equal participant len")]
1406    fn test_signer_polynomial_threshold_must_equal_quorum_min_sig() {
1407        signer_polynomial_threshold_must_equal_quorum::<MinSig>();
1408    }
1409
1410    fn verifier_polynomial_threshold_must_equal_quorum<V: Variant>() {
1411        let mut rng = test_rng();
1412        let participants = make_participants(&mut rng, 5);
1413        // Create a polynomial with threshold 2, but quorum of 5 participants is 4
1414        // quorum(5) = 4, but polynomial.required() = 2, so this should panic
1415        let (polynomial, _) =
1416            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(2));
1417        Scheme::<ed25519::PublicKey, V>::verifier(NAMESPACE, participants, polynomial);
1418    }
1419
1420    #[test]
1421    #[should_panic(expected = "polynomial total must equal participant len")]
1422    fn test_verifier_polynomial_threshold_must_equal_quorum_min_pk() {
1423        verifier_polynomial_threshold_must_equal_quorum::<MinPk>();
1424    }
1425
1426    #[test]
1427    #[should_panic(expected = "polynomial total must equal participant len")]
1428    fn test_verifier_polynomial_threshold_must_equal_quorum_min_sig() {
1429        verifier_polynomial_threshold_must_equal_quorum::<MinSig>();
1430    }
1431
1432    fn certificate_decode_rejects_length_mismatch<V: Variant>() {
1433        let mut rng = test_rng();
1434        let (schemes, _, _) = setup_signers::<V>(&mut rng, 4);
1435        let quorum = N3f1::quorum(schemes.len() as u32) as usize;
1436
1437        let attestations: Vec<_> = schemes
1438            .iter()
1439            .take(quorum)
1440            .map(|s| {
1441                s.sign::<Sha256Digest>(TestSubject {
1442                    message: Bytes::from_static(MESSAGE),
1443                })
1444                .unwrap()
1445            })
1446            .collect();
1447
1448        let certificate = schemes[0]
1449            .assemble::<_, N3f1>(attestations, &Sequential)
1450            .unwrap();
1451        let mut encoded = certificate.encode();
1452        encoded.truncate(encoded.len() - 1);
1453        assert!(V::Signature::decode(encoded).is_err());
1454    }
1455
1456    #[test]
1457    fn test_certificate_decode_rejects_length_mismatch_variants() {
1458        certificate_decode_rejects_length_mismatch::<MinPk>();
1459        certificate_decode_rejects_length_mismatch::<MinSig>();
1460    }
1461
1462    fn sign_vote_partial_matches_share<V: Variant>() {
1463        let mut rng = test_rng();
1464        let (schemes, _, _) = setup_signers::<V>(&mut rng, 4);
1465        let scheme = &schemes[0];
1466
1467        let signature = scheme
1468            .sign::<Sha256Digest>(TestSubject {
1469                message: Bytes::from_static(MESSAGE),
1470            })
1471            .unwrap();
1472
1473        // Verify the partial signature matches what we'd get from direct signing
1474        let share = scheme.share().expect("expected signer");
1475
1476        let expected = sign_message::<V>(share, NAMESPACE, MESSAGE);
1477
1478        assert_eq!(signature.signer, share.index);
1479        assert_eq!(signature.signature.get().unwrap(), &expected.value);
1480    }
1481
1482    #[test]
1483    fn test_sign_vote_partial_matches_share_variants() {
1484        sign_vote_partial_matches_share::<MinPk>();
1485        sign_vote_partial_matches_share::<MinSig>();
1486    }
1487
1488    #[cfg(feature = "arbitrary")]
1489    mod conformance {
1490        use super::*;
1491        use commonware_codec::conformance::CodecConformance;
1492
1493        commonware_conformance::conformance_tests! {
1494            CodecConformance<Certificate<MinSig>>,
1495        }
1496    }
1497}