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