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::{Batch, PrivateKey, PublicKey, Signature as Ed25519Signature};
10use crate::{
11    certificate::{Attestation, Namespace, Scheme, Signers, Subject, Verification},
12    BatchVerifier, Digest, Signer as _, Verifier as _,
13};
14#[cfg(not(feature = "std"))]
15use alloc::{collections::BTreeSet, vec::Vec};
16use bytes::{Buf, BufMut};
17use commonware_codec::{types::lazy::Lazy, EncodeSize, Error, Read, ReadRangeExt, Write};
18use commonware_parallel::Strategy;
19use commonware_utils::{
20    ordered::{Quorum, Set},
21    Faults, Participant,
22};
23use rand_core::CryptoRng;
24#[cfg(feature = "std")]
25use std::collections::BTreeSet;
26
27/// Generic Ed25519 signing scheme implementation.
28///
29/// This struct contains the core cryptographic operations without protocol-specific
30/// context types. It can be reused across different protocols (simplex, aggregation, etc.)
31/// by wrapping it with protocol-specific trait implementations via the macro.
32#[derive(Clone, Debug)]
33pub struct Generic<N: Namespace> {
34    /// Participants in the committee.
35    pub participants: Set<PublicKey>,
36    /// Key used for generating signatures.
37    pub signer: Option<(Participant, PrivateKey)>,
38    /// Pre-computed namespace(s) for this subject type.
39    pub namespace: N,
40}
41
42impl<N: Namespace> Generic<N> {
43    /// Creates a new generic Ed25519 scheme instance.
44    pub fn signer(
45        namespace: &[u8],
46        participants: Set<PublicKey>,
47        private_key: PrivateKey,
48    ) -> Option<Self> {
49        let signer = participants
50            .index(&private_key.public_key())
51            .map(|index| (index, private_key))?;
52
53        Some(Self {
54            participants,
55            signer: Some(signer),
56            namespace: N::derive(namespace),
57        })
58    }
59
60    /// Builds a verifier that can authenticate signatures without generating them.
61    pub fn verifier(namespace: &[u8], participants: Set<PublicKey>) -> Self {
62        Self {
63            participants,
64            signer: None,
65            namespace: N::derive(namespace),
66        }
67    }
68
69    /// Returns the index of "self" in the participant set, if available.
70    pub fn me(&self) -> Option<Participant> {
71        self.signer.as_ref().map(|(index, _)| *index)
72    }
73
74    /// Signs a subject and returns the signer index and signature.
75    pub fn sign<'a, S, D>(&self, subject: S::Subject<'a, D>) -> Option<Attestation<S>>
76    where
77        S: Scheme<Signature = Ed25519Signature>,
78        S::Subject<'a, D>: Subject<Namespace = N>,
79        D: Digest,
80    {
81        let (index, private_key) = self.signer.as_ref()?;
82
83        let signature = private_key.sign(subject.namespace(&self.namespace), &subject.message());
84
85        Some(Attestation {
86            signer: *index,
87            signature: signature.into(),
88        })
89    }
90
91    /// Verifies a single attestation from a signer.
92    pub fn verify_attestation<'a, S, D>(
93        &self,
94        subject: S::Subject<'a, D>,
95        attestation: &Attestation<S>,
96    ) -> bool
97    where
98        S: Scheme<Signature = Ed25519Signature>,
99        S::Subject<'a, D>: Subject<Namespace = N>,
100        D: Digest,
101    {
102        let Some(public_key) = self.participants.key(attestation.signer) else {
103            return false;
104        };
105        let Some(signature) = attestation.signature.get() else {
106            return false;
107        };
108
109        public_key.verify(
110            subject.namespace(&self.namespace),
111            &subject.message(),
112            signature,
113        )
114    }
115
116    /// Batch-verifies attestations and returns verified attestations and invalid signers.
117    pub fn verify_attestations<'a, S, R, D, I>(
118        &self,
119        rng: &mut R,
120        subject: S::Subject<'a, D>,
121        attestations: I,
122        strategy: &impl Strategy,
123    ) -> Verification<S>
124    where
125        S: Scheme<Signature = Ed25519Signature>,
126        S::Subject<'a, D>: Subject<Namespace = N>,
127        R: CryptoRng,
128        D: Digest,
129        I: IntoIterator<Item = Attestation<S>>,
130    {
131        let namespace = subject.namespace(&self.namespace);
132        let message = subject.message();
133
134        let attestations = attestations.into_iter();
135        let mut invalid = BTreeSet::new();
136        let mut candidates = Vec::with_capacity(attestations.size_hint().0);
137        let mut batch = Batch::new(attestations.size_hint().0);
138
139        for attestation in attestations {
140            let Some(public_key) = self.participants.key(attestation.signer) else {
141                invalid.insert(attestation.signer);
142                continue;
143            };
144            let Some(signature) = attestation.signature.get() else {
145                invalid.insert(attestation.signer);
146                continue;
147            };
148
149            batch.add(namespace, &message, public_key, signature);
150            candidates.push((attestation, public_key));
151        }
152
153        if !candidates.is_empty() && !batch.verify(rng, strategy) {
154            // Batch failed: fall back to per-signer verification to isolate faulty attestations.
155            for (attestation, public_key) in &candidates {
156                let Some(signature) = attestation.signature.get() else {
157                    invalid.insert(attestation.signer);
158                    continue;
159                };
160                if !public_key.verify(namespace, &message, signature) {
161                    invalid.insert(attestation.signer);
162                }
163            }
164        }
165
166        let verified = candidates
167            .into_iter()
168            .filter_map(|(attestation, _)| {
169                if invalid.contains(&attestation.signer) {
170                    None
171                } else {
172                    Some(attestation)
173                }
174            })
175            .collect();
176
177        Verification::new(verified, invalid.into_iter().collect())
178    }
179
180    /// Assembles a certificate from a collection of attestations.
181    pub fn assemble<S, I, M>(&self, attestations: I) -> Option<Certificate>
182    where
183        S: Scheme<Signature = Ed25519Signature>,
184        I: IntoIterator<Item = Attestation<S>>,
185        M: Faults,
186    {
187        // Collect the signers and signatures.
188        let mut entries = Vec::new();
189        for Attestation { signer, signature } in attestations {
190            if usize::from(signer) >= self.participants.len() {
191                return None;
192            }
193            let signature = signature.get().cloned()?;
194            entries.push((signer, signature));
195        }
196        if entries.len() < self.participants.quorum::<M>() as usize {
197            return None;
198        }
199
200        // Sort the signatures by signer index.
201        entries.sort_by_key(|(signer, _)| *signer);
202        let (signer, signatures): (Vec<Participant>, Vec<_>) = entries.into_iter().unzip();
203        let signers = Signers::from(self.participants.len(), signer);
204        let signatures = signatures.into_iter().map(Lazy::from).collect();
205
206        Some(Certificate {
207            signers,
208            signatures,
209        })
210    }
211
212    /// Stages a certificate for batch verification.
213    ///
214    /// Returns false if the certificate structure is invalid.
215    fn batch_verify_certificate<'a, S, D, M>(
216        &self,
217        batch: &mut Batch,
218        subject: S::Subject<'a, D>,
219        certificate: &Certificate,
220    ) -> bool
221    where
222        S: Scheme,
223        S::Subject<'a, D>: Subject<Namespace = N>,
224        D: Digest,
225        M: Faults,
226    {
227        // If the certificate signers length does not match the participant set, return false.
228        if certificate.signers.len() != self.participants.len() {
229            return false;
230        }
231
232        // If the certificate signers and signatures counts differ, return false.
233        if certificate.signers.count() != certificate.signatures.len() {
234            return false;
235        }
236
237        // If the certificate does not meet the quorum, return false.
238        if certificate.signers.count() < self.participants.quorum::<M>() as usize {
239            return false;
240        }
241
242        // Add the certificate to the batch.
243        let namespace = subject.namespace(&self.namespace);
244        let message = subject.message();
245        for (signer, signature) in certificate.signers.iter().zip(&certificate.signatures) {
246            let Some(public_key) = self.participants.key(signer) else {
247                return false;
248            };
249            let Some(signature) = signature.get() else {
250                return false;
251            };
252
253            batch.add(namespace, &message, public_key, signature);
254        }
255
256        true
257    }
258
259    /// Verifies a certificate using batch verification.
260    pub fn verify_certificate<'a, S, R, D, M>(
261        &self,
262        rng: &mut R,
263        subject: S::Subject<'a, D>,
264        certificate: &Certificate,
265        strategy: &impl Strategy,
266    ) -> bool
267    where
268        S: Scheme,
269        S::Subject<'a, D>: Subject<Namespace = N>,
270        R: CryptoRng,
271        D: Digest,
272        M: Faults,
273    {
274        let mut batch = Batch::new(certificate.signatures.len());
275        if !self.batch_verify_certificate::<S, D, M>(&mut batch, subject, certificate) {
276            return false;
277        }
278
279        batch.verify(rng, strategy)
280    }
281
282    /// Verifies multiple certificates in a batch.
283    pub fn verify_certificates<'a, S, R, D, I, M>(
284        &self,
285        rng: &mut R,
286        certificates: I,
287        strategy: &impl Strategy,
288    ) -> bool
289    where
290        S: Scheme,
291        S::Subject<'a, D>: Subject<Namespace = N>,
292        R: CryptoRng,
293        D: Digest,
294        I: Iterator<Item = (S::Subject<'a, D>, &'a Certificate)>,
295        M: Faults,
296    {
297        // Each certificate stages at most one signature per participant.
298        let per_certificate = self.participants.len();
299        let mut batch = Batch::new(certificates.size_hint().0.saturating_mul(per_certificate));
300        for (subject, certificate) in certificates {
301            if !self.batch_verify_certificate::<S, D, M>(&mut batch, subject, certificate) {
302                return false;
303            }
304        }
305
306        batch.verify(rng, strategy)
307    }
308
309    pub const fn is_attributable() -> bool {
310        true
311    }
312
313    pub const fn is_batchable() -> bool {
314        true
315    }
316
317    pub const fn certificate_codec_config(&self) -> <Certificate as commonware_codec::Read>::Cfg {
318        self.participants.len()
319    }
320
321    pub const fn certificate_codec_config_unbounded() -> <Certificate as commonware_codec::Read>::Cfg
322    {
323        u32::MAX as usize
324    }
325}
326
327#[derive(Clone, Debug, PartialEq, Eq, Hash)]
328pub struct Certificate {
329    /// Bitmap of participant indices that contributed signatures.
330    pub signers: Signers,
331    /// Ed25519 signatures emitted by the respective participants ordered by signer index.
332    pub signatures: Vec<Lazy<Ed25519Signature>>,
333}
334
335#[cfg(feature = "arbitrary")]
336impl arbitrary::Arbitrary<'_> for Certificate {
337    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
338        let signers = Signers::arbitrary(u)?;
339        let signatures = (0..signers.count())
340            .map(|_| u.arbitrary::<Ed25519Signature>().map(Lazy::from))
341            .collect::<arbitrary::Result<Vec<_>>>()?;
342        Ok(Self {
343            signers,
344            signatures,
345        })
346    }
347}
348
349impl Write for Certificate {
350    fn write(&self, writer: &mut impl BufMut) {
351        self.signers.write(writer);
352        self.signatures.write(writer);
353    }
354}
355
356impl EncodeSize for Certificate {
357    fn encode_size(&self) -> usize {
358        self.signers.encode_size() + self.signatures.encode_size()
359    }
360}
361
362impl Read for Certificate {
363    type Cfg = usize;
364
365    fn read_cfg(reader: &mut impl Buf, participants: &usize) -> Result<Self, Error> {
366        let signers = Signers::read_cfg(reader, participants)?;
367        if signers.count() == 0 {
368            return Err(Error::Invalid(
369                "cryptography::ed25519::certificate::Certificate",
370                "Certificate contains no signers",
371            ));
372        }
373
374        let signatures = Vec::<Lazy<Ed25519Signature>>::read_range(reader, ..=*participants)?;
375        if signers.count() != signatures.len() {
376            return Err(Error::Invalid(
377                "cryptography::ed25519::certificate::Certificate",
378                "Signers and signatures counts differ",
379            ));
380        }
381
382        Ok(Self {
383            signers,
384            signatures,
385        })
386    }
387}
388
389/// Generates an Ed25519 signing scheme wrapper for a specific protocol.
390///
391/// This macro creates a complete wrapper struct with constructors, `Scheme` trait
392/// implementation, and a `fixture` function for testing.
393///
394/// # Parameters
395///
396/// - `$subject`: The subject type used as `Scheme::Subject<'a, D>`. Use `'a` and `D`
397///   in the subject type to bind to the GAT lifetime and digest type parameters.
398///
399/// - `$namespace`: The namespace type that implements [`Namespace`].
400///   This type pre-computes and stores any protocol-specific namespace bytes derived from
401///   a base namespace. The scheme calls `$namespace::derive(base)` at construction time
402///   to create the namespace, then passes it to `Subject::namespace()` during signing
403///   and verification. For simple protocols with only a base namespace, `Vec<u8>` can be used directly.
404///   For protocols with multiple message types, a custom struct can pre-compute all variants.
405///
406/// # Example
407/// ```ignore
408/// // For non-generic subject types with a single namespace:
409/// impl_certificate_ed25519!(MySubject, Vec<u8>);
410///
411/// // For protocols with generic subject types:
412/// impl_certificate_ed25519!(Subject<'a, D>, Namespace);
413/// ```
414#[macro_export]
415macro_rules! impl_certificate_ed25519 {
416    ($subject:ty, $namespace:ty) => {
417        /// Generates a test fixture with Ed25519 identities and signing schemes.
418        ///
419        /// Returns a [`commonware_cryptography::certificate::mocks::Fixture`] whose keys and
420        /// scheme instances share a consistent ordering.
421        #[cfg(feature = "mocks")]
422        #[allow(dead_code)]
423        pub fn fixture<R>(
424            rng: &mut R,
425            namespace: &[u8],
426            n: u32,
427        ) -> $crate::certificate::mocks::Fixture<Scheme>
428        where
429            R: rand_core::CryptoRng,
430        {
431            $crate::ed25519::certificate::mocks::fixture(
432                rng,
433                namespace,
434                n,
435                Scheme::signer,
436                Scheme::verifier,
437            )
438        }
439
440        /// Ed25519 signing scheme wrapper.
441        #[derive(Clone, Debug)]
442        pub struct Scheme {
443            generic: $crate::ed25519::certificate::Generic<$namespace>,
444        }
445
446        impl Scheme {
447            /// Creates a new scheme instance with the provided key material.
448            ///
449            /// Participants use the same key for both identity and signing.
450            ///
451            /// If the provided private key does not match any signing key in the participant set,
452            /// the instance will act as a verifier (unable to generate signatures).
453            ///
454            /// Returns `None` if the provided private key does not match any participant
455            /// in the participant set.
456            pub fn signer(
457                namespace: &[u8],
458                participants: commonware_utils::ordered::Set<$crate::ed25519::PublicKey>,
459                private_key: $crate::ed25519::PrivateKey,
460            ) -> Option<Self> {
461                Some(Self {
462                    generic: $crate::ed25519::certificate::Generic::signer(
463                        namespace,
464                        participants,
465                        private_key,
466                    )?,
467                })
468            }
469
470            /// Builds a verifier that can authenticate signatures without generating them.
471            ///
472            /// Participants use the same key for both identity and signing.
473            pub fn verifier(
474                namespace: &[u8],
475                participants: commonware_utils::ordered::Set<$crate::ed25519::PublicKey>,
476            ) -> Self {
477                Self {
478                    generic: $crate::ed25519::certificate::Generic::verifier(
479                        namespace,
480                        participants,
481                    ),
482                }
483            }
484        }
485
486        impl $crate::certificate::Verifier for Scheme {
487            type Subject<'a, D: $crate::Digest> = $subject;
488            type PublicKey = $crate::ed25519::PublicKey;
489            type Certificate = $crate::ed25519::certificate::Certificate;
490
491            fn verify_certificate<R, D, M>(
492                &self,
493                rng: &mut R,
494                subject: Self::Subject<'_, D>,
495                certificate: &Self::Certificate,
496                strategy: &impl commonware_parallel::Strategy,
497            ) -> bool
498            where
499                R: rand_core::CryptoRng,
500                D: $crate::Digest,
501                M: commonware_utils::Faults,
502            {
503                self.generic
504                    .verify_certificate::<Self, _, D, M>(rng, subject, certificate, strategy)
505            }
506
507            fn verify_certificates<'a, R, D, I, M>(
508                &self,
509                rng: &mut R,
510                certificates: I,
511                strategy: &impl commonware_parallel::Strategy,
512            ) -> bool
513            where
514                R: rand_core::CryptoRng,
515                D: $crate::Digest,
516                I: Iterator<Item = (Self::Subject<'a, D>, &'a Self::Certificate)>,
517                M: commonware_utils::Faults,
518            {
519                self.generic
520                    .verify_certificates::<Self, _, D, _, M>(rng, certificates, strategy)
521            }
522
523            fn is_batchable() -> bool {
524                $crate::ed25519::certificate::Generic::<$namespace>::is_batchable()
525            }
526
527            fn certificate_codec_config(
528                &self,
529            ) -> <Self::Certificate as commonware_codec::Read>::Cfg {
530                self.generic.certificate_codec_config()
531            }
532
533            fn certificate_codec_config_unbounded(
534            ) -> <Self::Certificate as commonware_codec::Read>::Cfg {
535                $crate::ed25519::certificate::Generic::<$namespace>::certificate_codec_config_unbounded()
536            }
537        }
538
539        impl $crate::certificate::Scheme for Scheme {
540            type Signature = $crate::ed25519::Signature;
541
542            fn me(&self) -> Option<commonware_utils::Participant> {
543                self.generic.me()
544            }
545
546            fn participants(&self) -> &commonware_utils::ordered::Set<Self::PublicKey> {
547                &self.generic.participants
548            }
549
550            fn sign<D: $crate::Digest>(
551                &self,
552                subject: Self::Subject<'_, D>,
553            ) -> Option<$crate::certificate::Attestation<Self>> {
554                self.generic.sign::<_, D>(subject)
555            }
556
557            fn verify_attestation<R, D>(
558                &self,
559                _rng: &mut R,
560                subject: Self::Subject<'_, D>,
561                attestation: &$crate::certificate::Attestation<Self>,
562                _strategy: &impl commonware_parallel::Strategy,
563            ) -> bool
564            where
565                R: rand_core::CryptoRng,
566                D: $crate::Digest,
567            {
568                self.generic
569                    .verify_attestation::<_, D>(subject, attestation)
570            }
571
572            fn verify_attestations<R, D, I>(
573                &self,
574                rng: &mut R,
575                subject: Self::Subject<'_, D>,
576                attestations: I,
577                strategy: &impl commonware_parallel::Strategy,
578            ) -> $crate::certificate::Verification<Self>
579            where
580                R: rand_core::CryptoRng,
581                D: $crate::Digest,
582                I: IntoIterator<Item = $crate::certificate::Attestation<Self>>,
583            {
584                self.generic
585                    .verify_attestations::<_, _, D, _>(rng, subject, attestations, strategy)
586            }
587
588            fn assemble<I, M>(
589                &self,
590                attestations: I,
591                _strategy: &impl commonware_parallel::Strategy,
592            ) -> Option<Self::Certificate>
593            where
594                I: IntoIterator<Item = $crate::certificate::Attestation<Self>>,
595                M: commonware_utils::Faults,
596            {
597                self.generic.assemble::<Self, _, M>(attestations)
598            }
599
600            fn is_attributable() -> bool {
601                $crate::ed25519::certificate::Generic::<$namespace>::is_attributable()
602            }
603        }
604    };
605}
606
607#[cfg(test)]
608mod tests {
609    use super::*;
610    use crate::{
611        certificate::{Scheme as _, Verifier as _},
612        sha256::Digest as Sha256Digest,
613    };
614    use bytes::Bytes;
615    use commonware_codec::{Decode, Encode};
616    use commonware_math::algebra::Random;
617    use commonware_parallel::Sequential;
618    use commonware_utils::{ordered::Set, test_rng, Faults, N3f1, Participant, TryCollect};
619
620    const NAMESPACE: &[u8] = b"test-ed25519";
621    const MESSAGE: &[u8] = b"test message";
622
623    /// Test context type for generic scheme tests.
624    #[derive(Clone, Debug)]
625    pub struct TestSubject {
626        pub message: Bytes,
627    }
628
629    impl Subject for TestSubject {
630        type Namespace = Vec<u8>;
631
632        fn namespace<'a>(&self, derived: &'a Self::Namespace) -> &'a [u8] {
633            derived.as_ref()
634        }
635
636        fn message(&self) -> Bytes {
637            self.message.clone()
638        }
639    }
640
641    // Use the macro to generate the test scheme
642    impl_certificate_ed25519!(TestSubject, Vec<u8>);
643
644    fn setup_signers(rng: &mut impl CryptoRng, n: u32) -> (Vec<Scheme>, Scheme) {
645        let private_keys: Vec<_> = (0..n).map(|_| PrivateKey::random(&mut *rng)).collect();
646        let participants: Set<PublicKey> = private_keys
647            .iter()
648            .map(|sk| sk.public_key())
649            .try_collect()
650            .unwrap();
651
652        let signers = private_keys
653            .into_iter()
654            .map(|sk| Scheme::signer(NAMESPACE, participants.clone(), sk).unwrap())
655            .collect();
656
657        let verifier = Scheme::verifier(NAMESPACE, participants);
658
659        (signers, verifier)
660    }
661
662    #[test]
663    fn test_is_attributable() {
664        assert!(Generic::<Vec<u8>>::is_attributable());
665        assert!(Scheme::is_attributable());
666    }
667
668    #[test]
669    fn test_is_batchable() {
670        assert!(Generic::<Vec<u8>>::is_batchable());
671        assert!(Scheme::is_batchable());
672    }
673
674    #[test]
675    fn test_sign_vote_roundtrip() {
676        let mut rng = test_rng();
677        let (schemes, _) = setup_signers(&mut rng, 4);
678        let scheme = &schemes[0];
679
680        let attestation = scheme
681            .sign::<Sha256Digest>(TestSubject {
682                message: Bytes::from_static(MESSAGE),
683            })
684            .unwrap();
685        assert!(scheme.verify_attestation::<_, Sha256Digest>(
686            &mut rng,
687            TestSubject {
688                message: Bytes::from_static(MESSAGE),
689            },
690            &attestation,
691            &Sequential,
692        ));
693    }
694
695    #[test]
696    fn test_verifier_cannot_sign() {
697        let mut rng = test_rng();
698        let (_, verifier) = setup_signers(&mut rng, 4);
699        assert!(verifier
700            .sign::<Sha256Digest>(TestSubject {
701                message: Bytes::from_static(MESSAGE)
702            })
703            .is_none());
704    }
705
706    #[test]
707    fn test_verify_attestations_filters_invalid() {
708        let mut rng = test_rng();
709        let (schemes, _) = setup_signers(&mut rng, 5);
710        let quorum = N3f1::quorum(schemes.len()) as usize;
711
712        let attestations: Vec<_> = schemes
713            .iter()
714            .take(quorum)
715            .map(|s| {
716                s.sign::<Sha256Digest>(TestSubject {
717                    message: Bytes::from_static(MESSAGE),
718                })
719                .unwrap()
720            })
721            .collect();
722
723        let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
724            &mut rng,
725            TestSubject {
726                message: Bytes::from_static(MESSAGE),
727            },
728            attestations.clone(),
729            &Sequential,
730        );
731        assert!(result.invalid.is_empty());
732        assert_eq!(result.verified.len(), quorum);
733
734        // Test 1: Corrupt one attestation - invalid signer index
735        let mut attestations_corrupted = attestations.clone();
736        attestations_corrupted[0].signer = Participant::new(999);
737        let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
738            &mut rng,
739            TestSubject {
740                message: Bytes::from_static(MESSAGE),
741            },
742            attestations_corrupted,
743            &Sequential,
744        );
745        assert_eq!(result.invalid, vec![Participant::new(999)]);
746        assert_eq!(result.verified.len(), quorum - 1);
747
748        // Test 2: Corrupt one attestation - invalid signature
749        let mut attestations_corrupted = attestations;
750        attestations_corrupted[0].signature = attestations_corrupted[1].signature.clone();
751        let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
752            &mut rng,
753            TestSubject {
754                message: Bytes::from_static(MESSAGE),
755            },
756            attestations_corrupted,
757            &Sequential,
758        );
759        // Batch verification may detect either signer 0 (wrong sig) or signer 1 (duplicate sig)
760        assert_eq!(result.invalid.len(), 1);
761        assert_eq!(result.verified.len(), quorum - 1);
762    }
763
764    #[test]
765    fn test_assemble_certificate() {
766        let mut rng = test_rng();
767        let (schemes, _) = setup_signers(&mut rng, 4);
768        let quorum = N3f1::quorum(schemes.len()) as usize;
769
770        let attestations: Vec<_> = schemes
771            .iter()
772            .take(quorum)
773            .map(|s| {
774                s.sign::<Sha256Digest>(TestSubject {
775                    message: Bytes::from_static(MESSAGE),
776                })
777                .unwrap()
778            })
779            .collect();
780
781        let certificate = schemes[0]
782            .assemble::<_, N3f1>(attestations, &Sequential)
783            .unwrap();
784
785        // Verify certificate has correct number of signers
786        assert_eq!(certificate.signers.count(), quorum);
787        assert_eq!(certificate.signatures.len(), quorum);
788    }
789
790    #[test]
791    fn test_assemble_certificate_sorts_signers() {
792        let mut rng = test_rng();
793        let (schemes, _) = setup_signers(&mut rng, 4);
794
795        // Get indices and sort them to create attestations in guaranteed reverse order
796        let mut indexed: Vec<_> = (0..3).map(|i| (schemes[i].me().unwrap(), i)).collect();
797        indexed.sort_by_key(|(idx, _)| *idx);
798
799        // Create attestations in reverse sorted order (guaranteed non-sorted)
800        let attestations = vec![
801            schemes[indexed[2].1]
802                .sign::<Sha256Digest>(TestSubject {
803                    message: Bytes::from_static(MESSAGE),
804                })
805                .unwrap(),
806            schemes[indexed[1].1]
807                .sign::<Sha256Digest>(TestSubject {
808                    message: Bytes::from_static(MESSAGE),
809                })
810                .unwrap(),
811            schemes[indexed[0].1]
812                .sign::<Sha256Digest>(TestSubject {
813                    message: Bytes::from_static(MESSAGE),
814                })
815                .unwrap(),
816        ];
817
818        let certificate = schemes[0]
819            .assemble::<_, N3f1>(attestations, &Sequential)
820            .unwrap();
821
822        // Verify signers are sorted by signer index
823        let expected: Vec<_> = indexed.iter().map(|(idx, _)| *idx).collect();
824        assert_eq!(certificate.signers.iter().collect::<Vec<_>>(), expected);
825    }
826
827    #[test]
828    fn test_verify_certificate() {
829        let mut rng = test_rng();
830        let (schemes, verifier) = setup_signers(&mut rng, 4);
831        let quorum = N3f1::quorum(schemes.len()) as usize;
832
833        let attestations: Vec<_> = schemes
834            .iter()
835            .take(quorum)
836            .map(|s| {
837                s.sign::<Sha256Digest>(TestSubject {
838                    message: Bytes::from_static(MESSAGE),
839                })
840                .unwrap()
841            })
842            .collect();
843
844        let certificate = schemes[0]
845            .assemble::<_, N3f1>(attestations, &Sequential)
846            .unwrap();
847
848        assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
849            &mut rng,
850            TestSubject {
851                message: Bytes::from_static(MESSAGE)
852            },
853            &certificate,
854            &Sequential,
855        ));
856    }
857
858    #[test]
859    fn test_verify_certificate_detects_corruption() {
860        let mut rng = test_rng();
861        let (schemes, verifier) = setup_signers(&mut rng, 4);
862        let quorum = N3f1::quorum(schemes.len()) as usize;
863
864        let attestations: Vec<_> = schemes
865            .iter()
866            .take(quorum)
867            .map(|s| {
868                s.sign::<Sha256Digest>(TestSubject {
869                    message: Bytes::from_static(MESSAGE),
870                })
871                .unwrap()
872            })
873            .collect();
874
875        let certificate = schemes[0]
876            .assemble::<_, N3f1>(attestations, &Sequential)
877            .unwrap();
878
879        // Valid certificate passes
880        assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
881            &mut rng,
882            TestSubject {
883                message: Bytes::from_static(MESSAGE),
884            },
885            &certificate,
886            &Sequential,
887        ));
888
889        // Corrupted certificate fails
890        let mut corrupted = certificate;
891        corrupted.signatures[0] = corrupted.signatures[1].clone();
892        assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
893            &mut rng,
894            TestSubject {
895                message: Bytes::from_static(MESSAGE),
896            },
897            &corrupted,
898            &Sequential,
899        ));
900    }
901
902    #[test]
903    fn test_certificate_codec_roundtrip() {
904        let mut rng = test_rng();
905        let (schemes, _) = setup_signers(&mut rng, 4);
906        let quorum = N3f1::quorum(schemes.len()) as usize;
907
908        let attestations: Vec<_> = schemes
909            .iter()
910            .take(quorum)
911            .map(|s| {
912                s.sign::<Sha256Digest>(TestSubject {
913                    message: Bytes::from_static(MESSAGE),
914                })
915                .unwrap()
916            })
917            .collect();
918
919        let certificate = schemes[0]
920            .assemble::<_, N3f1>(attestations, &Sequential)
921            .unwrap();
922        let encoded = certificate.encode();
923        let decoded = Certificate::decode_cfg(encoded, &schemes.len()).expect("decode certificate");
924        assert_eq!(decoded, certificate);
925    }
926
927    #[test]
928    fn test_certificate_rejects_sub_quorum() {
929        let mut rng = test_rng();
930        let (schemes, _) = setup_signers(&mut rng, 4);
931        let sub_quorum = 2; // Less than quorum (3)
932
933        let attestations: Vec<_> = schemes
934            .iter()
935            .take(sub_quorum)
936            .map(|s| {
937                s.sign::<Sha256Digest>(TestSubject {
938                    message: Bytes::from_static(MESSAGE),
939                })
940                .unwrap()
941            })
942            .collect();
943
944        assert!(schemes[0]
945            .assemble::<_, N3f1>(attestations, &Sequential)
946            .is_none());
947    }
948
949    #[test]
950    fn test_certificate_rejects_invalid_signer() {
951        let mut rng = test_rng();
952        let (schemes, _) = setup_signers(&mut rng, 4);
953        let quorum = N3f1::quorum(schemes.len()) as usize;
954
955        let mut attestations: Vec<_> = schemes
956            .iter()
957            .take(quorum)
958            .map(|s| {
959                s.sign::<Sha256Digest>(TestSubject {
960                    message: Bytes::from_static(MESSAGE),
961                })
962                .unwrap()
963            })
964            .collect();
965
966        // Corrupt signer index to be out of range
967        attestations[0].signer = Participant::new(999);
968
969        assert!(schemes[0]
970            .assemble::<_, N3f1>(attestations, &Sequential)
971            .is_none());
972    }
973
974    #[test]
975    fn test_verify_certificate_rejects_sub_quorum() {
976        let mut rng = test_rng();
977        let (schemes, verifier) = setup_signers(&mut rng, 4);
978        let participants_len = schemes.len();
979
980        let attestations: Vec<_> = schemes
981            .iter()
982            .take(3)
983            .map(|s| {
984                s.sign::<Sha256Digest>(TestSubject {
985                    message: Bytes::from_static(MESSAGE),
986                })
987                .unwrap()
988            })
989            .collect();
990
991        let mut certificate = schemes[0]
992            .assemble::<_, N3f1>(attestations, &Sequential)
993            .unwrap();
994
995        // Artificially truncate to below quorum
996        let mut signers: Vec<Participant> = certificate.signers.iter().collect();
997        signers.pop();
998        certificate.signers = Signers::from(participants_len, signers);
999        certificate.signatures.pop();
1000
1001        assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1002            &mut rng,
1003            TestSubject {
1004                message: Bytes::from_static(MESSAGE),
1005            },
1006            &certificate,
1007            &Sequential,
1008        ));
1009    }
1010
1011    #[test]
1012    fn test_verify_certificate_rejects_mismatched_signature_count() {
1013        let mut rng = test_rng();
1014        let (schemes, verifier) = setup_signers(&mut rng, 4);
1015
1016        let attestations: Vec<_> = schemes
1017            .iter()
1018            .take(3)
1019            .map(|s| {
1020                s.sign::<Sha256Digest>(TestSubject {
1021                    message: Bytes::from_static(MESSAGE),
1022                })
1023                .unwrap()
1024            })
1025            .collect();
1026
1027        let mut certificate = schemes[0]
1028            .assemble::<_, N3f1>(attestations, &Sequential)
1029            .unwrap();
1030
1031        // Remove one signature but keep signers bitmap unchanged
1032        certificate.signatures.pop();
1033
1034        assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1035            &mut rng,
1036            TestSubject {
1037                message: Bytes::from_static(MESSAGE),
1038            },
1039            &certificate,
1040            &Sequential,
1041        ));
1042    }
1043
1044    #[test]
1045    fn test_verify_certificates_batch() {
1046        let mut rng = test_rng();
1047        let (schemes, verifier) = setup_signers(&mut rng, 4);
1048        let quorum = N3f1::quorum(schemes.len()) as usize;
1049
1050        let messages: Vec<Bytes> = [b"msg1".as_slice(), b"msg2".as_slice(), b"msg3".as_slice()]
1051            .into_iter()
1052            .map(Bytes::copy_from_slice)
1053            .collect();
1054        let mut certificates = Vec::new();
1055
1056        for msg in &messages {
1057            let attestations: Vec<_> = schemes
1058                .iter()
1059                .take(quorum)
1060                .map(|s| {
1061                    s.sign::<Sha256Digest>(TestSubject {
1062                        message: msg.clone(),
1063                    })
1064                    .unwrap()
1065                })
1066                .collect();
1067            certificates.push(
1068                schemes[0]
1069                    .assemble::<_, N3f1>(attestations, &Sequential)
1070                    .unwrap(),
1071            );
1072        }
1073
1074        let certs_iter = messages.iter().zip(&certificates).map(|(msg, cert)| {
1075            (
1076                TestSubject {
1077                    message: msg.clone(),
1078                },
1079                cert,
1080            )
1081        });
1082
1083        assert!(verifier.verify_certificates::<_, Sha256Digest, _, N3f1>(
1084            &mut rng,
1085            certs_iter,
1086            &Sequential
1087        ));
1088    }
1089
1090    #[test]
1091    fn test_verify_certificates_batch_detects_failure() {
1092        let mut rng = test_rng();
1093        let (schemes, verifier) = setup_signers(&mut rng, 4);
1094        let quorum = N3f1::quorum(schemes.len()) as usize;
1095
1096        let messages: Vec<Bytes> = [b"msg1".as_slice(), b"msg2".as_slice()]
1097            .into_iter()
1098            .map(Bytes::copy_from_slice)
1099            .collect();
1100        let mut certificates = Vec::new();
1101
1102        for msg in &messages {
1103            let attestations: Vec<_> = schemes
1104                .iter()
1105                .take(quorum)
1106                .map(|s| {
1107                    s.sign::<Sha256Digest>(TestSubject {
1108                        message: msg.clone(),
1109                    })
1110                    .unwrap()
1111                })
1112                .collect();
1113            certificates.push(
1114                schemes[0]
1115                    .assemble::<_, N3f1>(attestations, &Sequential)
1116                    .unwrap(),
1117            );
1118        }
1119
1120        // Corrupt second certificate
1121        certificates[1].signatures[0] = certificates[1].signatures[1].clone();
1122
1123        let certs_iter = messages.iter().zip(&certificates).map(|(msg, cert)| {
1124            (
1125                TestSubject {
1126                    message: msg.clone(),
1127                },
1128                cert,
1129            )
1130        });
1131
1132        assert!(!verifier.verify_certificates::<_, Sha256Digest, _, N3f1>(
1133            &mut rng,
1134            certs_iter,
1135            &Sequential
1136        ));
1137    }
1138
1139    #[test]
1140    #[should_panic(expected = "duplicate signer index")]
1141    fn test_assemble_certificate_rejects_duplicate_signers() {
1142        let mut rng = test_rng();
1143        let (schemes, _) = setup_signers(&mut rng, 4);
1144
1145        let mut attestations: Vec<_> = schemes
1146            .iter()
1147            .take(3)
1148            .map(|s| {
1149                s.sign::<Sha256Digest>(TestSubject {
1150                    message: Bytes::from_static(MESSAGE),
1151                })
1152                .unwrap()
1153            })
1154            .collect();
1155
1156        // Add a duplicate of the last vote
1157        attestations.push(attestations.last().unwrap().clone());
1158
1159        // This should panic due to duplicate signer
1160        schemes[0].assemble::<_, N3f1>(attestations, &Sequential);
1161    }
1162
1163    #[test]
1164    fn test_scheme_clone_and_verifier() {
1165        let mut rng = test_rng();
1166        let (schemes, _) = setup_signers(&mut rng, 4);
1167        let participants = schemes[0].participants().clone();
1168
1169        // Clone a signer
1170        let signer = schemes[0].clone();
1171        assert!(
1172            signer
1173                .sign::<Sha256Digest>(TestSubject {
1174                    message: Bytes::from_static(MESSAGE),
1175                })
1176                .is_some(),
1177            "signer should produce votes"
1178        );
1179
1180        // A verifier cannot produce votes
1181        let verifier = Scheme::verifier(NAMESPACE, participants);
1182        assert!(
1183            verifier
1184                .sign::<Sha256Digest>(TestSubject {
1185                    message: Bytes::from_static(MESSAGE),
1186                })
1187                .is_none(),
1188            "verifier should not produce votes"
1189        );
1190    }
1191
1192    #[test]
1193    fn test_certificate_decode_validation() {
1194        let mut rng = test_rng();
1195        let (schemes, _) = setup_signers(&mut rng, 4);
1196        let participants_len = schemes.len();
1197
1198        let attestations: Vec<_> = schemes
1199            .iter()
1200            .take(3)
1201            .map(|s| {
1202                s.sign::<Sha256Digest>(TestSubject {
1203                    message: Bytes::from_static(MESSAGE),
1204                })
1205                .unwrap()
1206            })
1207            .collect();
1208
1209        let certificate = schemes[0]
1210            .assemble::<_, N3f1>(attestations, &Sequential)
1211            .unwrap();
1212
1213        // Well-formed certificate decodes successfully
1214        let encoded = certificate.encode();
1215        let decoded =
1216            Certificate::decode_cfg(encoded, &participants_len).expect("decode certificate");
1217        assert_eq!(decoded, certificate);
1218
1219        // Certificate with no signers is rejected
1220        let empty = Certificate {
1221            signers: Signers::from(participants_len, std::iter::empty::<Participant>()),
1222            signatures: Vec::new(),
1223        };
1224        assert!(Certificate::decode_cfg(empty.encode(), &participants_len).is_err());
1225
1226        // Certificate with mismatched signature count is rejected
1227        let mismatched = Certificate {
1228            signers: Signers::from(participants_len, [0u32, 1].map(Participant::new)),
1229            signatures: vec![certificate.signatures[0].clone()],
1230        };
1231        assert!(Certificate::decode_cfg(mismatched.encode(), &participants_len).is_err());
1232
1233        // Certificate containing more signers than the participant set is rejected
1234        let mut signers = certificate.signers.iter().collect::<Vec<_>>();
1235        signers.push(Participant::from_usize(participants_len));
1236        let mut sigs = certificate.signatures.clone();
1237        sigs.push(certificate.signatures[0].clone());
1238        let extended = Certificate {
1239            signers: Signers::from(participants_len + 1, signers),
1240            signatures: sigs,
1241        };
1242        assert!(Certificate::decode_cfg(extended.encode(), &participants_len).is_err());
1243    }
1244
1245    #[test]
1246    fn test_verify_certificate_rejects_unknown_signer() {
1247        let mut rng = test_rng();
1248        let (schemes, verifier) = setup_signers(&mut rng, 4);
1249        let participants_len = schemes.len();
1250
1251        let attestations: Vec<_> = schemes
1252            .iter()
1253            .take(3)
1254            .map(|s| {
1255                s.sign::<Sha256Digest>(TestSubject {
1256                    message: Bytes::from_static(MESSAGE),
1257                })
1258                .unwrap()
1259            })
1260            .collect();
1261
1262        let mut certificate = schemes[0]
1263            .assemble::<_, N3f1>(attestations, &Sequential)
1264            .unwrap();
1265
1266        // Add an unknown signer (out of range)
1267        let mut signers: Vec<Participant> = certificate.signers.iter().collect();
1268        signers.push(Participant::from_usize(participants_len));
1269        certificate.signers = Signers::from(participants_len + 1, signers);
1270        certificate
1271            .signatures
1272            .push(certificate.signatures[0].clone());
1273
1274        assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1275            &mut rng,
1276            TestSubject {
1277                message: Bytes::from_static(MESSAGE),
1278            },
1279            &certificate,
1280            &Sequential,
1281        ));
1282    }
1283
1284    #[test]
1285    fn test_verify_certificate_rejects_invalid_certificate_signers_size() {
1286        let mut rng = test_rng();
1287        let (schemes, verifier) = setup_signers(&mut rng, 4);
1288        let participants_len = schemes.len();
1289
1290        let attestations: Vec<_> = schemes
1291            .iter()
1292            .take(3)
1293            .map(|s| {
1294                s.sign::<Sha256Digest>(TestSubject {
1295                    message: Bytes::from_static(MESSAGE),
1296                })
1297                .unwrap()
1298            })
1299            .collect();
1300
1301        let mut certificate = schemes[0]
1302            .assemble::<_, N3f1>(attestations, &Sequential)
1303            .unwrap();
1304
1305        // Valid certificate passes
1306        assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1307            &mut rng,
1308            TestSubject {
1309                message: Bytes::from_static(MESSAGE),
1310            },
1311            &certificate,
1312            &Sequential,
1313        ));
1314
1315        // Make the signers bitmap size larger (mismatched with participants)
1316        let signers: Vec<Participant> = certificate.signers.iter().collect();
1317        certificate.signers = Signers::from(participants_len + 1, signers);
1318
1319        // Certificate verification should fail due to size mismatch
1320        assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1321            &mut rng,
1322            TestSubject {
1323                message: Bytes::from_static(MESSAGE),
1324            },
1325            &certificate,
1326            &Sequential,
1327        ));
1328    }
1329
1330    #[test]
1331    fn test_verify_certificate_rejects_signers_size_mismatch() {
1332        let mut rng = test_rng();
1333        let (schemes, verifier) = setup_signers(&mut rng, 4);
1334        let participants_len = schemes.len();
1335
1336        let attestations: Vec<_> = schemes
1337            .iter()
1338            .take(3)
1339            .map(|s| {
1340                s.sign::<Sha256Digest>(TestSubject {
1341                    message: Bytes::from_static(MESSAGE),
1342                })
1343                .unwrap()
1344            })
1345            .collect();
1346
1347        let mut certificate = schemes[0]
1348            .assemble::<_, N3f1>(attestations, &Sequential)
1349            .unwrap();
1350
1351        // Make the signers bitmap size larger than participants
1352        let signers: Vec<Participant> = certificate.signers.iter().collect();
1353        certificate.signers = Signers::from(participants_len + 1, signers);
1354        certificate
1355            .signatures
1356            .push(certificate.signatures[0].clone());
1357
1358        assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1359            &mut rng,
1360            TestSubject {
1361                message: Bytes::from_static(MESSAGE),
1362            },
1363            &certificate,
1364            &Sequential,
1365        ));
1366    }
1367
1368    #[cfg(feature = "arbitrary")]
1369    mod conformance {
1370        use super::*;
1371        use commonware_codec::conformance::CodecConformance;
1372
1373        commonware_conformance::conformance_tests! {
1374            CodecConformance<Certificate>,
1375        }
1376    }
1377}