Skip to main content

commonware_cryptography/ed25519/certificate/
mod.rs

1//! Ed25519 signing scheme implementation.
2//!
3//! This module provides both the generic Ed25519 implementation and a macro to generate
4//! protocol-specific wrappers.
5
6#[cfg(feature = "mocks")]
7pub mod mocks;
8
9use super::{PrivateKey, PublicKey, Signature as Ed25519Signature};
10use crate::{
11    BatchEntry, BatchVerifier as _, Signer as _, Verifier as _,
12    certificate::{AssemblyError, Attestation, Namespace, Scheme, Signers, Subject, Verification},
13};
14#[cfg(not(feature = "std"))]
15use alloc::{collections::BTreeSet, vec::Vec};
16use bytes::{BufMut, Bytes};
17use commonware_codec::{
18    Buf, EncodeSize, Error, FixedSize, Read, ReadRangeExt, Write, types::lazy::Lazy,
19};
20use commonware_parallel::Strategy;
21use commonware_utils::{
22    Participant, Widen,
23    iter::NonEmpty,
24    ordered::{Quorum, Set},
25};
26use rand_core::CryptoRng;
27#[cfg(feature = "std")]
28use std::collections::BTreeSet;
29
30/// Generic Ed25519 signing scheme implementation.
31///
32/// This struct contains the core cryptographic operations without protocol-specific
33/// context types. It can be reused across different protocols (simplex, aggregation, etc.)
34/// by wrapping it with protocol-specific trait implementations via the macro.
35#[derive(Clone, Debug)]
36pub struct Generic<N: Namespace> {
37    /// Participants in the committee.
38    pub participants: Set<PublicKey>,
39    /// Key used for generating signatures.
40    pub signer: Option<(Participant, PrivateKey)>,
41    /// Pre-computed namespace(s) for this subject type.
42    pub namespace: N,
43}
44
45impl<N: Namespace> Generic<N> {
46    /// Creates a new generic Ed25519 scheme instance.
47    pub fn signer(
48        namespace: &[u8],
49        participants: Set<PublicKey>,
50        private_key: PrivateKey,
51    ) -> Option<Self> {
52        let signer = participants
53            .index(&private_key.public_key())
54            .map(|index| (index, private_key))?;
55
56        Some(Self {
57            participants,
58            signer: Some(signer),
59            namespace: N::derive(namespace),
60        })
61    }
62
63    /// Builds a verifier that can authenticate signatures without generating them.
64    pub fn verifier(namespace: &[u8], participants: Set<PublicKey>) -> Self {
65        Self {
66            participants,
67            signer: None,
68            namespace: N::derive(namespace),
69        }
70    }
71
72    /// Returns the index this instance signs at, or `None` if it cannot sign.
73    pub fn signer_index(&self) -> Option<Participant> {
74        self.signer.as_ref().map(|(index, _)| *index)
75    }
76
77    /// Signs a subject and returns the signer index and signature.
78    pub fn sign<S>(&self, subject: impl Subject<Namespace = N>) -> Option<Attestation<S>>
79    where
80        S: Scheme<Signature = Ed25519Signature>,
81    {
82        let (index, private_key) = self.signer.as_ref()?;
83
84        let signature = private_key.sign(subject.namespace(&self.namespace), &subject.message());
85
86        Some(Attestation {
87            signer: *index,
88            signature: signature.into(),
89        })
90    }
91
92    /// Verifies a single attestation from a signer.
93    pub fn verify_attestation<S>(
94        &self,
95        subject: impl Subject<Namespace = N>,
96        attestation: &Attestation<S>,
97    ) -> bool
98    where
99        S: Scheme<Signature = Ed25519Signature>,
100    {
101        let Some(public_key) = self.participants.key(attestation.signer) else {
102            return false;
103        };
104        let Some(signature) = attestation.signature.get() else {
105            return false;
106        };
107
108        public_key.verify(
109            subject.namespace(&self.namespace),
110            &subject.message(),
111            signature,
112        )
113    }
114
115    /// Batch-verifies attestations and returns verified attestations and invalid signers.
116    pub fn verify_attestations<S, R, I>(
117        &self,
118        rng: &mut R,
119        subject: impl Subject<Namespace = N>,
120        attestations: I,
121        strategy: &impl Strategy,
122    ) -> Verification<S>
123    where
124        S: Scheme<Signature = Ed25519Signature>,
125        R: CryptoRng,
126        I: IntoIterator<Item = Attestation<S>>,
127    {
128        let namespace = subject.namespace(&self.namespace);
129        let message = subject.message();
130
131        let attestations = attestations.into_iter();
132        let mut invalid = BTreeSet::new();
133        let mut candidates = Vec::with_capacity(attestations.size_hint().0);
134
135        for attestation in attestations {
136            let Some(public_key) = self.participants.key(attestation.signer) else {
137                invalid.insert(attestation.signer);
138                continue;
139            };
140            if attestation.signature.get().is_none() {
141                invalid.insert(attestation.signer);
142                continue;
143            }
144            candidates.push((attestation, public_key));
145        }
146
147        if !candidates.is_empty()
148            && !PublicKey::verify_batch(
149                rng,
150                &candidates,
151                |_, (attestation, public_key)| BatchEntry {
152                    namespace,
153                    message: &message,
154                    public_key,
155                    signature: attestation.signature.get().expect("validated signature"),
156                },
157                strategy,
158            )
159        {
160            // Batch failed: fall back to per-signer verification to isolate faulty attestations.
161            for (attestation, public_key) in &candidates {
162                let signature = attestation.signature.get().expect("validated signature");
163                if !public_key.verify(namespace, &message, signature) {
164                    invalid.insert(attestation.signer);
165                }
166            }
167        }
168
169        let verified = candidates
170            .into_iter()
171            .filter(|(attestation, _)| !invalid.contains(&attestation.signer))
172            .map(|(attestation, _)| attestation)
173            .collect();
174
175        Verification::new(verified, invalid.into_iter().collect())
176    }
177
178    /// Assembles a certificate from a non-empty collection of attestations.
179    pub fn assemble<S, I>(&self, attestations: NonEmpty<I>) -> Result<Certificate, AssemblyError>
180    where
181        S: Scheme<Signature = Ed25519Signature>,
182        I: Iterator<Item = Attestation<S>>,
183    {
184        // Collect the signers and signatures.
185        let mut entries = Vec::new();
186        for Attestation { signer, signature } in attestations {
187            self.participants
188                .key(signer)
189                .ok_or(AssemblyError::UnknownSigner(signer))?;
190            let signature = signature
191                .get()
192                .cloned()
193                .ok_or(AssemblyError::MalformedSignature(signer))?;
194            entries.push((signer, signature));
195        }
196
197        // Sort the signatures by signer index.
198        entries.sort_by_key(|(signer, _)| *signer);
199        let (signer, signatures): (Vec<Participant>, Vec<_>) = entries.into_iter().unzip();
200        let signers = Signers::try_from((&self.participants, signer))?
201            .require(self.participants.quorum::<S::Faults>())?;
202        let signatures = signatures.into_iter().map(Lazy::from).collect();
203
204        Ok(Certificate {
205            signers,
206            signatures,
207        })
208    }
209
210    /// Stages a certificate and returns the subject's namespace and message.
211    ///
212    /// Returns `None` if the certificate structure or a signature encoding is invalid.
213    /// Earlier signatures may already have been staged.
214    fn stage_certificate<'a, S: Scheme>(
215        &'a self,
216        subject: impl Subject<Namespace = N>,
217        certificate: &'a Certificate,
218        mut stage: impl FnMut(&'a PublicKey, &'a Ed25519Signature),
219    ) -> Option<(&'a [u8], Bytes)> {
220        if certificate.signers.len() != self.participants.len()
221            || certificate.signers.count() != certificate.signatures.len()
222            || certificate.signers.count() < Widen::widen(self.participants.quorum::<S::Faults>())
223        {
224            return None;
225        }
226
227        for (signer, signature) in certificate.signers.iter().zip(&certificate.signatures) {
228            stage(self.participants.key(signer)?, signature.get()?);
229        }
230
231        Some((subject.namespace(&self.namespace), subject.message()))
232    }
233
234    /// Verifies a certificate using batch verification.
235    pub fn verify_certificate<S, R>(
236        &self,
237        rng: &mut R,
238        subject: impl Subject<Namespace = N>,
239        certificate: &Certificate,
240        strategy: &impl Strategy,
241    ) -> bool
242    where
243        S: Scheme,
244        R: CryptoRng,
245    {
246        let mut entries = Vec::with_capacity(certificate.signatures.len());
247        let Some((namespace, message)) =
248            self.stage_certificate::<S>(subject, certificate, |public_key, signature| {
249                entries.push((public_key, signature));
250            })
251        else {
252            return false;
253        };
254
255        PublicKey::verify_batch(
256            rng,
257            &entries,
258            |_, (public_key, signature)| BatchEntry {
259                namespace,
260                message: &message,
261                public_key,
262                signature,
263            },
264            strategy,
265        )
266    }
267
268    /// Verifies multiple certificates in a batch.
269    pub fn verify_certificates<'a, S, R, T, I>(
270        &self,
271        rng: &mut R,
272        certificates: NonEmpty<I>,
273        strategy: &impl Strategy,
274    ) -> bool
275    where
276        S: Scheme,
277        T: Subject<Namespace = N>,
278        R: CryptoRng,
279        I: Iterator<Item = (T, &'a Certificate)>,
280    {
281        // Each certificate stages at most one signature per participant.
282        let per_certificate = self.participants.len();
283        let certificates = certificates.into_iter();
284        let mut messages = Vec::with_capacity(certificates.size_hint().0);
285        let mut entries =
286            Vec::with_capacity(certificates.size_hint().0.saturating_mul(per_certificate));
287        for (subject, certificate) in certificates {
288            let index = messages.len();
289            let Some(message) =
290                self.stage_certificate::<S>(subject, certificate, |public_key, signature| {
291                    entries.push((index, public_key, signature));
292                })
293            else {
294                return false;
295            };
296            messages.push(message);
297        }
298
299        PublicKey::verify_batch(
300            rng,
301            &entries,
302            |_, (index, public_key, signature)| BatchEntry {
303                namespace: messages[*index].0,
304                message: &messages[*index].1,
305                public_key,
306                signature,
307            },
308            strategy,
309        )
310    }
311
312    pub const fn is_attributable() -> bool {
313        true
314    }
315
316    pub const fn is_batchable() -> bool {
317        true
318    }
319
320    /// Returns an upper bound on the encoded certificate size for at most `participants`
321    /// participants.
322    pub fn max_certificate_size(participants: u32) -> usize {
323        crate::certificate::max_individual_certificate_size(participants, Ed25519Signature::SIZE)
324    }
325
326    pub const fn certificate_codec_config(&self) -> <Certificate as commonware_codec::Read>::Cfg {
327        self.participants.len()
328    }
329
330    pub const fn certificate_codec_config_unbounded() -> <Certificate as commonware_codec::Read>::Cfg
331    {
332        u32::MAX as usize
333    }
334}
335
336#[derive(Clone, Debug, PartialEq, Eq, Hash)]
337pub struct Certificate {
338    /// Bitmap of participant indices that contributed signatures.
339    pub signers: Signers,
340    /// Ed25519 signatures emitted by the respective participants ordered by signer index.
341    pub signatures: Vec<Lazy<Ed25519Signature>>,
342}
343
344#[cfg(feature = "arbitrary")]
345impl arbitrary::Arbitrary<'_> for Certificate {
346    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
347        let signers = Signers::arbitrary(u)?;
348        let signatures = (0..signers.count())
349            .map(|_| u.arbitrary::<Ed25519Signature>().map(Lazy::from))
350            .collect::<arbitrary::Result<Vec<_>>>()?;
351        Ok(Self {
352            signers,
353            signatures,
354        })
355    }
356}
357
358impl Write for Certificate {
359    fn write(&self, writer: &mut impl BufMut) {
360        self.signers.write(writer);
361        self.signatures.write(writer);
362    }
363}
364
365impl EncodeSize for Certificate {
366    fn encode_size(&self) -> usize {
367        self.signers.encode_size() + self.signatures.encode_size()
368    }
369}
370
371impl Read for Certificate {
372    type Cfg = usize;
373
374    fn read_cfg(reader: &mut impl Buf, participants: &usize) -> Result<Self, Error> {
375        let signers = Signers::read_cfg(reader, participants)?;
376        if signers.count() == 0 {
377            return Err(Error::Invalid(
378                "cryptography::ed25519::certificate::Certificate",
379                "Certificate contains no signers",
380            ));
381        }
382
383        let signatures = Vec::<Lazy<Ed25519Signature>>::read_range(reader, ..=*participants)?;
384        if signers.count() != signatures.len() {
385            return Err(Error::Invalid(
386                "cryptography::ed25519::certificate::Certificate",
387                "Signers and signatures counts differ",
388            ));
389        }
390
391        Ok(Self {
392            signers,
393            signatures,
394        })
395    }
396}
397
398/// Generates an Ed25519 signing scheme wrapper for a specific protocol.
399///
400/// This macro creates a complete wrapper struct with constructors, `Scheme` trait
401/// implementation, and a `fixture` function for testing.
402///
403/// # Parameters
404///
405/// - `$subject`: The subject type used as `Scheme::Subject<'a, D>`. Use `'a` and `D`
406///   in the subject type to bind to the GAT lifetime and digest type parameters.
407///
408/// - `$namespace`: The namespace type that implements [`Namespace`].
409///   This type pre-computes and stores any protocol-specific namespace bytes derived from
410///   a base namespace. The scheme calls `$namespace::derive(base)` at construction time
411///   to create the namespace, then passes it to `Subject::namespace()` during signing
412///   and verification. For simple protocols with only a base namespace, `Vec<u8>` can be used directly.
413///   For protocols with multiple message types, a custom struct can pre-compute all variants.
414///
415/// - `$faults`: The [`Faults`](commonware_utils::Faults) implementation used to compute certificate
416///   quorums.
417///
418/// # Example
419/// ```ignore
420/// // For non-generic subject types with a single namespace:
421/// impl_certificate_ed25519!(MySubject, Vec<u8>, commonware_utils::N3f1);
422///
423/// // For protocols with generic subject types:
424/// impl_certificate_ed25519!(Subject<'a, D>, Namespace, commonware_utils::N3f1);
425/// ```
426#[macro_export]
427macro_rules! impl_certificate_ed25519 {
428    ($subject:ty, $namespace:ty, $faults:ty) => {
429        /// Generates a test fixture with Ed25519 identities and signing schemes.
430        ///
431        /// Returns a [`commonware_cryptography::certificate::mocks::Fixture`] whose keys and
432        /// scheme instances share a consistent ordering.
433        #[cfg(feature = "mocks")]
434        #[allow(dead_code)]
435        pub fn fixture<R>(
436            rng: &mut R,
437            namespace: &[u8],
438            n: u32,
439        ) -> $crate::certificate::mocks::Fixture<Scheme>
440        where
441            R: rand_core::CryptoRng,
442        {
443            $crate::ed25519::certificate::mocks::fixture(
444                rng,
445                namespace,
446                n,
447                Scheme::signer,
448                Scheme::verifier,
449            )
450        }
451
452        /// Ed25519 signing scheme wrapper.
453        #[derive(Clone, Debug)]
454        pub struct Scheme {
455            /// Verification state, plus the signing material of a signer.
456            generic: $crate::ed25519::certificate::Generic<$namespace>,
457            /// Index of the local identity key for an instance built with [`Self::member`].
458            member: Option<commonware_utils::Participant>,
459        }
460
461        impl Scheme {
462            /// Creates a new scheme instance with the provided key material.
463            ///
464            /// Participants use the same key for both identity and signing.
465            ///
466            /// If the provided private key does not match any signing key in the participant set,
467            /// the instance will act as a verifier (unable to generate signatures).
468            ///
469            /// Returns `None` if the provided private key does not match any participant
470            /// in the participant set.
471            pub fn signer(
472                namespace: &[u8],
473                participants: commonware_utils::ordered::Set<$crate::ed25519::PublicKey>,
474                private_key: $crate::ed25519::PrivateKey,
475            ) -> Option<Self> {
476                Some(Self {
477                    generic: $crate::ed25519::certificate::Generic::signer(
478                        namespace,
479                        participants,
480                        private_key,
481                    )?,
482                    member: None,
483                })
484            }
485
486            /// Builds a verifier that can authenticate signatures without generating them.
487            ///
488            /// Participants use the same key for both identity and signing.
489            ///
490            /// A participant without signing material should use [`Self::member`] instead, so the
491            /// scheme reports its membership.
492            pub fn verifier(
493                namespace: &[u8],
494                participants: commonware_utils::ordered::Set<$crate::ed25519::PublicKey>,
495            ) -> Self {
496                Self {
497                    generic: $crate::ed25519::certificate::Generic::verifier(
498                        namespace,
499                        participants,
500                    ),
501                    member: None,
502                }
503            }
504
505            /// Builds a committee member that verifies like [`Self::verifier`] but also knows
506            /// its own place in the participant set.
507            ///
508            /// The instance cannot sign, and [`Scheme::me`] returns [`Membership::Member`] at
509            /// the position of `public_key` in the participant set. Returns `None` if
510            /// `public_key` is not a participant.
511            ///
512            /// [`Scheme::me`]: commonware_cryptography::certificate::Scheme::me
513            /// [`Membership::Member`]: commonware_cryptography::certificate::Membership::Member
514            pub fn member(
515                namespace: &[u8],
516                participants: commonware_utils::ordered::Set<$crate::ed25519::PublicKey>,
517                public_key: &$crate::ed25519::PublicKey,
518            ) -> Option<Self> {
519                let member = commonware_utils::ordered::Quorum::index(&participants, public_key)?;
520                Some(Self {
521                    generic: $crate::ed25519::certificate::Generic::verifier(
522                        namespace,
523                        participants,
524                    ),
525                    member: Some(member),
526                })
527            }
528        }
529
530        impl $crate::certificate::Verifier for Scheme {
531            type Subject<'a, D: $crate::Digest> = $subject;
532            type Faults = $faults;
533            type PublicKey = $crate::ed25519::PublicKey;
534            type Certificate = $crate::ed25519::certificate::Certificate;
535
536            fn verify_certificate<R, D>(
537                &self,
538                rng: &mut R,
539                subject: Self::Subject<'_, D>,
540                certificate: &Self::Certificate,
541                strategy: &impl commonware_parallel::Strategy,
542            ) -> bool
543            where
544                R: rand_core::CryptoRng,
545                D: $crate::Digest,
546            {
547                self.generic
548                    .verify_certificate::<Self, _>(rng, subject, certificate, strategy)
549            }
550
551            fn verify_certificates<'a, R, D, I>(
552                &self,
553                rng: &mut R,
554                certificates: commonware_utils::iter::NonEmpty<I>,
555                strategy: &impl commonware_parallel::Strategy,
556            ) -> bool
557            where
558                R: rand_core::CryptoRng,
559                D: $crate::Digest,
560                I: Iterator<Item = (Self::Subject<'a, D>, &'a Self::Certificate)>,
561            {
562                self.generic
563                    .verify_certificates::<Self, _, _, _>(rng, certificates, strategy)
564            }
565
566            fn is_batchable() -> bool {
567                $crate::ed25519::certificate::Generic::<$namespace>::is_batchable()
568            }
569
570            fn max_certificate_size(participants: u32) -> usize {
571                $crate::ed25519::certificate::Generic::<$namespace>::max_certificate_size(participants)
572            }
573
574            fn certificate_codec_config(
575                &self,
576            ) -> <Self::Certificate as commonware_codec::Read>::Cfg {
577                self.generic.certificate_codec_config()
578            }
579
580            fn certificate_codec_config_unbounded(
581            ) -> <Self::Certificate as commonware_codec::Read>::Cfg {
582                $crate::ed25519::certificate::Generic::<$namespace>::certificate_codec_config_unbounded()
583            }
584        }
585
586        impl $crate::certificate::Scheme for Scheme {
587            type Signature = $crate::ed25519::Signature;
588
589            fn me(&self) -> Option<$crate::certificate::Membership> {
590                self.generic
591                    .signer_index()
592                    .map($crate::certificate::Membership::Signer)
593                    .or(self.member.map($crate::certificate::Membership::Member))
594            }
595
596            fn participants(&self) -> &commonware_utils::ordered::Set<Self::PublicKey> {
597                &self.generic.participants
598            }
599
600            fn sign<D: $crate::Digest>(
601                &self,
602                subject: Self::Subject<'_, D>,
603            ) -> Option<$crate::certificate::Attestation<Self>> {
604                self.generic.sign(subject)
605            }
606
607            fn verify_attestation<R, D>(
608                &self,
609                _rng: &mut R,
610                subject: Self::Subject<'_, D>,
611                attestation: &$crate::certificate::Attestation<Self>,
612                _strategy: &impl commonware_parallel::Strategy,
613            ) -> bool
614            where
615                R: rand_core::CryptoRng,
616                D: $crate::Digest,
617            {
618                self.generic
619                    .verify_attestation(subject, attestation)
620            }
621
622            fn verify_attestations<R, D, I>(
623                &self,
624                rng: &mut R,
625                subject: Self::Subject<'_, D>,
626                attestations: I,
627                strategy: &impl commonware_parallel::Strategy,
628            ) -> $crate::certificate::Verification<Self>
629            where
630                R: rand_core::CryptoRng,
631                D: $crate::Digest,
632                I: IntoIterator<Item = $crate::certificate::Attestation<Self>>,
633            {
634                self.generic
635                    .verify_attestations(rng, subject, attestations, strategy)
636            }
637
638            fn optimistic_assemble<'a, R, D, I, J>(
639                &self,
640                rng: &mut R,
641                subject: Self::Subject<'_, D>,
642                pending: I,
643                verified: J,
644                strategy: &impl commonware_parallel::Strategy,
645            ) -> Result<Self::Certificate, $crate::certificate::Verification<Self>>
646            where
647                R: rand_core::CryptoRng,
648                D: $crate::Digest,
649                I: IntoIterator<Item = $crate::certificate::Attestation<Self>>,
650                I::IntoIter: ExactSizeIterator + Send,
651                J: IntoIterator<Item = &'a $crate::certificate::Attestation<Self>>,
652                J::IntoIter: Send,
653            {
654                $crate::certificate::verify_then_assemble::<Self, _, D, _, _>(
655                    self, rng, subject, pending, verified, strategy,
656                )
657            }
658
659            fn assemble<I>(
660                &self,
661                attestations: commonware_utils::iter::NonEmpty<I>,
662                _strategy: &impl commonware_parallel::Strategy,
663            ) -> Result<Self::Certificate, $crate::certificate::AssemblyError>
664            where
665                I: Iterator<Item = $crate::certificate::Attestation<Self>> + Send,
666            {
667                self.generic.assemble::<Self, _>(attestations)
668            }
669
670            fn is_attributable() -> bool {
671                $crate::ed25519::certificate::Generic::<$namespace>::is_attributable()
672            }
673        }
674    };
675}
676
677#[cfg(test)]
678mod tests {
679    use super::*;
680    use crate::{
681        certificate::{Membership, Scheme as _, Verifier as _},
682        sha256::Digest as Sha256Digest,
683    };
684    use bytes::Bytes;
685    use commonware_codec::{Decode, Encode, varint::MAX_U32_VARINT_SIZE};
686    use commonware_math::algebra::Random;
687    use commonware_parallel::{Rayon, Sequential};
688    use commonware_utils::{
689        Faults, N3f1, NZUsize, Participant, TestRng, TryCollect, non_empty, ordered::Set, test_rng,
690    };
691    use rand_core::Rng;
692
693    const NAMESPACE: &[u8] = b"test-ed25519";
694    const MESSAGE: &[u8] = b"test message";
695
696    /// Test context type for generic scheme tests.
697    #[derive(Clone, Debug)]
698    pub struct TestSubject {
699        pub message: Bytes,
700    }
701
702    impl Subject for TestSubject {
703        type Namespace = Vec<u8>;
704
705        fn namespace<'a>(&self, derived: &'a Self::Namespace) -> &'a [u8] {
706            derived.as_ref()
707        }
708
709        fn message(&self) -> Bytes {
710            self.message.clone()
711        }
712    }
713
714    // Use the macro to generate the test scheme
715    impl_certificate_ed25519!(TestSubject, Vec<u8>, N3f1);
716
717    fn setup_signers(rng: &mut impl CryptoRng, n: u32) -> (Vec<Scheme>, Scheme) {
718        let private_keys: Vec<_> = (0..n).map(|_| PrivateKey::random(&mut *rng)).collect();
719        let participants: Set<PublicKey> = private_keys
720            .iter()
721            .map(|sk| sk.public_key())
722            .try_collect()
723            .unwrap();
724
725        let signers = private_keys
726            .into_iter()
727            .map(|sk| Scheme::signer(NAMESPACE, participants.clone(), sk).unwrap())
728            .collect();
729
730        let verifier = Scheme::verifier(NAMESPACE, participants);
731
732        (signers, verifier)
733    }
734
735    #[test]
736    fn test_is_attributable() {
737        assert!(Generic::<Vec<u8>>::is_attributable());
738        assert!(Scheme::is_attributable());
739    }
740
741    #[test]
742    fn test_is_batchable() {
743        assert!(Generic::<Vec<u8>>::is_batchable());
744        assert!(Scheme::is_batchable());
745    }
746
747    #[test]
748    fn test_optimistic_assemble_reuses_verified_attestations() {
749        let mut rng = test_rng();
750        let (schemes, verifier) = setup_signers(&mut rng, 5);
751        let quorum = N3f1::quorum(schemes.len()) as usize;
752        let subject = TestSubject {
753            message: Bytes::from_static(MESSAGE),
754        };
755        let attestations: Vec<_> = schemes
756            .iter()
757            .take(quorum)
758            .map(|scheme| scheme.sign::<Sha256Digest>(subject.clone()).unwrap())
759            .collect();
760
761        // One seed per signature makes RNG consumption expose the size of the
762        // verification batch, even when the single worker executes it serially.
763        let strategy = Rayon::new(NZUsize!(1))
764            .unwrap()
765            .with_parallelism(NZUsize!(quorum));
766        for pending_len in [quorum, 1, 0] {
767            let (prior, pending) = attestations.split_at(quorum - pending_len);
768            let verified = verifier.verify_attestations::<_, Sha256Digest, _>(
769                &mut rng,
770                subject.clone(),
771                prior.iter().cloned(),
772                &Sequential,
773            );
774            assert!(verified.invalid.is_empty());
775
776            // Construction must do only the verification needed for fresh votes,
777            // including when every vote was already authenticated.
778            let mut expected_rng = TestRng::new(0);
779            let expected = verifier.verify_attestations::<_, Sha256Digest, _>(
780                &mut expected_rng,
781                subject.clone(),
782                pending.iter().cloned(),
783                &strategy,
784            );
785            assert!(expected.invalid.is_empty());
786            let mut actual_rng = TestRng::new(0);
787            let certificate = verifier
788                .optimistic_assemble::<_, Sha256Digest, _, _>(
789                    &mut actual_rng,
790                    subject.clone(),
791                    pending.iter().cloned(),
792                    &verified.verified,
793                    &strategy,
794                )
795                .ok()
796                .expect("verified quorum must certify");
797            assert_eq!(
798                actual_rng.next_u64(),
799                expected_rng.next_u64(),
800                "retained votes must not enter the verification batch"
801            );
802            assert!(verifier.verify_certificate::<_, Sha256Digest>(
803                &mut rng,
804                subject.clone(),
805                &certificate,
806                &Sequential,
807            ));
808        }
809    }
810
811    #[test]
812    fn test_sign_vote_roundtrip() {
813        let mut rng = test_rng();
814        let (schemes, _) = setup_signers(&mut rng, 4);
815        let scheme = &schemes[0];
816
817        let attestation = scheme
818            .sign::<Sha256Digest>(TestSubject {
819                message: Bytes::from_static(MESSAGE),
820            })
821            .unwrap();
822        assert!(scheme.verify_attestation::<_, Sha256Digest>(
823            &mut rng,
824            TestSubject {
825                message: Bytes::from_static(MESSAGE),
826            },
827            &attestation,
828            &Sequential,
829        ));
830    }
831
832    #[test]
833    fn test_verifier_cannot_sign() {
834        let mut rng = test_rng();
835        let (_, verifier) = setup_signers(&mut rng, 4);
836        assert!(
837            verifier
838                .sign::<Sha256Digest>(TestSubject {
839                    message: Bytes::from_static(MESSAGE)
840                })
841                .is_none()
842        );
843    }
844
845    /// A member reports its membership, cannot sign, and verifies a quorum certificate, while a
846    /// key outside the committee yields no member.
847    #[test]
848    fn test_member_reports_membership_without_signing() {
849        let mut rng = test_rng();
850        let (schemes, verifier) = setup_signers(&mut rng, 4);
851        let participants = verifier.participants().clone();
852        let subject = TestSubject {
853            message: Bytes::from_static(MESSAGE),
854        };
855
856        // A key outside the committee yields no member.
857        let outsider = PrivateKey::random(&mut rng).public_key();
858        assert!(Scheme::member(NAMESPACE, participants.clone(), &outsider).is_none());
859
860        // A member reports its membership but cannot sign. Verifiers report no membership, and
861        // each signer reports the index it signs at.
862        let index = Participant::new(2);
863        let key = participants.key(index).unwrap().clone();
864        let member = Scheme::member(NAMESPACE, participants, &key).unwrap();
865        assert_eq!(member.me(), Some(Membership::Member(index)));
866        assert_eq!(member.me().unwrap().signer(), None);
867        assert!(member.sign::<Sha256Digest>(subject.clone()).is_none());
868        assert_eq!(verifier.me(), None);
869        for scheme in &schemes {
870            let Some(Membership::Signer(signer)) = scheme.me() else {
871                panic!("signer must report signing membership");
872            };
873            let attestation = scheme.sign::<Sha256Digest>(subject.clone()).unwrap();
874            assert_eq!(attestation.signer, signer);
875        }
876
877        // A member verifies a certificate assembled by a quorum of signers.
878        let attestations: Vec<_> = schemes
879            .iter()
880            .take(Widen::widen(N3f1::quorum(schemes.len())))
881            .map(|s| s.sign::<Sha256Digest>(subject.clone()).unwrap())
882            .collect();
883        let certificate = schemes[0]
884            .assemble(non_empty![@attestations], &Sequential)
885            .unwrap();
886        assert!(member.verify_certificate::<_, Sha256Digest>(
887            &mut rng,
888            subject,
889            &certificate,
890            &Sequential,
891        ));
892    }
893
894    #[test]
895    fn test_verify_attestations_filters_invalid() {
896        let mut rng = test_rng();
897        let (schemes, _) = setup_signers(&mut rng, 5);
898        let quorum = N3f1::quorum(schemes.len()) as usize;
899
900        let attestations: Vec<_> = schemes
901            .iter()
902            .take(quorum)
903            .map(|s| {
904                s.sign::<Sha256Digest>(TestSubject {
905                    message: Bytes::from_static(MESSAGE),
906                })
907                .unwrap()
908            })
909            .collect();
910
911        let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
912            &mut rng,
913            TestSubject {
914                message: Bytes::from_static(MESSAGE),
915            },
916            attestations.clone(),
917            &Sequential,
918        );
919        assert!(result.invalid.is_empty());
920        assert_eq!(result.verified.len(), quorum);
921
922        // An unknown signer index must invalidate only that attestation.
923        let mut attestations_corrupted = attestations.clone();
924        attestations_corrupted[0].signer = Participant::new(999);
925        let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
926            &mut rng,
927            TestSubject {
928                message: Bytes::from_static(MESSAGE),
929            },
930            attestations_corrupted,
931            &Sequential,
932        );
933        assert_eq!(result.invalid, vec![Participant::new(999)]);
934        assert_eq!(result.verified.len(), quorum - 1);
935
936        // Copying another signer's signature must invalidate exactly one attestation.
937        let mut attestations_corrupted = attestations;
938        attestations_corrupted[0].signature = attestations_corrupted[1].signature.clone();
939        let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
940            &mut rng,
941            TestSubject {
942                message: Bytes::from_static(MESSAGE),
943            },
944            attestations_corrupted,
945            &Sequential,
946        );
947        assert_eq!(result.invalid.len(), 1);
948        assert_eq!(result.verified.len(), quorum - 1);
949
950        // Malformed signature encodings and unknown signers cannot reach batch verification.
951        let subject = TestSubject {
952            message: Bytes::from_static(MESSAGE),
953        };
954        let mut malformed = schemes[0].sign::<Sha256Digest>(subject.clone()).unwrap();
955        let mut truncated = Bytes::from_static(&[0u8; 3]);
956        malformed.signature = Lazy::deferred(&mut truncated, ());
957        let mut unknown = schemes[1].sign::<Sha256Digest>(subject.clone()).unwrap();
958        unknown.signer = Participant::new(999);
959
960        // If no attestations reach batch verification, the caller's RNG state must remain unchanged.
961        for attestations in [Vec::new(), vec![malformed, unknown]] {
962            let expected_invalid = attestations.len();
963            let mut actual_rng = TestRng::new(0);
964            let mut expected_rng = TestRng::new(0);
965            let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
966                &mut actual_rng,
967                subject.clone(),
968                attestations,
969                &Sequential,
970            );
971            assert!(result.verified.is_empty());
972            assert_eq!(result.invalid.len(), expected_invalid);
973            assert_eq!(actual_rng.next_u64(), expected_rng.next_u64());
974        }
975    }
976
977    #[test]
978    fn test_assemble_certificate() {
979        let mut rng = test_rng();
980        let (schemes, _) = setup_signers(&mut rng, 4);
981        let quorum = N3f1::quorum(schemes.len()) 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(non_empty![@attestations], &Sequential)
996            .unwrap();
997
998        // Verify certificate has correct number of signers
999        assert_eq!(certificate.signers.count(), quorum);
1000        assert_eq!(certificate.signatures.len(), quorum);
1001    }
1002
1003    #[test]
1004    fn test_assemble_certificate_sorts_signers() {
1005        let mut rng = test_rng();
1006        let (schemes, _) = setup_signers(&mut rng, 4);
1007
1008        // Get indices and sort them to create attestations in guaranteed reverse order
1009        let mut indexed: Vec<_> = (0..3)
1010            .map(|i| (schemes[i].me().unwrap().index(), i))
1011            .collect();
1012        indexed.sort_by_key(|(idx, _)| *idx);
1013
1014        // Create attestations in reverse sorted order (guaranteed non-sorted)
1015        let attestations = vec![
1016            schemes[indexed[2].1]
1017                .sign::<Sha256Digest>(TestSubject {
1018                    message: Bytes::from_static(MESSAGE),
1019                })
1020                .unwrap(),
1021            schemes[indexed[1].1]
1022                .sign::<Sha256Digest>(TestSubject {
1023                    message: Bytes::from_static(MESSAGE),
1024                })
1025                .unwrap(),
1026            schemes[indexed[0].1]
1027                .sign::<Sha256Digest>(TestSubject {
1028                    message: Bytes::from_static(MESSAGE),
1029                })
1030                .unwrap(),
1031        ];
1032
1033        let certificate = schemes[0]
1034            .assemble(non_empty![@attestations], &Sequential)
1035            .unwrap();
1036
1037        // Verify signers are sorted by signer index
1038        let expected: Vec<_> = indexed.iter().map(|(idx, _)| *idx).collect();
1039        assert_eq!(certificate.signers.iter().collect::<Vec<_>>(), expected);
1040    }
1041
1042    #[test]
1043    fn test_verify_certificate() {
1044        let mut rng = test_rng();
1045        let (schemes, verifier) = setup_signers(&mut rng, 4);
1046        let quorum = N3f1::quorum(schemes.len()) as usize;
1047
1048        let attestations: Vec<_> = schemes
1049            .iter()
1050            .take(quorum)
1051            .map(|s| {
1052                s.sign::<Sha256Digest>(TestSubject {
1053                    message: Bytes::from_static(MESSAGE),
1054                })
1055                .unwrap()
1056            })
1057            .collect();
1058
1059        let certificate = schemes[0]
1060            .assemble(non_empty![@attestations], &Sequential)
1061            .unwrap();
1062
1063        assert!(verifier.verify_certificate::<_, Sha256Digest>(
1064            &mut rng,
1065            TestSubject {
1066                message: Bytes::from_static(MESSAGE)
1067            },
1068            &certificate,
1069            &Sequential,
1070        ));
1071    }
1072
1073    #[test]
1074    fn test_verify_certificate_detects_corruption() {
1075        let mut rng = test_rng();
1076        let (schemes, verifier) = setup_signers(&mut rng, 4);
1077        let quorum = N3f1::quorum(schemes.len()) as usize;
1078
1079        let attestations: Vec<_> = schemes
1080            .iter()
1081            .take(quorum)
1082            .map(|s| {
1083                s.sign::<Sha256Digest>(TestSubject {
1084                    message: Bytes::from_static(MESSAGE),
1085                })
1086                .unwrap()
1087            })
1088            .collect();
1089
1090        let certificate = schemes[0]
1091            .assemble(non_empty![@attestations], &Sequential)
1092            .unwrap();
1093
1094        // Valid certificate passes
1095        assert!(verifier.verify_certificate::<_, Sha256Digest>(
1096            &mut rng,
1097            TestSubject {
1098                message: Bytes::from_static(MESSAGE),
1099            },
1100            &certificate,
1101            &Sequential,
1102        ));
1103
1104        // Corrupted certificate fails
1105        let mut corrupted = certificate;
1106        corrupted.signatures[0] = corrupted.signatures[1].clone();
1107        assert!(!verifier.verify_certificate::<_, Sha256Digest>(
1108            &mut rng,
1109            TestSubject {
1110                message: Bytes::from_static(MESSAGE),
1111            },
1112            &corrupted,
1113            &Sequential,
1114        ));
1115    }
1116
1117    #[test]
1118    fn test_certificate_codec_roundtrip() {
1119        let mut rng = test_rng();
1120        let (schemes, _) = setup_signers(&mut rng, 4);
1121        let quorum = N3f1::quorum(schemes.len()) as usize;
1122
1123        let attestations: Vec<_> = schemes
1124            .iter()
1125            .take(quorum)
1126            .map(|s| {
1127                s.sign::<Sha256Digest>(TestSubject {
1128                    message: Bytes::from_static(MESSAGE),
1129                })
1130                .unwrap()
1131            })
1132            .collect();
1133
1134        let certificate = schemes[0]
1135            .assemble(non_empty![@attestations], &Sequential)
1136            .unwrap();
1137        let encoded = certificate.encode();
1138        assert!(
1139            encoded.len() <= Scheme::max_certificate_size(u32::try_from(schemes.len()).unwrap())
1140        );
1141        let decoded = Certificate::decode_cfg(encoded, &schemes.len()).expect("decode certificate");
1142        assert_eq!(decoded, certificate);
1143    }
1144
1145    #[test]
1146    fn test_max_certificate_size() {
1147        // The bound reserves the signature-list prefix at its type's maximum width.
1148        let mut rng = test_rng();
1149        let (schemes, _) = setup_signers(&mut rng, 128);
1150        let attestations = schemes.iter().map(|s| {
1151            s.sign::<Sha256Digest>(TestSubject {
1152                message: Bytes::from_static(MESSAGE),
1153            })
1154            .unwrap()
1155        });
1156        let certificate = schemes[0]
1157            .assemble(non_empty![@attestations], &Sequential)
1158            .unwrap();
1159        let prefix_slack = MAX_U32_VARINT_SIZE - schemes.len().encode_size();
1160        assert_eq!(
1161            certificate.encode().len() + prefix_slack,
1162            Scheme::max_certificate_size(u32::try_from(schemes.len()).unwrap())
1163        );
1164    }
1165
1166    #[test]
1167    fn test_certificate_rejects_sub_quorum() {
1168        let mut rng = test_rng();
1169        let (schemes, _) = setup_signers(&mut rng, 4);
1170        let expected = N3f1::quorum(schemes.len());
1171        let found = expected - 1;
1172        let found_count = usize::try_from(found).expect("quorum exceeds usize::MAX");
1173
1174        let attestations: Vec<_> = schemes
1175            .iter()
1176            .take(found_count)
1177            .map(|s| {
1178                s.sign::<Sha256Digest>(TestSubject {
1179                    message: Bytes::from_static(MESSAGE),
1180                })
1181                .unwrap()
1182            })
1183            .collect();
1184
1185        assert_eq!(
1186            schemes[0].assemble(non_empty![@attestations], &Sequential),
1187            Err(AssemblyError::InsufficientAttestations(expected, found))
1188        );
1189    }
1190
1191    #[test]
1192    fn test_certificate_rejects_invalid_signer() {
1193        let mut rng = test_rng();
1194        let (schemes, _) = setup_signers(&mut rng, 4);
1195        let quorum =
1196            usize::try_from(N3f1::quorum(schemes.len())).expect("quorum exceeds usize::MAX");
1197
1198        let mut attestations: Vec<_> = schemes
1199            .iter()
1200            .take(quorum)
1201            .map(|s| {
1202                s.sign::<Sha256Digest>(TestSubject {
1203                    message: Bytes::from_static(MESSAGE),
1204                })
1205                .unwrap()
1206            })
1207            .collect();
1208
1209        // Corrupt signer index to be out of range
1210        let unknown = Participant::new(999);
1211        attestations[0].signer = unknown;
1212
1213        assert_eq!(
1214            schemes[0].assemble(non_empty![@attestations], &Sequential),
1215            Err(AssemblyError::UnknownSigner(unknown))
1216        );
1217    }
1218
1219    #[test]
1220    fn test_certificate_rejects_malformed_signature() {
1221        // Build a valid quorum certificate to isolate malformed-signature rejection.
1222        let mut rng = test_rng();
1223        let (schemes, verifier) = setup_signers(&mut rng, 4);
1224        let quorum =
1225            usize::try_from(N3f1::quorum(schemes.len())).expect("quorum exceeds usize::MAX");
1226
1227        let mut attestations: Vec<_> = schemes
1228            .iter()
1229            .take(quorum)
1230            .map(|s| {
1231                s.sign::<Sha256Digest>(TestSubject {
1232                    message: Bytes::from_static(MESSAGE),
1233                })
1234                .unwrap()
1235            })
1236            .collect();
1237
1238        let valid = schemes[0]
1239            .assemble(non_empty![@attestations.clone()], &Sequential)
1240            .unwrap();
1241
1242        // Assembly must identify the signer whose attestation cannot be decoded.
1243        let signer = attestations[0].signer;
1244        let mut truncated = Bytes::from_static(&[0u8; 3]);
1245        attestations[0].signature = Lazy::deferred(&mut truncated, ());
1246
1247        assert_eq!(
1248            schemes[0].assemble(non_empty![@attestations], &Sequential),
1249            Err(AssemblyError::MalformedSignature(signer))
1250        );
1251
1252        // Reject a malformed certificate before drawing any verification randomness.
1253        let subject = TestSubject {
1254            message: Bytes::from_static(MESSAGE),
1255        };
1256        let mut invalid = valid.clone();
1257        let mut truncated = Bytes::from_static(&[0u8; 3]);
1258        *invalid.signatures.last_mut().unwrap() = Lazy::deferred(&mut truncated, ());
1259        let mut actual_rng = TestRng::new(0);
1260        let mut expected_rng = TestRng::new(0);
1261        assert!(!verifier.verify_certificate::<_, Sha256Digest>(
1262            &mut actual_rng,
1263            subject.clone(),
1264            &invalid,
1265            &Sequential,
1266        ));
1267        assert_eq!(actual_rng.next_u64(), expected_rng.next_u64());
1268
1269        // A later malformed certificate must reject the combined batch before drawing randomness.
1270        let mut actual_rng = TestRng::new(0);
1271        let mut expected_rng = TestRng::new(0);
1272        assert!(!verifier.verify_certificates::<_, Sha256Digest, _>(
1273            &mut actual_rng,
1274            non_empty![(subject.clone(), &valid), (subject, &invalid)],
1275            &Sequential,
1276        ));
1277        assert_eq!(actual_rng.next_u64(), expected_rng.next_u64());
1278    }
1279
1280    #[test]
1281    fn test_verify_certificate_rejects_sub_quorum() {
1282        let mut rng = test_rng();
1283        let (schemes, verifier) = setup_signers(&mut rng, 4);
1284        let participants_len = schemes.len();
1285
1286        let attestations: Vec<_> = schemes
1287            .iter()
1288            .take(3)
1289            .map(|s| {
1290                s.sign::<Sha256Digest>(TestSubject {
1291                    message: Bytes::from_static(MESSAGE),
1292                })
1293                .unwrap()
1294            })
1295            .collect();
1296
1297        let mut certificate = schemes[0]
1298            .assemble(non_empty![@attestations], &Sequential)
1299            .unwrap();
1300
1301        // Artificially truncate to below quorum
1302        let mut signers: Vec<Participant> = certificate.signers.iter().collect();
1303        signers.pop();
1304        certificate.signers = Signers::new(participants_len.try_into().unwrap(), signers).unwrap();
1305        certificate.signatures.pop();
1306
1307        assert!(!verifier.verify_certificate::<_, Sha256Digest>(
1308            &mut rng,
1309            TestSubject {
1310                message: Bytes::from_static(MESSAGE),
1311            },
1312            &certificate,
1313            &Sequential,
1314        ));
1315    }
1316
1317    #[test]
1318    fn test_verify_certificate_rejects_mismatched_signature_count() {
1319        let mut rng = test_rng();
1320        let (schemes, verifier) = setup_signers(&mut rng, 4);
1321
1322        let attestations: Vec<_> = schemes
1323            .iter()
1324            .take(3)
1325            .map(|s| {
1326                s.sign::<Sha256Digest>(TestSubject {
1327                    message: Bytes::from_static(MESSAGE),
1328                })
1329                .unwrap()
1330            })
1331            .collect();
1332
1333        let mut certificate = schemes[0]
1334            .assemble(non_empty![@attestations], &Sequential)
1335            .unwrap();
1336
1337        // Remove one signature but keep signers bitmap unchanged
1338        certificate.signatures.pop();
1339
1340        assert!(!verifier.verify_certificate::<_, Sha256Digest>(
1341            &mut rng,
1342            TestSubject {
1343                message: Bytes::from_static(MESSAGE),
1344            },
1345            &certificate,
1346            &Sequential,
1347        ));
1348    }
1349
1350    #[test]
1351    fn test_verify_certificates_batch() {
1352        let mut rng = test_rng();
1353        let (schemes, verifier) = setup_signers(&mut rng, 4);
1354        let quorum = N3f1::quorum(schemes.len()) as usize;
1355
1356        let messages: Vec<Bytes> = [b"msg1".as_slice(), b"msg2".as_slice(), b"msg3".as_slice()]
1357            .into_iter()
1358            .map(Bytes::from_static)
1359            .collect();
1360        let mut certificates = Vec::new();
1361
1362        for msg in &messages {
1363            let attestations: Vec<_> = schemes
1364                .iter()
1365                .take(quorum)
1366                .map(|s| {
1367                    s.sign::<Sha256Digest>(TestSubject {
1368                        message: msg.clone(),
1369                    })
1370                    .unwrap()
1371                })
1372                .collect();
1373            certificates.push(
1374                schemes[0]
1375                    .assemble(non_empty![@attestations], &Sequential)
1376                    .unwrap(),
1377            );
1378        }
1379
1380        // Sequential verification of multiple certificates consumes the same
1381        // randomness as verification of one certificate.
1382        let certs_iter = messages.iter().zip(&certificates).map(|(msg, cert)| {
1383            (
1384                TestSubject {
1385                    message: msg.clone(),
1386                },
1387                cert,
1388            )
1389        });
1390
1391        let mut actual_rng = TestRng::new(0);
1392        let mut expected_rng = TestRng::new(0);
1393        assert!(verifier.verify_certificate::<_, Sha256Digest>(
1394            &mut expected_rng,
1395            TestSubject {
1396                message: messages[0].clone(),
1397            },
1398            &certificates[0],
1399            &Sequential,
1400        ));
1401        assert!(verifier.verify_certificates::<_, Sha256Digest, _>(
1402            &mut actual_rng,
1403            non_empty![@certs_iter],
1404            &Sequential
1405        ));
1406        assert_eq!(actual_rng.next_u64(), expected_rng.next_u64());
1407    }
1408
1409    #[test]
1410    fn test_verify_certificates_batch_detects_failure() {
1411        let mut rng = test_rng();
1412        let (schemes, verifier) = setup_signers(&mut rng, 4);
1413        let quorum = N3f1::quorum(schemes.len()) as usize;
1414
1415        let messages: Vec<Bytes> = [b"msg1".as_slice(), b"msg2".as_slice()]
1416            .into_iter()
1417            .map(Bytes::from_static)
1418            .collect();
1419        let mut certificates = Vec::new();
1420
1421        for msg in &messages {
1422            let attestations: Vec<_> = schemes
1423                .iter()
1424                .take(quorum)
1425                .map(|s| {
1426                    s.sign::<Sha256Digest>(TestSubject {
1427                        message: msg.clone(),
1428                    })
1429                    .unwrap()
1430                })
1431                .collect();
1432            certificates.push(
1433                schemes[0]
1434                    .assemble(non_empty![@attestations], &Sequential)
1435                    .unwrap(),
1436            );
1437        }
1438
1439        // Corrupt second certificate
1440        certificates[1].signatures[0] = certificates[1].signatures[1].clone();
1441
1442        let certs_iter = messages.iter().zip(&certificates).map(|(msg, cert)| {
1443            (
1444                TestSubject {
1445                    message: msg.clone(),
1446                },
1447                cert,
1448            )
1449        });
1450
1451        assert!(!verifier.verify_certificates::<_, Sha256Digest, _>(
1452            &mut rng,
1453            non_empty![@certs_iter],
1454            &Sequential
1455        ));
1456    }
1457
1458    #[test]
1459    fn test_assemble_certificate_rejects_duplicate_signers() {
1460        let mut rng = test_rng();
1461        let (schemes, _) = setup_signers(&mut rng, 4);
1462
1463        let mut attestations: Vec<_> = schemes
1464            .iter()
1465            .take(3)
1466            .map(|s| {
1467                s.sign::<Sha256Digest>(TestSubject {
1468                    message: Bytes::from_static(MESSAGE),
1469                })
1470                .unwrap()
1471            })
1472            .collect();
1473
1474        // Add a duplicate of the last vote
1475        let duplicate = attestations.last().unwrap().clone();
1476        let signer = duplicate.signer;
1477        attestations.push(duplicate);
1478
1479        assert_eq!(
1480            schemes[0].assemble(non_empty![@attestations], &Sequential),
1481            Err(AssemblyError::DuplicateSigner(signer))
1482        );
1483    }
1484
1485    #[test]
1486    fn test_scheme_clone_and_verifier() {
1487        let mut rng = test_rng();
1488        let (schemes, _) = setup_signers(&mut rng, 4);
1489        let participants = schemes[0].participants().clone();
1490
1491        // Clone a signer
1492        let signer = schemes[0].clone();
1493        assert!(
1494            signer
1495                .sign::<Sha256Digest>(TestSubject {
1496                    message: Bytes::from_static(MESSAGE),
1497                })
1498                .is_some(),
1499            "signer should produce votes"
1500        );
1501
1502        // A verifier cannot produce votes
1503        let verifier = Scheme::verifier(NAMESPACE, participants);
1504        assert!(
1505            verifier
1506                .sign::<Sha256Digest>(TestSubject {
1507                    message: Bytes::from_static(MESSAGE),
1508                })
1509                .is_none(),
1510            "verifier should not produce votes"
1511        );
1512    }
1513
1514    #[test]
1515    fn test_certificate_decode_validation() {
1516        let mut rng = test_rng();
1517        let (schemes, _) = setup_signers(&mut rng, 4);
1518        let participants_len = schemes.len();
1519        let participant_count =
1520            u32::try_from(participants_len).expect("participant count exceeds u32::MAX");
1521
1522        let attestations: Vec<_> = schemes
1523            .iter()
1524            .take(3)
1525            .map(|s| {
1526                s.sign::<Sha256Digest>(TestSubject {
1527                    message: Bytes::from_static(MESSAGE),
1528                })
1529                .unwrap()
1530            })
1531            .collect();
1532
1533        let certificate = schemes[0]
1534            .assemble(non_empty![@attestations], &Sequential)
1535            .unwrap();
1536
1537        // Well-formed certificate decodes successfully
1538        let encoded = certificate.encode();
1539        let decoded =
1540            Certificate::decode_cfg(encoded, &participants_len).expect("decode certificate");
1541        assert_eq!(decoded, certificate);
1542
1543        // Certificate with no signers is rejected
1544        let empty = Certificate {
1545            signers: Signers::new(participant_count, std::iter::empty::<Participant>()).unwrap(),
1546            signatures: Vec::new(),
1547        };
1548        assert!(Certificate::decode_cfg(empty.encode(), &participants_len).is_err());
1549
1550        // Certificate with mismatched signature count is rejected
1551        let mismatched = Certificate {
1552            signers: Signers::new(participant_count, [0u32, 1].map(Participant::new)).unwrap(),
1553            signatures: vec![certificate.signatures[0].clone()],
1554        };
1555        assert!(Certificate::decode_cfg(mismatched.encode(), &participants_len).is_err());
1556
1557        // Certificate containing more signers than the participant set is rejected
1558        let mut signers = certificate.signers.iter().collect::<Vec<_>>();
1559        signers.push(Participant::from_usize(participants_len));
1560        let mut sigs = certificate.signatures.clone();
1561        sigs.push(certificate.signatures[0].clone());
1562        let extended = Certificate {
1563            signers: Signers::new(participant_count + 1, signers).unwrap(),
1564            signatures: sigs,
1565        };
1566        assert!(Certificate::decode_cfg(extended.encode(), &participants_len).is_err());
1567    }
1568
1569    #[test]
1570    fn test_verify_certificate_rejects_unknown_signer() {
1571        let mut rng = test_rng();
1572        let (schemes, verifier) = setup_signers(&mut rng, 4);
1573        let participants_len = schemes.len();
1574
1575        let attestations: Vec<_> = schemes
1576            .iter()
1577            .take(3)
1578            .map(|s| {
1579                s.sign::<Sha256Digest>(TestSubject {
1580                    message: Bytes::from_static(MESSAGE),
1581                })
1582                .unwrap()
1583            })
1584            .collect();
1585
1586        let mut certificate = schemes[0]
1587            .assemble(non_empty![@attestations], &Sequential)
1588            .unwrap();
1589
1590        // Add an unknown signer (out of range)
1591        let mut signers: Vec<Participant> = certificate.signers.iter().collect();
1592        signers.push(Participant::from_usize(participants_len));
1593        certificate.signers =
1594            Signers::new((participants_len + 1).try_into().unwrap(), signers).unwrap();
1595        certificate
1596            .signatures
1597            .push(certificate.signatures[0].clone());
1598
1599        assert!(!verifier.verify_certificate::<_, Sha256Digest>(
1600            &mut rng,
1601            TestSubject {
1602                message: Bytes::from_static(MESSAGE),
1603            },
1604            &certificate,
1605            &Sequential,
1606        ));
1607    }
1608
1609    #[test]
1610    fn test_verify_certificate_rejects_invalid_certificate_signers_size() {
1611        let mut rng = test_rng();
1612        let (schemes, verifier) = setup_signers(&mut rng, 4);
1613        let participants_len = schemes.len();
1614
1615        let attestations: Vec<_> = schemes
1616            .iter()
1617            .take(3)
1618            .map(|s| {
1619                s.sign::<Sha256Digest>(TestSubject {
1620                    message: Bytes::from_static(MESSAGE),
1621                })
1622                .unwrap()
1623            })
1624            .collect();
1625
1626        let mut certificate = schemes[0]
1627            .assemble(non_empty![@attestations], &Sequential)
1628            .unwrap();
1629
1630        // Valid certificate passes
1631        assert!(verifier.verify_certificate::<_, Sha256Digest>(
1632            &mut rng,
1633            TestSubject {
1634                message: Bytes::from_static(MESSAGE),
1635            },
1636            &certificate,
1637            &Sequential,
1638        ));
1639
1640        // Make the signers bitmap size larger (mismatched with participants)
1641        let signers: Vec<Participant> = certificate.signers.iter().collect();
1642        certificate.signers =
1643            Signers::new((participants_len + 1).try_into().unwrap(), signers).unwrap();
1644
1645        // Certificate verification should fail due to size mismatch
1646        assert!(!verifier.verify_certificate::<_, Sha256Digest>(
1647            &mut rng,
1648            TestSubject {
1649                message: Bytes::from_static(MESSAGE),
1650            },
1651            &certificate,
1652            &Sequential,
1653        ));
1654    }
1655
1656    #[test]
1657    fn test_verify_certificate_rejects_signers_size_mismatch() {
1658        let mut rng = test_rng();
1659        let (schemes, verifier) = setup_signers(&mut rng, 4);
1660        let participants_len = schemes.len();
1661
1662        let attestations: Vec<_> = schemes
1663            .iter()
1664            .take(3)
1665            .map(|s| {
1666                s.sign::<Sha256Digest>(TestSubject {
1667                    message: Bytes::from_static(MESSAGE),
1668                })
1669                .unwrap()
1670            })
1671            .collect();
1672
1673        let mut certificate = schemes[0]
1674            .assemble(non_empty![@attestations], &Sequential)
1675            .unwrap();
1676
1677        // Make the signers bitmap size larger than participants
1678        let signers: Vec<Participant> = certificate.signers.iter().collect();
1679        certificate.signers =
1680            Signers::new((participants_len + 1).try_into().unwrap(), signers).unwrap();
1681        certificate
1682            .signatures
1683            .push(certificate.signatures[0].clone());
1684
1685        assert!(!verifier.verify_certificate::<_, Sha256Digest>(
1686            &mut rng,
1687            TestSubject {
1688                message: Bytes::from_static(MESSAGE),
1689            },
1690            &certificate,
1691            &Sequential,
1692        ));
1693    }
1694
1695    #[cfg(feature = "arbitrary")]
1696    mod conformance {
1697        use super::*;
1698        use commonware_codec::conformance::CodecConformance;
1699
1700        commonware_conformance::conformance_tests! {
1701            CodecConformance<Certificate>,
1702        }
1703    }
1704}