commonware_consensus/simplex/signing_scheme/
ed25519.rs

1//! Ed25519 implementation of the [`Scheme`] trait for `simplex`.
2//!
3//! [`Scheme`] is **attributable**: individual signatures can be safely
4//! presented to some third party as evidence of either liveness or of committing a fault. Certificates
5//! contain signer indices alongside individual signatures, enabling secure
6//! per-validator activity tracking and fault detection.
7
8use crate::{
9    simplex::{
10        signing_scheme::{self, utils::Signers, vote_namespace_and_message},
11        types::{OrderedExt, Vote, VoteContext, VoteVerification},
12    },
13    types::Round,
14};
15use bytes::{Buf, BufMut};
16use commonware_codec::{EncodeSize, Error, Read, ReadRangeExt, Write};
17use commonware_cryptography::{
18    ed25519::{self, Batch},
19    BatchVerifier, Digest, Signer as _, Verifier as _,
20};
21use commonware_utils::set::Ordered;
22use rand::{CryptoRng, Rng};
23use std::collections::BTreeSet;
24
25/// Ed25519 implementation of the [`Scheme`] trait.
26#[derive(Clone, Debug)]
27pub struct Scheme {
28    /// Participants in the committee.
29    participants: Ordered<ed25519::PublicKey>,
30    /// Key used for generating signatures.
31    signer: Option<(u32, ed25519::PrivateKey)>,
32}
33
34impl Scheme {
35    /// Creates a new scheme instance with the provided key material.
36    ///
37    /// Participants use the same key for both identity and consensus.
38    ///
39    /// If the provided private key does not match any consensus key in the committee,
40    /// the instance will act as a verifier (unable to generate signatures).
41    pub fn new(
42        participants: Ordered<ed25519::PublicKey>,
43        private_key: ed25519::PrivateKey,
44    ) -> Self {
45        let signer = participants
46            .position(&private_key.public_key())
47            .map(|index| (index as u32, private_key));
48
49        Self {
50            participants,
51            signer,
52        }
53    }
54
55    /// Builds a verifier that can authenticate votes without generating signatures.
56    ///
57    /// Participants use the same key for both identity and consensus.
58    pub fn verifier(participants: Ordered<ed25519::PublicKey>) -> Self {
59        Self {
60            participants,
61            signer: None,
62        }
63    }
64
65    /// Stage a certificate for batch verification.
66    fn batch_verify_certificate<'a, D: Digest>(
67        &self,
68        batch: &mut Batch,
69        namespace: &[u8],
70        context: VoteContext<'a, D>,
71        certificate: &'a Certificate,
72    ) -> bool {
73        // If the certificate signers length does not match the participant set, return false.
74        if certificate.signers.len() != self.participants.len() {
75            return false;
76        }
77
78        // If the certificate signers and signatures counts differ, return false.
79        if certificate.signers.count() != certificate.signatures.len() {
80            return false;
81        }
82
83        // If the certificate does not meet the quorum, return false.
84        if certificate.signers.count() < self.participants.quorum() as usize {
85            return false;
86        }
87
88        // Add the certificate to the batch.
89        let (namespace, message) = vote_namespace_and_message(namespace, context);
90        for (signer, signature) in certificate.signers.iter().zip(&certificate.signatures) {
91            let Some(public_key) = self.participants.get(signer as usize) else {
92                return false;
93            };
94
95            batch.add(
96                Some(namespace.as_ref()),
97                message.as_ref(),
98                public_key,
99                signature,
100            );
101        }
102
103        true
104    }
105}
106
107#[derive(Clone, Debug, PartialEq, Eq, Hash)]
108pub struct Certificate {
109    /// Bitmap of validator indices that contributed signatures.
110    pub signers: Signers,
111    /// Ed25519 signatures emitted by the respective validators ordered by signer index.
112    pub signatures: Vec<ed25519::Signature>,
113}
114
115impl Write for Certificate {
116    fn write(&self, writer: &mut impl BufMut) {
117        self.signers.write(writer);
118        self.signatures.write(writer);
119    }
120}
121
122impl EncodeSize for Certificate {
123    fn encode_size(&self) -> usize {
124        self.signers.encode_size() + self.signatures.encode_size()
125    }
126}
127
128impl Read for Certificate {
129    type Cfg = usize;
130
131    fn read_cfg(reader: &mut impl Buf, participants: &usize) -> Result<Self, Error> {
132        let signers = Signers::read_cfg(reader, participants)?;
133        if signers.count() == 0 {
134            return Err(Error::Invalid(
135                "consensus::simplex::signing_scheme::ed25519::Certificate",
136                "Certificate contains no signers",
137            ));
138        }
139
140        let signatures = Vec::<ed25519::Signature>::read_range(reader, ..=*participants)?;
141        if signers.count() != signatures.len() {
142            return Err(Error::Invalid(
143                "consensus::simplex::signing_scheme::ed25519::Certificate",
144                "Signers and signatures counts differ",
145            ));
146        }
147
148        Ok(Self {
149            signers,
150            signatures,
151        })
152    }
153}
154
155impl signing_scheme::Scheme for Scheme {
156    type PublicKey = ed25519::PublicKey;
157    type Signature = ed25519::Signature;
158    type Certificate = Certificate;
159    type Seed = ();
160
161    fn me(&self) -> Option<u32> {
162        self.signer.as_ref().map(|(index, _)| *index)
163    }
164
165    fn participants(&self) -> &Ordered<Self::PublicKey> {
166        &self.participants
167    }
168
169    fn sign_vote<D: Digest>(
170        &self,
171        namespace: &[u8],
172        context: VoteContext<'_, D>,
173    ) -> Option<Vote<Self>> {
174        let (index, private_key) = self.signer.as_ref()?;
175
176        let (namespace, message) = vote_namespace_and_message(namespace, context);
177        let signature = private_key.sign(Some(namespace.as_ref()), message.as_ref());
178
179        Some(Vote {
180            signer: *index,
181            signature,
182        })
183    }
184
185    fn verify_vote<D: Digest>(
186        &self,
187        namespace: &[u8],
188        context: VoteContext<'_, D>,
189        vote: &Vote<Self>,
190    ) -> bool {
191        let Some(public_key) = self.participants.get(vote.signer as usize) else {
192            return false;
193        };
194
195        let (namespace, message) = vote_namespace_and_message(namespace, context);
196        public_key.verify(Some(namespace.as_ref()), message.as_ref(), &vote.signature)
197    }
198
199    fn verify_votes<R, D, I>(
200        &self,
201        rng: &mut R,
202        namespace: &[u8],
203        context: VoteContext<'_, D>,
204        votes: I,
205    ) -> VoteVerification<Self>
206    where
207        R: Rng + CryptoRng,
208        D: Digest,
209        I: IntoIterator<Item = Vote<Self>>,
210    {
211        let (namespace, message) = vote_namespace_and_message(namespace, context);
212
213        let mut invalid = BTreeSet::new();
214        let mut candidates = Vec::new();
215        let mut batch = Batch::new();
216
217        for vote in votes.into_iter() {
218            let Some(public_key) = self.participants.get(vote.signer as usize) else {
219                invalid.insert(vote.signer);
220                continue;
221            };
222
223            batch.add(
224                Some(namespace.as_ref()),
225                message.as_ref(),
226                public_key,
227                &vote.signature,
228            );
229
230            candidates.push((vote, public_key));
231        }
232
233        if !candidates.is_empty() && !batch.verify(rng) {
234            // Batch failed: fall back to per-signer verification to isolate faulty votes.
235            for (vote, public_key) in &candidates {
236                if !public_key.verify(Some(namespace.as_ref()), message.as_ref(), &vote.signature) {
237                    invalid.insert(vote.signer);
238                }
239            }
240        }
241
242        let verified = candidates
243            .into_iter()
244            .filter_map(|(vote, _)| {
245                if invalid.contains(&vote.signer) {
246                    None
247                } else {
248                    Some(vote)
249                }
250            })
251            .collect();
252
253        VoteVerification::new(verified, invalid.into_iter().collect())
254    }
255
256    fn assemble_certificate<I>(&self, votes: I) -> Option<Self::Certificate>
257    where
258        I: IntoIterator<Item = Vote<Self>>,
259    {
260        // Collect the signers and signatures.
261        let mut entries = Vec::new();
262        for Vote { signer, signature } in votes {
263            if signer as usize >= self.participants.len() {
264                return None;
265            }
266
267            entries.push((signer, signature));
268        }
269        if entries.len() < self.participants.quorum() as usize {
270            return None;
271        }
272
273        // Sort the signatures by signer index.
274        entries.sort_by_key(|(signer, _)| *signer);
275        let (signer, signatures): (Vec<u32>, Vec<_>) = entries.into_iter().unzip();
276        let signers = Signers::from(self.participants.len(), signer);
277
278        Some(Certificate {
279            signers,
280            signatures,
281        })
282    }
283
284    fn verify_certificate<R: Rng + CryptoRng, D: Digest>(
285        &self,
286        rng: &mut R,
287        namespace: &[u8],
288        context: VoteContext<'_, D>,
289        certificate: &Self::Certificate,
290    ) -> bool {
291        let mut batch = Batch::new();
292        if !self.batch_verify_certificate(&mut batch, namespace, context, certificate) {
293            return false;
294        }
295
296        batch.verify(rng)
297    }
298
299    fn verify_certificates<'a, R, D, I>(
300        &self,
301        rng: &mut R,
302        namespace: &[u8],
303        certificates: I,
304    ) -> bool
305    where
306        R: Rng + CryptoRng,
307        D: Digest,
308        I: Iterator<Item = (VoteContext<'a, D>, &'a Self::Certificate)>,
309    {
310        let mut batch = Batch::new();
311        for (context, certificate) in certificates {
312            if !self.batch_verify_certificate(&mut batch, namespace, context, certificate) {
313                return false;
314            }
315        }
316
317        batch.verify(rng)
318    }
319
320    fn seed(&self, _: Round, _: &Self::Certificate) -> Option<Self::Seed> {
321        None
322    }
323
324    fn is_attributable(&self) -> bool {
325        true
326    }
327
328    fn certificate_codec_config(&self) -> <Self::Certificate as Read>::Cfg {
329        self.participants.len()
330    }
331
332    fn certificate_codec_config_unbounded() -> <Self::Certificate as Read>::Cfg {
333        u32::MAX as usize
334    }
335}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340    use crate::{
341        simplex::{
342            mocks::fixtures::{ed25519, Fixture},
343            signing_scheme::Scheme as _,
344            types::{Proposal, VoteContext},
345        },
346        types::Round,
347    };
348    use commonware_codec::{Decode, Encode};
349    use commonware_cryptography::{sha256::Digest as Sha256Digest, Hasher, Sha256};
350    use commonware_utils::quorum;
351    use rand::{
352        rngs::{OsRng, StdRng},
353        thread_rng, SeedableRng,
354    };
355
356    const NAMESPACE: &[u8] = b"ed25519-signing-scheme";
357
358    fn setup_signers(n: u32, seed: u64) -> (Vec<Scheme>, Ordered<ed25519::PublicKey>) {
359        let mut rng = StdRng::seed_from_u64(seed);
360        let Fixture {
361            participants,
362            schemes,
363            ..
364        } = ed25519(&mut rng, n);
365
366        (schemes, participants.into())
367    }
368
369    fn sample_proposal(round: u64, view: u64, tag: u8) -> Proposal<Sha256Digest> {
370        Proposal::new(
371            Round::new(round, view),
372            view.saturating_sub(1),
373            Sha256::hash(&[tag]),
374        )
375    }
376
377    #[test]
378    fn test_sign_vote_roundtrip_for_each_context() {
379        let (schemes, _) = setup_signers(4, 42);
380        let scheme = &schemes[0];
381
382        let proposal = sample_proposal(0, 2, 1);
383        let vote = scheme
384            .sign_vote(
385                NAMESPACE,
386                VoteContext::Notarize {
387                    proposal: &proposal,
388                },
389            )
390            .unwrap();
391        assert!(scheme.verify_vote(
392            NAMESPACE,
393            VoteContext::Notarize {
394                proposal: &proposal,
395            },
396            &vote
397        ));
398
399        let vote = scheme
400            .sign_vote::<Sha256Digest>(
401                NAMESPACE,
402                VoteContext::Nullify {
403                    round: proposal.round,
404                },
405            )
406            .unwrap();
407        assert!(scheme.verify_vote::<Sha256Digest>(
408            NAMESPACE,
409            VoteContext::Nullify {
410                round: proposal.round,
411            },
412            &vote
413        ));
414
415        let vote = scheme
416            .sign_vote(
417                NAMESPACE,
418                VoteContext::Finalize {
419                    proposal: &proposal,
420                },
421            )
422            .unwrap();
423        assert!(scheme.verify_vote(
424            NAMESPACE,
425            VoteContext::Finalize {
426                proposal: &proposal,
427            },
428            &vote
429        ));
430    }
431
432    #[test]
433    fn test_verify_votes_filters_bad_signers() {
434        let (schemes, _) = setup_signers(5, 42);
435        let quorum = quorum(schemes.len() as u32) as usize;
436        let proposal = sample_proposal(0, 5, 3);
437
438        let mut votes: Vec<_> = schemes
439            .iter()
440            .take(quorum)
441            .map(|scheme| {
442                scheme
443                    .sign_vote(
444                        NAMESPACE,
445                        VoteContext::Notarize {
446                            proposal: &proposal,
447                        },
448                    )
449                    .unwrap()
450            })
451            .collect();
452
453        let scheme = &schemes[0];
454        let verification = scheme.verify_votes(
455            &mut thread_rng(),
456            NAMESPACE,
457            VoteContext::Notarize {
458                proposal: &proposal,
459            },
460            votes.clone(),
461        );
462        assert!(verification.invalid_signers.is_empty());
463        assert_eq!(verification.verified.len(), quorum);
464
465        // Invalid signer index should be detected.
466        votes[0].signer = 999;
467        let verification = scheme.verify_votes(
468            &mut thread_rng(),
469            NAMESPACE,
470            VoteContext::Notarize {
471                proposal: &proposal,
472            },
473            votes.clone(),
474        );
475        assert_eq!(verification.invalid_signers, vec![999]);
476        assert_eq!(verification.verified.len(), quorum - 1);
477
478        // Invalid signature should be detected.
479        votes[0].signer = 0;
480        votes[0].signature = votes[1].signature.clone();
481        let verification = scheme.verify_votes(
482            &mut thread_rng(),
483            NAMESPACE,
484            VoteContext::Notarize {
485                proposal: &proposal,
486            },
487            votes,
488        );
489        assert_eq!(verification.invalid_signers, vec![0]);
490        assert_eq!(verification.verified.len(), quorum - 1);
491    }
492
493    #[test]
494    fn test_assemble_certificate_sorts_signers() {
495        let (schemes, _) = setup_signers(4, 42);
496        let proposal = sample_proposal(0, 7, 4);
497
498        let votes = [
499            schemes[2]
500                .sign_vote(
501                    NAMESPACE,
502                    VoteContext::Finalize {
503                        proposal: &proposal,
504                    },
505                )
506                .unwrap(),
507            schemes[0]
508                .sign_vote(
509                    NAMESPACE,
510                    VoteContext::Finalize {
511                        proposal: &proposal,
512                    },
513                )
514                .unwrap(),
515            schemes[1]
516                .sign_vote(
517                    NAMESPACE,
518                    VoteContext::Finalize {
519                        proposal: &proposal,
520                    },
521                )
522                .unwrap(),
523        ];
524
525        let certificate = schemes[0]
526            .assemble_certificate(votes)
527            .expect("assemble certificate");
528        assert_eq!(
529            certificate.signers.iter().collect::<Vec<_>>(),
530            vec![0, 1, 2]
531        );
532    }
533
534    #[test]
535    fn test_assemble_certificate_requires_quorum() {
536        let (schemes, _) = setup_signers(4, 42);
537        let proposal = sample_proposal(0, 9, 5);
538
539        let votes: Vec<_> = schemes
540            .iter()
541            .take(2)
542            .map(|scheme| {
543                scheme
544                    .sign_vote(
545                        NAMESPACE,
546                        VoteContext::Notarize {
547                            proposal: &proposal,
548                        },
549                    )
550                    .unwrap()
551            })
552            .collect();
553
554        assert!(schemes[0].assemble_certificate(votes).is_none());
555    }
556
557    #[test]
558    fn test_assemble_certificate_rejects_out_of_range_signer() {
559        let (schemes, _) = setup_signers(4, 42);
560        let proposal = sample_proposal(0, 13, 7);
561
562        let mut votes: Vec<_> = schemes
563            .iter()
564            .take(3)
565            .map(|scheme| {
566                scheme
567                    .sign_vote(
568                        NAMESPACE,
569                        VoteContext::Notarize {
570                            proposal: &proposal,
571                        },
572                    )
573                    .unwrap()
574            })
575            .collect();
576        votes[0].signer = 42;
577
578        assert!(schemes[0].assemble_certificate(votes).is_none());
579    }
580
581    #[test]
582    #[should_panic(expected = "duplicate signer index: 2")]
583    fn test_assemble_certificate_rejects_duplicate_signers() {
584        let (schemes, _) = setup_signers(4, 42);
585        let proposal = sample_proposal(0, 25, 13);
586
587        let mut votes: Vec<_> = schemes
588            .iter()
589            .take(3)
590            .map(|scheme| {
591                scheme
592                    .sign_vote(
593                        NAMESPACE,
594                        VoteContext::Finalize {
595                            proposal: &proposal,
596                        },
597                    )
598                    .unwrap()
599            })
600            .collect();
601
602        votes.push(votes.last().unwrap().clone());
603
604        schemes[0].assemble_certificate(votes);
605    }
606
607    #[test]
608    fn test_verify_certificate_detects_corruption() {
609        let (schemes, participants) = setup_signers(4, 42);
610        let proposal = sample_proposal(0, 15, 8);
611
612        let votes: Vec<_> = schemes
613            .iter()
614            .take(3)
615            .map(|scheme| {
616                scheme
617                    .sign_vote(
618                        NAMESPACE,
619                        VoteContext::Finalize {
620                            proposal: &proposal,
621                        },
622                    )
623                    .unwrap()
624            })
625            .collect();
626
627        let certificate = schemes[0]
628            .assemble_certificate(votes)
629            .expect("assemble certificate");
630
631        let verifier = Scheme::verifier(participants);
632        assert!(verifier.verify_certificate(
633            &mut thread_rng(),
634            NAMESPACE,
635            VoteContext::Finalize {
636                proposal: &proposal,
637            },
638            &certificate,
639        ));
640
641        let mut corrupted = certificate.clone();
642        corrupted.signatures[0] = corrupted.signatures[1].clone();
643        assert!(!verifier.verify_certificate(
644            &mut thread_rng(),
645            NAMESPACE,
646            VoteContext::Finalize {
647                proposal: &proposal,
648            },
649            &corrupted,
650        ));
651    }
652
653    #[test]
654    fn test_certificate_codec_roundtrip() {
655        let (schemes, _) = setup_signers(4, 42);
656        let proposal = sample_proposal(0, 17, 9);
657
658        let votes: Vec<_> = schemes
659            .iter()
660            .take(3)
661            .map(|scheme| {
662                scheme
663                    .sign_vote(
664                        NAMESPACE,
665                        VoteContext::Notarize {
666                            proposal: &proposal,
667                        },
668                    )
669                    .unwrap()
670            })
671            .collect();
672
673        let certificate = schemes[0]
674            .assemble_certificate(votes)
675            .expect("assemble certificate");
676        let encoded = certificate.encode();
677        let decoded = Certificate::decode_cfg(encoded, &schemes.len()).expect("decode certificate");
678        assert_eq!(decoded, certificate);
679    }
680
681    #[test]
682    fn test_scheme_clone_and_verifier() {
683        let (schemes, participants) = setup_signers(4, 42);
684        let signer = schemes[0].clone();
685        let proposal = sample_proposal(0, 21, 11);
686
687        assert!(
688            signer
689                .sign_vote(
690                    NAMESPACE,
691                    VoteContext::Notarize {
692                        proposal: &proposal,
693                    },
694                )
695                .is_some(),
696            "signer should produce votes"
697        );
698
699        let verifier = Scheme::verifier(participants);
700        assert!(
701            verifier
702                .sign_vote(
703                    NAMESPACE,
704                    VoteContext::Notarize {
705                        proposal: &proposal,
706                    },
707                )
708                .is_none(),
709            "verifier should not produce votes"
710        );
711    }
712
713    #[test]
714    fn test_certificate_decode_validation() {
715        let (schemes, participants) = setup_signers(4, 42);
716        let proposal = sample_proposal(0, 19, 10);
717
718        let votes: Vec<_> = schemes
719            .iter()
720            .take(3)
721            .map(|scheme| {
722                scheme
723                    .sign_vote(
724                        NAMESPACE,
725                        VoteContext::Notarize {
726                            proposal: &proposal,
727                        },
728                    )
729                    .unwrap()
730            })
731            .collect();
732
733        let certificate = schemes[0]
734            .assemble_certificate(votes)
735            .expect("assemble certificate");
736
737        // Well-formed certificate decodes successfully.
738        let encoded = certificate.encode();
739        let mut cursor = &encoded[..];
740        let decoded =
741            Certificate::read_cfg(&mut cursor, &participants.len()).expect("decode certificate");
742        assert_eq!(decoded, certificate);
743
744        // Certificate with no signers is rejected.
745        let empty = Certificate {
746            signers: Signers::from(participants.len(), std::iter::empty::<u32>()),
747            signatures: Vec::new(),
748        };
749        assert!(Certificate::decode_cfg(empty.encode(), &participants.len()).is_err());
750
751        // Certificate with mismatched signature count is rejected.
752        let mismatched = Certificate {
753            signers: Signers::from(participants.len(), [0u32, 1]),
754            signatures: vec![certificate.signatures[0].clone()],
755        };
756        assert!(Certificate::decode_cfg(mismatched.encode(), &participants.len()).is_err());
757
758        // Certificate containing more signers than the participant set is rejected.
759        let mut signers = certificate.signers.iter().collect::<Vec<_>>();
760        signers.push(participants.len() as u32);
761        let mut signatures = certificate.signatures.clone();
762        signatures.push(certificate.signatures[0].clone());
763        let extended = Certificate {
764            signers: Signers::from(participants.len() + 1, signers),
765            signatures,
766        };
767        assert!(Certificate::decode_cfg(extended.encode(), &participants.len()).is_err());
768    }
769
770    #[test]
771    fn test_verify_certificate() {
772        let (schemes, participants) = setup_signers(4, 42);
773        let proposal = sample_proposal(0, 21, 11);
774
775        let votes: Vec<_> = schemes
776            .iter()
777            .take(quorum(schemes.len() as u32) as usize)
778            .map(|scheme| {
779                scheme
780                    .sign_vote(
781                        NAMESPACE,
782                        VoteContext::Finalize {
783                            proposal: &proposal,
784                        },
785                    )
786                    .unwrap()
787            })
788            .collect();
789
790        let certificate = schemes[0]
791            .assemble_certificate(votes)
792            .expect("assemble certificate");
793
794        let verifier = Scheme::verifier(participants);
795        assert!(verifier.verify_certificate(
796            &mut OsRng,
797            NAMESPACE,
798            VoteContext::Finalize {
799                proposal: &proposal,
800            },
801            &certificate,
802        ));
803    }
804
805    #[test]
806    fn test_verify_certificate_rejects_sub_quorum() {
807        let (schemes, participants) = setup_signers(4, 42);
808        let proposal = sample_proposal(0, 23, 12);
809
810        let votes: Vec<_> = schemes
811            .iter()
812            .take(3)
813            .map(|scheme| {
814                scheme
815                    .sign_vote(
816                        NAMESPACE,
817                        VoteContext::Finalize {
818                            proposal: &proposal,
819                        },
820                    )
821                    .unwrap()
822            })
823            .collect();
824
825        let certificate = schemes[0]
826            .assemble_certificate(votes)
827            .expect("assemble certificate");
828
829        let mut truncated = certificate.clone();
830        let mut signers: Vec<u32> = truncated.signers.iter().collect();
831        signers.pop();
832        truncated.signers = Signers::from(participants.len(), signers);
833        truncated.signatures.pop();
834
835        let verifier = Scheme::verifier(participants);
836        assert!(!verifier.verify_certificate(
837            &mut thread_rng(),
838            NAMESPACE,
839            VoteContext::Finalize {
840                proposal: &proposal,
841            },
842            &truncated,
843        ));
844    }
845
846    #[test]
847    fn test_verify_certificate_rejects_unknown_signer() {
848        let (schemes, participants) = setup_signers(4, 42);
849        let proposal = sample_proposal(0, 25, 13);
850
851        let votes: Vec<_> = schemes
852            .iter()
853            .take(3)
854            .map(|scheme| {
855                scheme
856                    .sign_vote(
857                        NAMESPACE,
858                        VoteContext::Finalize {
859                            proposal: &proposal,
860                        },
861                    )
862                    .unwrap()
863            })
864            .collect();
865
866        let mut certificate = schemes[0]
867            .assemble_certificate(votes)
868            .expect("assemble certificate");
869
870        let mut signers: Vec<u32> = certificate.signers.iter().collect();
871        signers.push(participants.len() as u32);
872        certificate.signers = Signers::from(participants.len() + 1, signers);
873        certificate
874            .signatures
875            .push(certificate.signatures[0].clone());
876
877        let verifier = Scheme::verifier(participants);
878        assert!(!verifier.verify_certificate(
879            &mut thread_rng(),
880            NAMESPACE,
881            VoteContext::Finalize {
882                proposal: &proposal,
883            },
884            &certificate,
885        ));
886    }
887
888    #[test]
889    fn test_verify_certificate_rejects_invalid_certificate_signers_size() {
890        let (schemes, participants) = setup_signers(4, 42);
891        let proposal = sample_proposal(0, 26, 14);
892
893        let votes: Vec<_> = schemes
894            .iter()
895            .take(3)
896            .map(|scheme| {
897                scheme
898                    .sign_vote(
899                        NAMESPACE,
900                        VoteContext::Finalize {
901                            proposal: &proposal,
902                        },
903                    )
904                    .unwrap()
905            })
906            .collect();
907
908        let mut certificate = schemes[0]
909            .assemble_certificate(votes)
910            .expect("assemble certificate");
911
912        // The certificate is valid
913        let verifier = Scheme::verifier(participants.clone());
914        assert!(verifier.verify_certificate(
915            &mut thread_rng(),
916            NAMESPACE,
917            VoteContext::Finalize {
918                proposal: &proposal,
919            },
920            &certificate,
921        ));
922
923        // Make the signers bitmap size smaller
924        let signers: Vec<u32> = certificate.signers.iter().collect();
925        certificate.signers = Signers::from(participants.len() - 1, signers);
926
927        // The certificate verification should fail
928        assert!(!verifier.verify_certificate(
929            &mut thread_rng(),
930            NAMESPACE,
931            VoteContext::Finalize {
932                proposal: &proposal,
933            },
934            &certificate,
935        ));
936    }
937
938    #[test]
939    fn test_verify_certificate_rejects_mismatched_signature_count() {
940        let (schemes, participants) = setup_signers(4, 42);
941        let proposal = sample_proposal(0, 27, 14);
942
943        let votes: Vec<_> = schemes
944            .iter()
945            .take(3)
946            .map(|scheme| {
947                scheme
948                    .sign_vote(
949                        NAMESPACE,
950                        VoteContext::Finalize {
951                            proposal: &proposal,
952                        },
953                    )
954                    .unwrap()
955            })
956            .collect();
957
958        let mut certificate = schemes[0]
959            .assemble_certificate(votes)
960            .expect("assemble certificate");
961        certificate.signatures.pop();
962
963        let verifier = Scheme::verifier(participants);
964        assert!(!verifier.verify_certificate(
965            &mut thread_rng(),
966            NAMESPACE,
967            VoteContext::Finalize {
968                proposal: &proposal,
969            },
970            &certificate,
971        ));
972    }
973
974    #[test]
975    fn test_verify_certificates_batch_detects_failure() {
976        let (schemes, participants) = setup_signers(4, 42);
977        let proposal_a = sample_proposal(0, 23, 12);
978        let proposal_b = sample_proposal(1, 24, 13);
979
980        let votes_a: Vec<_> = schemes
981            .iter()
982            .take(3)
983            .map(|scheme| {
984                scheme
985                    .sign_vote(
986                        NAMESPACE,
987                        VoteContext::Notarize {
988                            proposal: &proposal_a,
989                        },
990                    )
991                    .unwrap()
992            })
993            .collect();
994        let votes_b: Vec<_> = schemes
995            .iter()
996            .take(3)
997            .map(|scheme| {
998                scheme
999                    .sign_vote(
1000                        NAMESPACE,
1001                        VoteContext::Finalize {
1002                            proposal: &proposal_b,
1003                        },
1004                    )
1005                    .unwrap()
1006            })
1007            .collect();
1008
1009        let certificate_a = schemes[0]
1010            .assemble_certificate(votes_a)
1011            .expect("assemble certificate");
1012        let mut bad_certificate = schemes[0]
1013            .assemble_certificate(votes_b)
1014            .expect("assemble certificate");
1015        bad_certificate.signatures[0] = bad_certificate.signatures[1].clone();
1016
1017        let verifier = Scheme::verifier(participants);
1018        let mut iter = [
1019            (
1020                VoteContext::Notarize {
1021                    proposal: &proposal_a,
1022                },
1023                &certificate_a,
1024            ),
1025            (
1026                VoteContext::Finalize {
1027                    proposal: &proposal_b,
1028                },
1029                &bad_certificate,
1030            ),
1031        ]
1032        .into_iter();
1033
1034        assert!(!verifier.verify_certificates(&mut thread_rng(), NAMESPACE, &mut iter));
1035    }
1036}