Skip to main content

commonware_cryptography/secp256r1/certificate/
mod.rs

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