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