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