1#[commonware_macros::stability(ALPHA)]
52use crate::simplex::scheme::seed_namespace;
53use crate::{
54 Epochable, Viewable,
55 simplex::{
56 scheme::Namespace,
57 types::{Finalization, Notarization, Subject},
58 },
59 types::{Epoch, Participant, Round, View},
60};
61use bytes::BufMut;
62use commonware_codec::{
63 Buf, Encode, EncodeSize, Error, FixedSize, Read, ReadExt, Write, types::lazy::Lazy,
64};
65#[commonware_macros::stability(ALPHA)]
66use commonware_cryptography::bls12381::tle;
67use commonware_cryptography::{
68 Digest, PublicKey,
69 bls12381::primitives::{
70 group::Share,
71 ops::{self, batch, threshold},
72 sharing::Sharing,
73 variant::{PartialSignature, Variant},
74 },
75 certificate::{
76 self, AssemblyError, Attestation, Membership, Signers, Subject as CertificateSubject,
77 Verification,
78 },
79};
80use commonware_macros::stability;
81use commonware_parallel::Strategy;
82use commonware_utils::{
83 N3f1,
84 iter::NonEmpty,
85 non_empty,
86 ordered::{Quorum, Set},
87};
88use rand::rngs::StdRng;
89use rand_core::{CryptoRng, SeedableRng};
90use std::{
91 collections::{BTreeSet, HashMap},
92 fmt::Debug,
93};
94
95#[derive(Clone, Debug)]
97enum Role<P: PublicKey, V: Variant> {
98 Signer {
99 participants: Set<P>,
101 polynomial: Sharing<V>,
103 share: Share,
105 namespace: Namespace,
107 },
108 Verifier {
109 participants: Set<P>,
111 polynomial: Sharing<V>,
113 index: Option<Participant>,
115 namespace: Namespace,
117 },
118 CertificateVerifier {
119 identity: V::Public,
121 namespace: Namespace,
123 },
124}
125
126#[derive(Clone, Debug)]
135pub struct Scheme<P: PublicKey, V: Variant> {
136 role: Role<P, V>,
137}
138
139impl<P: PublicKey, V: Variant> Scheme<P, V> {
140 pub fn signer(
158 namespace: &[u8],
159 participants: Set<P>,
160 polynomial: Sharing<V>,
161 share: Share,
162 ) -> Option<Self> {
163 assert_eq!(
164 polynomial.total().get() as usize,
165 participants.len(),
166 "polynomial total must equal participant len"
167 );
168 assert_eq!(
169 polynomial.required(),
170 participants.quorum::<N3f1>(),
171 "polynomial threshold must equal quorum"
172 );
173 polynomial.precompute_partial_publics();
174 let partial_public = polynomial
175 .partial_public(share.index)
176 .expect("share index must match participant indices");
177 if partial_public == share.public::<V>() {
178 Some(Self {
179 role: Role::Signer {
180 participants,
181 polynomial,
182 share,
183 namespace: Namespace::new(namespace),
184 },
185 })
186 } else {
187 None
188 }
189 }
190
191 pub fn verifier(namespace: &[u8], participants: Set<P>, polynomial: Sharing<V>) -> Self {
209 Self::verifier_with_index(namespace, participants, polynomial, None)
210 }
211
212 pub fn member(
224 namespace: &[u8],
225 participants: Set<P>,
226 polynomial: Sharing<V>,
227 public_key: &P,
228 ) -> Option<Self> {
229 let index = participants.index(public_key)?;
230 Some(Self::verifier_with_index(
231 namespace,
232 participants,
233 polynomial,
234 Some(index),
235 ))
236 }
237
238 fn verifier_with_index(
241 namespace: &[u8],
242 participants: Set<P>,
243 polynomial: Sharing<V>,
244 index: Option<Participant>,
245 ) -> Self {
246 assert_eq!(
247 polynomial.total().get() as usize,
248 participants.len(),
249 "polynomial total must equal participant len"
250 );
251 assert_eq!(
252 polynomial.required(),
253 participants.quorum::<N3f1>(),
254 "polynomial threshold must equal quorum"
255 );
256 polynomial.precompute_partial_publics();
257
258 Self {
259 role: Role::Verifier {
260 participants,
261 polynomial,
262 index,
263 namespace: Namespace::new(namespace),
264 },
265 }
266 }
267
268 pub fn certificate_verifier(namespace: &[u8], identity: V::Public) -> Self {
276 Self {
277 role: Role::CertificateVerifier {
278 identity,
279 namespace: Namespace::new(namespace),
280 },
281 }
282 }
283
284 pub fn participants(&self) -> &Set<P> {
286 match &self.role {
287 Role::Signer { participants, .. } => participants,
288 Role::Verifier { participants, .. } => participants,
289 Role::CertificateVerifier { .. } => {
290 panic!("can only be called for signer and verifier")
291 }
292 }
293 }
294
295 pub fn identity(&self) -> &V::Public {
297 match &self.role {
298 Role::Signer { polynomial, .. } => polynomial.public(),
299 Role::Verifier { polynomial, .. } => polynomial.public(),
300 Role::CertificateVerifier { identity, .. } => identity,
301 }
302 }
303
304 pub const fn share(&self) -> Option<&Share> {
306 match &self.role {
307 Role::Signer { share, .. } => Some(share),
308 _ => None,
309 }
310 }
311
312 pub fn polynomial(&self) -> &Sharing<V> {
314 match &self.role {
315 Role::Signer { polynomial, .. } => polynomial,
316 Role::Verifier { polynomial, .. } => polynomial,
317 Role::CertificateVerifier { .. } => {
318 panic!("can only be called for signer and verifier")
319 }
320 }
321 }
322
323 const fn namespace(&self) -> &Namespace {
325 match &self.role {
326 Role::Signer { namespace, .. } => namespace,
327 Role::Verifier { namespace, .. } => namespace,
328 Role::CertificateVerifier { namespace, .. } => namespace,
329 }
330 }
331
332 #[stability(ALPHA)]
340 pub fn encrypt<R: CryptoRng>(
341 &self,
342 rng: &mut R,
343 target: Round,
344 message: impl Into<tle::Block>,
345 ) -> Result<tle::Ciphertext<V>, tle::Error> {
346 let block = message.into();
347 let target_message = target.encode();
348 tle::encrypt(
349 rng,
350 *self.identity(),
351 (&self.namespace().seed, &target_message),
352 &block,
353 )
354 }
355}
356
357#[stability(ALPHA)]
365pub fn encrypt<R: CryptoRng, V: Variant>(
366 rng: &mut R,
367 identity: V::Public,
368 namespace: &[u8],
369 target: Round,
370 message: impl Into<tle::Block>,
371) -> Result<tle::Ciphertext<V>, tle::Error> {
372 let block = message.into();
373 let seed_ns = seed_namespace(namespace);
374 let target_message = target.encode();
375 tle::encrypt(rng, identity, (&seed_ns, &target_message), &block)
376}
377
378#[cfg(feature = "mocks")]
383pub fn fixture<V, R>(
384 rng: &mut R,
385 namespace: &[u8],
386 n: u32,
387) -> commonware_cryptography::certificate::mocks::Fixture<
388 Scheme<commonware_cryptography::ed25519::PublicKey, V>,
389>
390where
391 V: Variant,
392 R: rand_core::CryptoRng,
393{
394 commonware_cryptography::bls12381::certificate::threshold::mocks::fixture::<_, V, _>(
395 rng,
396 namespace,
397 n,
398 |namespace, participants, polynomial, share| {
399 Scheme::signer(namespace, participants, polynomial, share)
400 },
401 |namespace, participants, polynomial| Scheme::verifier(namespace, participants, polynomial),
402 )
403}
404
405#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
407pub struct Signature<V: Variant> {
408 pub vote_signature: V::Signature,
410 pub seed_signature: V::Signature,
412}
413
414impl<V: Variant> Write for Signature<V> {
415 fn write(&self, writer: &mut impl BufMut) {
416 self.vote_signature.write(writer);
417 self.seed_signature.write(writer);
418 }
419}
420
421impl<V: Variant> Read for Signature<V> {
422 type Cfg = ();
423
424 fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
425 let vote_signature = V::Signature::read(reader)?;
426 let seed_signature = V::Signature::read(reader)?;
427
428 Ok(Self {
429 vote_signature,
430 seed_signature,
431 })
432 }
433}
434
435impl<V: Variant> FixedSize for Signature<V> {
436 const SIZE: usize = V::Signature::SIZE * 2;
437}
438
439#[cfg(feature = "arbitrary")]
440impl<V: Variant> arbitrary::Arbitrary<'_> for Signature<V>
441where
442 V::Signature: for<'a> arbitrary::Arbitrary<'a>,
443{
444 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
445 Ok(Self {
446 vote_signature: u.arbitrary()?,
447 seed_signature: u.arbitrary()?,
448 })
449 }
450}
451
452#[derive(Clone, Debug, PartialEq, Eq, Hash)]
454pub struct Certificate<V: Variant> {
455 pub signature: Lazy<Signature<V>>,
457}
458
459impl<V: Variant> Certificate<V> {
460 pub fn get(&self) -> Option<&Signature<V>> {
464 self.signature.get()
465 }
466}
467
468impl<V: Variant> From<Signature<V>> for Certificate<V> {
469 fn from(signature: Signature<V>) -> Self {
470 Self {
471 signature: Lazy::from(signature),
472 }
473 }
474}
475
476impl<V: Variant> Write for Certificate<V> {
477 fn write(&self, writer: &mut impl BufMut) {
478 self.signature.write(writer);
479 }
480}
481
482impl<V: Variant> Read for Certificate<V> {
483 type Cfg = ();
484
485 fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
486 let signature = Lazy::<Signature<V>>::read(reader)?;
487 Ok(Self { signature })
488 }
489}
490
491impl<V: Variant> FixedSize for Certificate<V> {
492 const SIZE: usize = Signature::<V>::SIZE;
493}
494
495#[cfg(feature = "arbitrary")]
496impl<V: Variant> arbitrary::Arbitrary<'_> for Certificate<V>
497where
498 V::Signature: for<'a> arbitrary::Arbitrary<'a>,
499{
500 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
501 Ok(Self {
502 signature: Lazy::from(u.arbitrary::<Signature<V>>()?),
503 })
504 }
505}
506
507#[derive(Clone, Debug, PartialEq, Hash, Eq)]
509pub struct Seed<V: Variant> {
510 pub round: Round,
512 pub signature: V::Signature,
514}
515
516impl<V: Variant> Seed<V> {
517 pub const fn new(round: Round, signature: V::Signature) -> Self {
519 Self { round, signature }
520 }
521
522 pub fn verify<P: PublicKey>(&self, scheme: &Scheme<P, V>) -> bool {
524 let seed_message = self.round.encode();
525
526 ops::verify_message::<V>(
527 scheme.identity(),
528 &scheme.namespace().seed,
529 &seed_message,
530 &self.signature,
531 )
532 .is_ok()
533 }
534
535 pub const fn round(&self) -> Round {
537 self.round
538 }
539
540 #[stability(ALPHA)]
545 pub fn decrypt(&self, ciphertext: &tle::Ciphertext<V>) -> Option<tle::Block> {
546 decrypt(self, ciphertext)
547 }
548}
549
550#[stability(ALPHA)]
555pub fn decrypt<V: Variant>(seed: &Seed<V>, ciphertext: &tle::Ciphertext<V>) -> Option<tle::Block> {
556 tle::decrypt(&seed.signature, ciphertext)
557}
558
559impl<V: Variant> Epochable for Seed<V> {
560 fn epoch(&self) -> Epoch {
561 self.round.epoch()
562 }
563}
564
565impl<V: Variant> Viewable for Seed<V> {
566 fn view(&self) -> View {
567 self.round.view()
568 }
569}
570
571impl<V: Variant> Write for Seed<V> {
572 fn write(&self, writer: &mut impl BufMut) {
573 self.round.write(writer);
574 self.signature.write(writer);
575 }
576}
577
578impl<V: Variant> Read for Seed<V> {
579 type Cfg = ();
580
581 fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
582 let round = Round::read(reader)?;
583 let signature = V::Signature::read(reader)?;
584
585 Ok(Self { round, signature })
586 }
587}
588
589impl<V: Variant> EncodeSize for Seed<V> {
590 fn encode_size(&self) -> usize {
591 self.round.encode_size() + self.signature.encode_size()
592 }
593}
594
595#[cfg(feature = "arbitrary")]
596impl<V: Variant> arbitrary::Arbitrary<'_> for Seed<V>
597where
598 V::Signature: for<'a> arbitrary::Arbitrary<'a>,
599{
600 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
601 Ok(Self {
602 round: u.arbitrary()?,
603 signature: u.arbitrary()?,
604 })
605 }
606}
607
608pub trait Seedable<V: Variant> {
610 fn seed(&self) -> Seed<V>;
612}
613
614impl<P: PublicKey, V: Variant, D: Digest> Seedable<V> for Notarization<Scheme<P, V>, D> {
615 fn seed(&self) -> Seed<V> {
621 let cert = self
622 .certificate
623 .get()
624 .expect("verified certificate must decode");
625 Seed::new(self.proposal.round, cert.seed_signature)
626 }
627}
628
629impl<P: PublicKey, V: Variant, D: Digest> Seedable<V> for Finalization<Scheme<P, V>, D> {
630 fn seed(&self) -> Seed<V> {
636 let cert = self
637 .certificate
638 .get()
639 .expect("verified certificate must decode");
640 Seed::new(self.proposal.round, cert.seed_signature)
641 }
642}
643
644fn seed_message_from_subject<D: Digest>(subject: &Subject<'_, D>) -> bytes::Bytes {
648 match subject {
649 Subject::Notarize { proposal } | Subject::Finalize { proposal } => proposal.round.encode(),
650 Subject::Nullify { round } => round.encode(),
651 }
652}
653
654impl<P: PublicKey, V: Variant> certificate::Verifier for Scheme<P, V> {
655 type Subject<'a, D: Digest> = Subject<'a, D>;
656 type Faults = N3f1;
657 type PublicKey = P;
658 type Certificate = Certificate<V>;
659
660 fn verify_certificate<R, D>(
661 &self,
662 rng: &mut R,
663 subject: Subject<'_, D>,
664 certificate: &Self::Certificate,
665 strategy: &impl Strategy,
666 ) -> bool
667 where
668 R: CryptoRng,
669 D: Digest,
670 {
671 let Some(cert) = certificate.get() else {
672 return false;
673 };
674
675 let identity = self.identity();
676 let namespace = self.namespace();
677
678 let vote_namespace = subject.namespace(namespace);
679 let vote_message = subject.message();
680 let seed_message = seed_message_from_subject(&subject);
681
682 let entries = non_empty![
683 (vote_namespace, vote_message.as_ref(), cert.vote_signature),
684 (
685 namespace.seed.as_ref(),
686 seed_message.as_ref(),
687 cert.seed_signature,
688 ),
689 ];
690 batch::verify_same_signer::<_, V, _>(rng, identity, entries, strategy).is_ok()
691 }
692
693 fn verify_certificates<'a, R, D, I>(
694 &self,
695 rng: &mut R,
696 certificates: NonEmpty<I>,
697 strategy: &impl Strategy,
698 ) -> bool
699 where
700 R: CryptoRng,
701 D: Digest,
702 I: Iterator<Item = (Subject<'a, D>, &'a Self::Certificate)>,
703 {
704 let identity = self.identity();
705 let namespace = self.namespace();
706
707 let mut seeds = HashMap::new();
708 let mut entries: Vec<_> = Vec::new();
709
710 for (context, certificate) in certificates {
711 let Some(cert) = certificate.get() else {
712 return false;
713 };
714
715 let vote_namespace = context.namespace(namespace);
717 let vote_message = context.message();
718 entries.push((vote_namespace, vote_message, cert.vote_signature));
719
720 let seed_message = seed_message_from_subject(&context);
725 if let Some(previous) = seeds.get(&seed_message) {
726 if *previous != cert.seed_signature {
727 return false;
728 }
729 } else {
730 entries.push((&namespace.seed, seed_message.clone(), cert.seed_signature));
731 seeds.insert(seed_message, cert.seed_signature);
732 }
733 }
734
735 batch::verify_same_signer::<_, V, _>(
738 rng,
739 identity,
740 non_empty![@entries
741 .iter()
742 .map(|(ns, msg, sig)| (*ns, msg.as_ref(), *sig))],
743 strategy,
744 )
745 .is_ok()
746 }
747
748 fn is_batchable() -> bool {
749 true
750 }
751
752 fn max_certificate_size(_participants: u32) -> usize {
753 Certificate::<V>::SIZE
754 }
755
756 fn certificate_codec_config(&self) -> <Self::Certificate as Read>::Cfg {}
757
758 fn certificate_codec_config_unbounded() -> <Self::Certificate as Read>::Cfg {}
759}
760
761impl<P: PublicKey, V: Variant> certificate::Scheme for Scheme<P, V> {
762 type Signature = Signature<V>;
763
764 fn me(&self) -> Option<Membership> {
765 match &self.role {
766 Role::Signer { share, .. } => Some(Membership::Signer(share.index)),
767 Role::Verifier { index, .. } => index.map(Membership::Member),
768 Role::CertificateVerifier { .. } => None,
769 }
770 }
771
772 fn participants(&self) -> &Set<Self::PublicKey> {
773 self.participants()
774 }
775
776 fn sign<D: Digest>(&self, subject: Subject<'_, D>) -> Option<Attestation<Self>> {
777 let share = self.share()?;
778
779 let namespace = self.namespace();
780 let vote_namespace = subject.namespace(namespace);
781 let vote_message = subject.message();
782 let vote_signature =
783 threshold::sign_message::<V>(share, vote_namespace, &vote_message).value;
784
785 let seed_message = seed_message_from_subject(&subject);
786 let seed_signature =
787 threshold::sign_message::<V>(share, &namespace.seed, &seed_message).value;
788
789 let signature = Signature {
790 vote_signature,
791 seed_signature,
792 };
793
794 Some(Attestation {
795 signer: share.index,
796 signature: signature.into(),
797 })
798 }
799
800 fn verify_attestation<R, D>(
801 &self,
802 rng: &mut R,
803 subject: Subject<'_, D>,
804 attestation: &Attestation<Self>,
805 strategy: &impl Strategy,
806 ) -> bool
807 where
808 R: CryptoRng,
809 D: Digest,
810 {
811 let Ok(evaluated) = self.polynomial().partial_public(attestation.signer) else {
812 return false;
813 };
814
815 let namespace = self.namespace();
816 let vote_namespace = subject.namespace(namespace);
817 let vote_message = subject.message();
818 let seed_message = seed_message_from_subject(&subject);
819
820 let Some(signature) = attestation.signature.get() else {
821 return false;
822 };
823
824 let entries = non_empty![
825 (
826 vote_namespace,
827 vote_message.as_ref(),
828 signature.vote_signature,
829 ),
830 (
831 namespace.seed.as_ref(),
832 seed_message.as_ref(),
833 signature.seed_signature,
834 ),
835 ];
836 batch::verify_same_signer::<_, V, _>(rng, &evaluated, entries, strategy).is_ok()
837 }
838
839 fn verify_attestations<R, D, I>(
840 &self,
841 rng: &mut R,
842 subject: Subject<'_, D>,
843 attestations: I,
844 strategy: &impl Strategy,
845 ) -> Verification<Self>
846 where
847 R: CryptoRng,
848 D: Digest,
849 I: IntoIterator<Item = Attestation<Self>>,
850 I::IntoIter: Send,
851 {
852 let namespace = self.namespace();
853 let (partials, failures) =
854 strategy.map_partition_collect_vec(attestations.into_iter(), |attestation| {
855 let index = attestation.signer;
856 let value = attestation.signature.get().map(|sig| {
857 (
858 PartialSignature::<V> {
859 index,
860 value: sig.vote_signature,
861 },
862 PartialSignature::<V> {
863 index,
864 value: sig.seed_signature,
865 },
866 )
867 });
868 (index, value)
869 });
870
871 let polynomial = self.polynomial();
872 let vote_namespace = subject.namespace(namespace);
873 let vote_message = subject.message();
874 let seed_message = seed_message_from_subject(&subject);
875
876 let mut invalid: BTreeSet<_> = failures.into_iter().collect();
880 if partials.is_empty() {
881 return Verification::new(Vec::new(), invalid.into_iter().collect());
882 }
883 let vote_partials = non_empty![@partials.iter().map(|(vote, _)| vote)];
884 let seed_partials = non_empty![@partials.iter().map(|(_, seed)| seed)];
885
886 let mut vote_rng_seed = [0u8; 32];
888 let mut seed_rng_seed = [0u8; 32];
889 rng.fill_bytes(&mut vote_rng_seed);
890 rng.fill_bytes(&mut seed_rng_seed);
891
892 let (vote_invalid, seed_invalid) = strategy.join(
894 || {
895 let mut vote_rng = StdRng::from_seed(vote_rng_seed);
896 match threshold::batch_verify_same_message::<_, V, _>(
897 &mut vote_rng,
898 polynomial,
899 vote_namespace,
900 &vote_message,
901 vote_partials,
902 strategy,
903 ) {
904 Ok(()) => BTreeSet::new(),
905 Err(errs) => errs.into_iter().map(|p| p.index).collect(),
906 }
907 },
908 || {
909 let mut seed_rng = StdRng::from_seed(seed_rng_seed);
910 match threshold::batch_verify_same_message::<_, V, _>(
911 &mut seed_rng,
912 polynomial,
913 &namespace.seed,
914 &seed_message,
915 seed_partials,
916 strategy,
917 ) {
918 Ok(()) => BTreeSet::new(),
919 Err(errs) => errs.into_iter().map(|p| p.index).collect(),
920 }
921 },
922 );
923
924 invalid.extend(vote_invalid.union(&seed_invalid).copied());
926
927 let verified = partials
929 .into_iter()
930 .filter(|(vote, _)| !invalid.contains(&vote.index))
931 .map(|(vote, seed)| Attestation {
932 signer: vote.index,
933 signature: Signature {
934 vote_signature: vote.value,
935 seed_signature: seed.value,
936 }
937 .into(),
938 })
939 .collect();
940
941 Verification::new(verified, invalid.into_iter().collect())
942 }
943
944 fn optimistic_assemble<'a, R, D, I, J>(
945 &self,
946 rng: &mut R,
947 subject: Self::Subject<'_, D>,
948 pending: I,
949 verified: J,
950 strategy: &impl Strategy,
951 ) -> Result<Self::Certificate, Verification<Self>>
952 where
953 R: CryptoRng,
954 D: Digest,
955 I: IntoIterator<Item = Attestation<Self>>,
956 I::IntoIter: ExactSizeIterator + Send,
957 J: IntoIterator<Item = &'a Attestation<Self>>,
958 J::IntoIter: Send,
959 {
960 certificate::optimistic_assemble::<Self, _, D, _, _>(
961 self, rng, subject, pending, verified, strategy,
962 )
963 }
964
965 fn assemble<I>(
966 &self,
967 attestations: NonEmpty<I>,
968 strategy: &impl Strategy,
969 ) -> Result<Self::Certificate, AssemblyError>
970 where
971 I: Iterator<Item = Attestation<Self>> + Send,
972 {
973 let partials = strategy.try_map_collect_vec(attestations, |attestation| {
975 let index = attestation.signer;
976 attestation
977 .signature
978 .get()
979 .map(|sig| {
980 (
981 PartialSignature::<V> {
982 index,
983 value: sig.vote_signature,
984 },
985 PartialSignature::<V> {
986 index,
987 value: sig.seed_signature,
988 },
989 )
990 })
991 .ok_or(AssemblyError::MalformedSignature(index))
992 })?;
993
994 let quorum = self.polynomial();
996 Signers::try_from((quorum, partials.iter().map(|(vote, _)| vote.index)))?;
997
998 let (vote_partials, seed_partials): (Vec<_>, Vec<_>) = partials.into_iter().unzip();
1000 let (vote_signature, seed_signature) =
1001 threshold::recover_pair(quorum, vote_partials.iter(), seed_partials.iter(), strategy)
1002 .map_err(|_| AssemblyError::RecoveryFailed)?;
1003 Ok(Signature {
1004 vote_signature,
1005 seed_signature,
1006 }
1007 .into())
1008 }
1009
1010 fn is_attributable() -> bool {
1011 false
1012 }
1013}
1014
1015#[cfg(test)]
1016mod tests {
1017 use super::*;
1018 use crate::{
1019 simplex::{
1020 scheme::{notarize_namespace, seed_namespace},
1021 types::{Finalization, Finalize, Notarization, Notarize, Proposal, Subject},
1022 },
1023 types::{Round, View},
1024 };
1025 use commonware_codec::{Decode, Encode};
1026 use commonware_cryptography::{
1027 Hasher, Sha256, Signer as _,
1028 bls12381::{
1029 dkg::feldman_desmedt as dkg,
1030 primitives::{
1031 group::Scalar,
1032 ops::threshold,
1033 sharing::Mode,
1034 variant::{MinPk, MinSig, Variant},
1035 },
1036 },
1037 certificate::{Scheme as _, Verifier as _, mocks::Fixture},
1038 ed25519,
1039 ed25519::certificate::mocks::participants as ed25519_participants,
1040 sha256::Digest as Sha256Digest,
1041 };
1042 use commonware_math::algebra::{Additive, CryptoGroup, Random};
1043 use commonware_parallel::{Sequential, mocks::explore};
1044 use commonware_utils::{Faults, N3f1, N5f1, NZU32, Widen, test_rng};
1045 use rand::{SeedableRng, rngs::StdRng};
1046
1047 const NAMESPACE: &[u8] = b"bls-threshold-signing-scheme";
1048
1049 type Scheme<V> = super::Scheme<ed25519::PublicKey, V>;
1050 type Signature<V> = super::Signature<V>;
1051
1052 fn setup_signers<V: Variant>(n: u32, seed: u64) -> (Vec<Scheme<V>>, Scheme<V>) {
1053 let mut rng = StdRng::seed_from_u64(seed);
1054 let Fixture {
1055 schemes, verifier, ..
1056 } = fixture::<V, _>(&mut rng, NAMESPACE, n);
1057
1058 (schemes, verifier)
1059 }
1060
1061 fn sample_proposal(epoch: Epoch, view: View, tag: u8) -> Proposal<Sha256Digest> {
1062 Proposal::new(
1063 Round::new(epoch, view),
1064 view.previous().unwrap(),
1065 Sha256::hash(&[&[tag]]),
1066 )
1067 }
1068
1069 fn signer_shares_must_match_participant_indices<V: Variant>() {
1070 let mut rng = test_rng();
1071 let participants = ed25519_participants(&mut rng, 4);
1072 let (polynomial, mut shares) =
1073 dkg::deal_anonymous::<V, N3f1>(&mut rng, Mode::NonZeroCounter, NZU32!(4));
1074 shares[0].index = Participant::new(999);
1075 Scheme::<V>::signer(
1076 NAMESPACE,
1077 participants.keys().clone(),
1078 polynomial,
1079 shares[0].clone(),
1080 );
1081 }
1082
1083 #[test]
1084 #[should_panic(expected = "share index must match participant indices")]
1085 fn test_signer_shares_must_match_participant_indices_min_pk() {
1086 signer_shares_must_match_participant_indices::<MinPk>();
1087 }
1088
1089 #[test]
1090 #[should_panic(expected = "share index must match participant indices")]
1091 fn test_signer_shares_must_match_participant_indices_min_sig() {
1092 signer_shares_must_match_participant_indices::<MinSig>();
1093 }
1094 fn scheme_polynomial_threshold_must_equal_quorum<V: Variant>() {
1095 let mut rng = test_rng();
1096 let participants = ed25519_participants(&mut rng, 5);
1097 let (polynomial, shares) =
1098 dkg::deal_anonymous::<V, N3f1>(&mut rng, Mode::NonZeroCounter, NZU32!(4));
1099 Scheme::<V>::signer(
1100 NAMESPACE,
1101 participants.keys().clone(),
1102 polynomial,
1103 shares[0].clone(),
1104 );
1105 }
1106
1107 #[test]
1108 #[should_panic(expected = "polynomial total must equal participant len")]
1109 fn test_scheme_polynomial_threshold_must_equal_quorum_min_pk() {
1110 scheme_polynomial_threshold_must_equal_quorum::<MinPk>();
1111 }
1112
1113 #[test]
1114 #[should_panic(expected = "polynomial total must equal participant len")]
1115 fn test_scheme_polynomial_threshold_must_equal_quorum_min_sig() {
1116 scheme_polynomial_threshold_must_equal_quorum::<MinSig>();
1117 }
1118
1119 fn verifier_polynomial_threshold_must_equal_quorum<V: Variant>() {
1120 let mut rng = test_rng();
1121 let participants = ed25519_participants(&mut rng, 5);
1122 let (polynomial, _) =
1123 dkg::deal_anonymous::<V, N3f1>(&mut rng, Mode::NonZeroCounter, NZU32!(4));
1124 Scheme::<V>::verifier(NAMESPACE, participants.keys().clone(), polynomial);
1125 }
1126
1127 #[test]
1128 #[should_panic(expected = "polynomial total must equal participant len")]
1129 fn test_verifier_polynomial_threshold_must_equal_quorum_min_pk() {
1130 verifier_polynomial_threshold_must_equal_quorum::<MinPk>();
1131 }
1132
1133 #[test]
1134 #[should_panic(expected = "polynomial total must equal participant len")]
1135 fn test_verifier_polynomial_threshold_must_equal_quorum_min_sig() {
1136 verifier_polynomial_threshold_must_equal_quorum::<MinSig>();
1137 }
1138
1139 fn signer_validates_polynomial_degree<V: Variant>() {
1140 let mut rng = test_rng();
1141 let participants = ed25519_participants(&mut rng, 4);
1142
1143 let (polynomial, shares) =
1146 dkg::deal_anonymous::<V, N5f1>(&mut rng, Mode::NonZeroCounter, NZU32!(4));
1147
1148 Scheme::<V>::signer(
1149 NAMESPACE,
1150 participants.keys().clone(),
1151 polynomial,
1152 shares[0].clone(),
1153 );
1154 }
1155
1156 #[test]
1157 #[should_panic(expected = "polynomial threshold must equal quorum")]
1158 fn test_signer_validates_polynomial_degree_min_pk() {
1159 signer_validates_polynomial_degree::<MinPk>();
1160 }
1161
1162 #[test]
1163 #[should_panic(expected = "polynomial threshold must equal quorum")]
1164 fn test_signer_validates_polynomial_degree_min_sig() {
1165 signer_validates_polynomial_degree::<MinSig>();
1166 }
1167
1168 fn verifier_validates_polynomial_degree<V: Variant>() {
1169 let mut rng = test_rng();
1170 let participants = ed25519_participants(&mut rng, 4);
1171
1172 let (polynomial, _) =
1175 dkg::deal_anonymous::<V, N5f1>(&mut rng, Mode::NonZeroCounter, NZU32!(4));
1176
1177 Scheme::<V>::verifier(NAMESPACE, participants.keys().clone(), polynomial);
1178 }
1179
1180 #[test]
1181 #[should_panic(expected = "polynomial threshold must equal quorum")]
1182 fn test_verifier_validates_polynomial_degree_min_pk() {
1183 verifier_validates_polynomial_degree::<MinPk>();
1184 }
1185
1186 #[test]
1187 #[should_panic(expected = "polynomial threshold must equal quorum")]
1188 fn test_verifier_validates_polynomial_degree_min_sig() {
1189 verifier_validates_polynomial_degree::<MinSig>();
1190 }
1191
1192 #[test]
1193 fn test_is_not_attributable() {
1194 assert!(!Scheme::<MinPk>::is_attributable());
1195 assert!(!Scheme::<MinSig>::is_attributable());
1196 }
1197
1198 #[test]
1199 fn test_is_batchable() {
1200 assert!(Scheme::<MinPk>::is_batchable());
1201 assert!(Scheme::<MinSig>::is_batchable());
1202 }
1203
1204 fn sign_vote_roundtrip_for_each_context<V: Variant>() {
1205 let (schemes, _) = setup_signers::<V>(4, 7);
1206 let scheme = &schemes[0];
1207 let mut rng = test_rng();
1208
1209 let proposal = sample_proposal(Epoch::new(0), View::new(2), 1);
1210 let notarize_vote = scheme
1211 .sign(Subject::Notarize {
1212 proposal: &proposal,
1213 })
1214 .unwrap();
1215 assert!(scheme.verify_attestation::<_, Sha256Digest>(
1216 &mut rng,
1217 Subject::Notarize {
1218 proposal: &proposal,
1219 },
1220 ¬arize_vote,
1221 &Sequential,
1222 ));
1223
1224 let nullify_vote = scheme
1225 .sign::<Sha256Digest>(Subject::Nullify {
1226 round: proposal.round,
1227 })
1228 .unwrap();
1229 assert!(scheme.verify_attestation::<_, Sha256Digest>(
1230 &mut rng,
1231 Subject::Nullify {
1232 round: proposal.round,
1233 },
1234 &nullify_vote,
1235 &Sequential,
1236 ));
1237
1238 let finalize_vote = scheme
1239 .sign(Subject::Finalize {
1240 proposal: &proposal,
1241 })
1242 .unwrap();
1243 assert!(scheme.verify_attestation::<_, Sha256Digest>(
1244 &mut rng,
1245 Subject::Finalize {
1246 proposal: &proposal,
1247 },
1248 &finalize_vote,
1249 &Sequential,
1250 ));
1251 }
1252
1253 #[test]
1254 fn test_sign_vote_roundtrip_for_each_context() {
1255 sign_vote_roundtrip_for_each_context::<MinPk>();
1256 sign_vote_roundtrip_for_each_context::<MinSig>();
1257 }
1258
1259 fn verifier_cannot_sign<V: Variant>() {
1260 let (_, verifier) = setup_signers::<V>(4, 11);
1261
1262 let proposal = sample_proposal(Epoch::new(0), View::new(3), 2);
1263 assert!(
1264 verifier
1265 .sign(Subject::Notarize {
1266 proposal: &proposal,
1267 })
1268 .is_none(),
1269 "verifier should not produce signatures"
1270 );
1271 }
1272
1273 #[test]
1274 fn test_verifier_cannot_sign() {
1275 verifier_cannot_sign::<MinPk>();
1276 verifier_cannot_sign::<MinSig>();
1277 }
1278
1279 fn member_reports_membership_without_signing<V: Variant>() {
1282 let (schemes, verifier) = setup_signers::<V>(4, 12);
1283 let participants = verifier.participants().clone();
1284 let polynomial = verifier.polynomial().clone();
1285 let proposal = sample_proposal(Epoch::new(0), View::new(3), 2);
1286
1287 let outsider = ed25519::PrivateKey::from_seed(u64::MAX).public_key();
1289 assert!(
1290 Scheme::<V>::member(
1291 NAMESPACE,
1292 participants.clone(),
1293 polynomial.clone(),
1294 &outsider
1295 )
1296 .is_none()
1297 );
1298
1299 let index = Participant::new(2);
1302 let key = participants.key(index).unwrap().clone();
1303 let member = Scheme::<V>::member(NAMESPACE, participants, polynomial, &key).unwrap();
1304 assert_eq!(member.me(), Some(Membership::Member(index)));
1305 assert_eq!(member.me().unwrap().signer(), None);
1306 assert!(
1307 member
1308 .sign(Subject::Notarize {
1309 proposal: &proposal,
1310 })
1311 .is_none()
1312 );
1313 assert_eq!(verifier.me(), None);
1314 let certificate_verifier =
1315 Scheme::<V>::certificate_verifier(NAMESPACE, *verifier.identity());
1316 assert_eq!(certificate_verifier.me(), None);
1317 for scheme in &schemes {
1318 let Some(Membership::Signer(signer)) = scheme.me() else {
1319 panic!("signer must report signing membership");
1320 };
1321 let vote = scheme
1322 .sign(Subject::Notarize {
1323 proposal: &proposal,
1324 })
1325 .unwrap();
1326 assert_eq!(vote.signer, signer);
1327 }
1328
1329 let votes: Vec<_> = schemes
1331 .iter()
1332 .take(Widen::widen(N3f1::quorum(schemes.len())))
1333 .map(|scheme| {
1334 scheme
1335 .sign(Subject::Finalize {
1336 proposal: &proposal,
1337 })
1338 .unwrap()
1339 })
1340 .collect();
1341 let certificate = schemes[0]
1342 .assemble(non_empty![@votes], &Sequential)
1343 .unwrap();
1344 assert!(member.verify_certificate::<_, Sha256Digest>(
1345 &mut test_rng(),
1346 Subject::Finalize {
1347 proposal: &proposal,
1348 },
1349 &certificate,
1350 &Sequential,
1351 ));
1352 }
1353
1354 #[test]
1355 fn test_member_reports_membership_without_signing() {
1356 member_reports_membership_without_signing::<MinPk>();
1357 member_reports_membership_without_signing::<MinSig>();
1358 }
1359
1360 fn verifier_accepts_votes<V: Variant>() {
1361 let (schemes, verifier) = setup_signers::<V>(4, 11);
1362 let proposal = sample_proposal(Epoch::new(0), View::new(3), 2);
1363 let vote = schemes[1]
1364 .sign(Subject::Notarize {
1365 proposal: &proposal,
1366 })
1367 .unwrap();
1368 assert!(verifier.verify_attestation::<_, Sha256Digest>(
1369 &mut test_rng(),
1370 Subject::Notarize {
1371 proposal: &proposal,
1372 },
1373 &vote,
1374 &Sequential,
1375 ));
1376 }
1377
1378 #[test]
1379 fn test_verifier_accepts_votes() {
1380 verifier_accepts_votes::<MinPk>();
1381 verifier_accepts_votes::<MinSig>();
1382 }
1383
1384 fn verify_votes_filters_bad_signers<V: Variant>() {
1385 let mut rng = test_rng();
1386 let (schemes, _) = setup_signers::<V>(5, 13);
1387 let quorum = N3f1::quorum(schemes.len()) as usize;
1388 let proposal = sample_proposal(Epoch::new(0), View::new(5), 3);
1389
1390 let mut votes: Vec<_> = schemes
1391 .iter()
1392 .take(quorum)
1393 .map(|scheme| {
1394 scheme
1395 .sign(Subject::Notarize {
1396 proposal: &proposal,
1397 })
1398 .unwrap()
1399 })
1400 .collect();
1401
1402 let verification = schemes[0].verify_attestations(
1403 &mut rng,
1404 Subject::Notarize {
1405 proposal: &proposal,
1406 },
1407 votes.clone(),
1408 &Sequential,
1409 );
1410 assert!(verification.invalid.is_empty());
1411 assert_eq!(verification.verified.len(), quorum);
1412
1413 votes[0].signer = Participant::new(999);
1414 let verification = schemes[0].verify_attestations(
1415 &mut rng,
1416 Subject::Notarize {
1417 proposal: &proposal,
1418 },
1419 votes,
1420 &Sequential,
1421 );
1422 assert_eq!(verification.invalid, vec![Participant::new(999)]);
1423 assert_eq!(verification.verified.len(), quorum - 1);
1424 }
1425
1426 #[test]
1427 fn test_verify_votes_filters_bad_signers() {
1428 verify_votes_filters_bad_signers::<MinPk>();
1429 verify_votes_filters_bad_signers::<MinSig>();
1430 }
1431
1432 fn assemble_certificate_requires_quorum<V: Variant>() {
1433 let (schemes, _) = setup_signers::<V>(4, 17);
1434 let quorum = N3f1::quorum(schemes.len());
1435 let subquorum = usize::try_from(quorum - 1).expect("quorum exceeds usize::MAX");
1436 let proposal = sample_proposal(Epoch::new(0), View::new(7), 4);
1437
1438 let votes: Vec<_> = schemes
1439 .iter()
1440 .take(subquorum)
1441 .map(|scheme| {
1442 scheme
1443 .sign(Subject::Notarize {
1444 proposal: &proposal,
1445 })
1446 .unwrap()
1447 })
1448 .collect();
1449
1450 assert_eq!(
1451 schemes[0].assemble(non_empty![@votes], &Sequential),
1452 Err(AssemblyError::InsufficientAttestations(quorum, quorum - 1))
1453 );
1454 }
1455
1456 #[test]
1457 fn test_assemble_certificate_requires_quorum() {
1458 assemble_certificate_requires_quorum::<MinPk>();
1459 assemble_certificate_requires_quorum::<MinSig>();
1460 }
1461
1462 fn assemble_certificate_rejects_duplicate_signers<V: Variant>() {
1463 let (schemes, _) = setup_signers::<V>(4, 18);
1464 let quorum =
1465 usize::try_from(N3f1::quorum(schemes.len())).expect("quorum exceeds usize::MAX");
1466 let proposal = sample_proposal(Epoch::new(0), View::new(8), 4);
1467 let mut votes: Vec<_> = schemes
1468 .iter()
1469 .take(quorum)
1470 .map(|scheme| {
1471 scheme
1472 .sign(Subject::Notarize {
1473 proposal: &proposal,
1474 })
1475 .unwrap()
1476 })
1477 .collect();
1478 let duplicate = votes[0].signer;
1479 votes.push(votes[0].clone());
1480
1481 assert_eq!(
1482 schemes[0].assemble(non_empty![@votes], &Sequential),
1483 Err(AssemblyError::DuplicateSigner(duplicate))
1484 );
1485 }
1486
1487 #[test]
1488 fn test_assemble_certificate_rejects_duplicate_signers() {
1489 assemble_certificate_rejects_duplicate_signers::<MinPk>();
1490 assemble_certificate_rejects_duplicate_signers::<MinSig>();
1491 }
1492
1493 fn assemble_certificate_rejects_unknown_signer<V: Variant>() {
1494 let (schemes, _) = setup_signers::<V>(4, 19);
1495 let quorum =
1496 usize::try_from(N3f1::quorum(schemes.len())).expect("quorum exceeds usize::MAX");
1497 let proposal = sample_proposal(Epoch::new(0), View::new(9), 4);
1498 let mut votes: Vec<_> = schemes
1499 .iter()
1500 .take(quorum)
1501 .map(|scheme| {
1502 scheme
1503 .sign(Subject::Notarize {
1504 proposal: &proposal,
1505 })
1506 .unwrap()
1507 })
1508 .collect();
1509 let unknown_signer = Participant::from_usize(schemes.len());
1510 let mut unknown = votes[0].clone();
1511 unknown.signer = unknown_signer;
1512 votes.push(unknown);
1513
1514 assert_eq!(
1515 schemes[0].assemble(non_empty![@votes], &Sequential),
1516 Err(AssemblyError::UnknownSigner(unknown_signer))
1517 );
1518 }
1519
1520 #[test]
1521 fn test_assemble_certificate_rejects_unknown_signer() {
1522 assemble_certificate_rejects_unknown_signer::<MinPk>();
1523 assemble_certificate_rejects_unknown_signer::<MinSig>();
1524 }
1525
1526 fn assemble_certificate_rejects_malformed_signature<V: Variant>() {
1527 let (schemes, _) = setup_signers::<V>(4, 20);
1528 let quorum =
1529 usize::try_from(N3f1::quorum(schemes.len())).expect("quorum exceeds usize::MAX");
1530 let proposal = sample_proposal(Epoch::new(0), View::new(10), 4);
1531 let mut votes: Vec<_> = schemes
1532 .iter()
1533 .take(quorum)
1534 .map(|scheme| {
1535 scheme
1536 .sign(Subject::Notarize {
1537 proposal: &proposal,
1538 })
1539 .unwrap()
1540 })
1541 .collect();
1542 let malformed_signer = votes[0].signer;
1543 let mut malformed = bytes::Bytes::from_static(&[0]);
1544 votes[0].signature = Lazy::deferred(&mut malformed, ());
1545
1546 assert_eq!(
1547 schemes[0].assemble(non_empty![@votes], &Sequential),
1548 Err(AssemblyError::MalformedSignature(malformed_signer))
1549 );
1550 }
1551
1552 #[test]
1553 fn test_assemble_certificate_rejects_malformed_signature() {
1554 assemble_certificate_rejects_malformed_signature::<MinPk>();
1555 assemble_certificate_rejects_malformed_signature::<MinSig>();
1556 }
1557
1558 fn verify_certificate<V: Variant>() {
1559 let (schemes, verifier) = setup_signers::<V>(4, 19);
1560 let quorum = N3f1::quorum(schemes.len()) as usize;
1561 let proposal = sample_proposal(Epoch::new(0), View::new(9), 5);
1562
1563 let votes: Vec<_> = schemes
1564 .iter()
1565 .take(quorum)
1566 .map(|scheme| {
1567 scheme
1568 .sign(Subject::Finalize {
1569 proposal: &proposal,
1570 })
1571 .unwrap()
1572 })
1573 .collect();
1574
1575 let certificate = schemes[0]
1576 .assemble(non_empty![@votes], &Sequential)
1577 .expect("assemble certificate");
1578
1579 assert!(verifier.verify_certificate::<_, Sha256Digest>(
1580 &mut test_rng(),
1581 Subject::Finalize {
1582 proposal: &proposal,
1583 },
1584 &certificate,
1585 &Sequential,
1586 ));
1587 }
1588
1589 #[test]
1590 fn test_verify_certificate() {
1591 verify_certificate::<MinPk>();
1592 verify_certificate::<MinSig>();
1593 }
1594
1595 fn verify_certificate_detects_corruption<V: Variant>() {
1596 let mut rng = test_rng();
1597 let (schemes, verifier) = setup_signers::<V>(4, 23);
1598 let quorum = N3f1::quorum(schemes.len()) as usize;
1599 let proposal = sample_proposal(Epoch::new(0), View::new(11), 6);
1600
1601 let votes: Vec<_> = schemes
1602 .iter()
1603 .take(quorum)
1604 .map(|scheme| {
1605 scheme
1606 .sign(Subject::Notarize {
1607 proposal: &proposal,
1608 })
1609 .unwrap()
1610 })
1611 .collect();
1612
1613 let certificate = schemes[0]
1614 .assemble(non_empty![@votes], &Sequential)
1615 .expect("assemble certificate");
1616
1617 assert!(verifier.verify_certificate::<_, Sha256Digest>(
1618 &mut rng,
1619 Subject::Notarize {
1620 proposal: &proposal,
1621 },
1622 &certificate,
1623 &Sequential,
1624 ));
1625
1626 let cert = certificate.get().unwrap();
1627 let corrupted: Certificate<V> = Signature {
1628 vote_signature: cert.seed_signature,
1629 seed_signature: cert.seed_signature,
1630 }
1631 .into();
1632 assert!(!verifier.verify_certificate::<_, Sha256Digest>(
1633 &mut rng,
1634 Subject::Notarize {
1635 proposal: &proposal,
1636 },
1637 &corrupted,
1638 &Sequential,
1639 ));
1640 }
1641
1642 #[test]
1643 fn test_verify_certificate_detects_corruption() {
1644 verify_certificate_detects_corruption::<MinPk>();
1645 verify_certificate_detects_corruption::<MinSig>();
1646 }
1647
1648 fn verify_certificate_rejects_identity_components<V: Variant>() {
1649 let mut rng = test_rng();
1650 let (schemes, verifier) = setup_signers::<V>(4, 27);
1651 let quorum =
1652 usize::try_from(N3f1::quorum(schemes.len())).expect("quorum exceeds usize::MAX");
1653 let proposal = sample_proposal(Epoch::new(0), View::new(12), 7);
1654 let votes: Vec<_> = schemes
1655 .iter()
1656 .take(quorum)
1657 .map(|scheme| {
1658 scheme
1659 .sign(Subject::Notarize {
1660 proposal: &proposal,
1661 })
1662 .unwrap()
1663 })
1664 .collect();
1665 let certificate = schemes[0]
1666 .assemble(non_empty![@votes], &Sequential)
1667 .expect("assemble certificate");
1668 let signature = certificate.get().unwrap();
1669 let corrupted = [
1670 Signature {
1671 vote_signature: V::Signature::zero(),
1672 seed_signature: signature.seed_signature,
1673 }
1674 .into(),
1675 Signature {
1676 vote_signature: signature.vote_signature,
1677 seed_signature: V::Signature::zero(),
1678 }
1679 .into(),
1680 ];
1681
1682 for certificate in &corrupted {
1683 assert!(!verifier.verify_certificate::<_, Sha256Digest>(
1684 &mut rng,
1685 Subject::Notarize {
1686 proposal: &proposal,
1687 },
1688 certificate,
1689 &Sequential,
1690 ));
1691 }
1692 }
1693
1694 #[test]
1695 fn test_verify_certificate_rejects_identity_components() {
1696 verify_certificate_rejects_identity_components::<MinPk>();
1697 verify_certificate_rejects_identity_components::<MinSig>();
1698 }
1699
1700 fn certificate_codec_roundtrip<V: Variant>() {
1701 let (schemes, _) = setup_signers::<V>(5, 29);
1702 let quorum = N3f1::quorum(schemes.len()) as usize;
1703 let proposal = sample_proposal(Epoch::new(0), View::new(13), 7);
1704
1705 let votes: Vec<_> = schemes
1706 .iter()
1707 .take(quorum)
1708 .map(|scheme| {
1709 scheme
1710 .sign(Subject::Notarize {
1711 proposal: &proposal,
1712 })
1713 .unwrap()
1714 })
1715 .collect();
1716
1717 let certificate = schemes[0]
1718 .assemble(non_empty![@votes], &Sequential)
1719 .expect("assemble certificate");
1720
1721 let encoded = certificate.encode();
1722 assert_eq!(
1723 encoded.len(),
1724 Scheme::<V>::max_certificate_size(u32::try_from(schemes.len()).unwrap())
1725 );
1726 let decoded = Certificate::<V>::decode_cfg(encoded, &()).expect("decode certificate");
1727 assert_eq!(decoded, certificate);
1728 }
1729
1730 #[test]
1731 fn test_certificate_codec_roundtrip() {
1732 certificate_codec_roundtrip::<MinPk>();
1733 certificate_codec_roundtrip::<MinSig>();
1734 }
1735
1736 fn seed_codec_roundtrip<V: Variant>() {
1737 let (schemes, _) = setup_signers::<V>(4, 5);
1738 let quorum = N3f1::quorum(schemes.len()) as usize;
1739 let proposal = sample_proposal(Epoch::new(0), View::new(1), 0);
1740
1741 let votes: Vec<_> = schemes
1742 .iter()
1743 .take(quorum)
1744 .map(|scheme| {
1745 scheme
1746 .sign(Subject::Finalize {
1747 proposal: &proposal,
1748 })
1749 .unwrap()
1750 })
1751 .collect();
1752
1753 let certificate = schemes[0]
1754 .assemble(non_empty![@votes], &Sequential)
1755 .expect("assemble certificate");
1756 let cert = certificate.get().unwrap();
1757
1758 let seed = Seed::new(proposal.round, cert.seed_signature);
1759
1760 let encoded = seed.encode();
1761 let decoded = Seed::<V>::decode_cfg(encoded, &()).expect("decode seed");
1762 assert_eq!(decoded, seed);
1763 }
1764
1765 #[test]
1766 fn test_seed_codec_roundtrip() {
1767 seed_codec_roundtrip::<MinPk>();
1768 seed_codec_roundtrip::<MinSig>();
1769 }
1770
1771 fn seed_verify<V: Variant>() {
1772 let (schemes, _) = setup_signers::<V>(4, 5);
1773 let quorum = N3f1::quorum(schemes.len()) as usize;
1774 let proposal = sample_proposal(Epoch::new(0), View::new(1), 0);
1775
1776 let votes: Vec<_> = schemes
1777 .iter()
1778 .take(quorum)
1779 .map(|scheme| {
1780 scheme
1781 .sign(Subject::Finalize {
1782 proposal: &proposal,
1783 })
1784 .unwrap()
1785 })
1786 .collect();
1787
1788 let certificate = schemes[0]
1789 .assemble(non_empty![@votes], &Sequential)
1790 .expect("assemble certificate");
1791 let cert = certificate.get().unwrap();
1792
1793 let seed = Seed::new(proposal.round, cert.seed_signature);
1794
1795 assert!(seed.verify(&schemes[0]));
1796
1797 let invalid_seed = Seed::new(
1799 Round::new(proposal.epoch(), proposal.view().next()),
1800 cert.seed_signature,
1801 );
1802
1803 assert!(!invalid_seed.verify(&schemes[0]));
1804 }
1805
1806 #[test]
1807 fn test_seed_verify() {
1808 seed_verify::<MinPk>();
1809 seed_verify::<MinSig>();
1810 }
1811
1812 fn seedable<V: Variant>() {
1813 let (schemes, _) = setup_signers::<V>(4, 5);
1814 let quorum = N3f1::quorum(schemes.len()) as usize;
1815 let proposal = sample_proposal(Epoch::new(0), View::new(1), 0);
1816
1817 let notarizes: Vec<_> = schemes
1818 .iter()
1819 .take(quorum)
1820 .map(|scheme| Notarize::sign(scheme, proposal.clone()).unwrap())
1821 .collect();
1822
1823 let notarization =
1824 Notarization::from_notarizes(&schemes[0], non_empty![@¬arizes], &Sequential)
1825 .unwrap();
1826
1827 let finalizes: Vec<_> = schemes
1828 .iter()
1829 .take(quorum)
1830 .map(|scheme| Finalize::sign(scheme, proposal.clone()).unwrap())
1831 .collect();
1832
1833 let finalization =
1834 Finalization::from_finalizes(&schemes[0], non_empty![@&finalizes], &Sequential)
1835 .unwrap();
1836
1837 assert_eq!(notarization.seed(), finalization.seed());
1838 assert!(notarization.seed().verify(&schemes[0]));
1839 }
1840
1841 #[test]
1842 fn test_seedable() {
1843 seedable::<MinPk>();
1844 seedable::<MinSig>();
1845 }
1846
1847 fn scheme_clone_and_verifier<V: Variant>() {
1848 let (schemes, verifier) = setup_signers::<V>(4, 31);
1849 let signer = schemes[0].clone();
1850 let proposal = sample_proposal(Epoch::new(0), View::new(21), 9);
1851
1852 assert!(
1853 signer
1854 .sign(Subject::Notarize {
1855 proposal: &proposal,
1856 })
1857 .is_some(),
1858 "signer should produce votes"
1859 );
1860
1861 assert!(
1862 verifier
1863 .sign(Subject::Notarize {
1864 proposal: &proposal,
1865 })
1866 .is_none(),
1867 "verifier should not produce votes"
1868 );
1869 }
1870
1871 #[test]
1872 fn test_scheme_clone_and_verifier() {
1873 scheme_clone_and_verifier::<MinPk>();
1874 scheme_clone_and_verifier::<MinSig>();
1875 }
1876
1877 fn certificate_verifier_accepts_certificates<V: Variant>() {
1878 let (schemes, _) = setup_signers::<V>(4, 37);
1879 let quorum = N3f1::quorum(schemes.len()) as usize;
1880 let proposal = sample_proposal(Epoch::new(0), View::new(15), 8);
1881
1882 let votes: Vec<_> = schemes
1883 .iter()
1884 .take(quorum)
1885 .map(|scheme| {
1886 scheme
1887 .sign(Subject::Finalize {
1888 proposal: &proposal,
1889 })
1890 .unwrap()
1891 })
1892 .collect();
1893
1894 let certificate = schemes[0]
1895 .assemble(non_empty![@votes], &Sequential)
1896 .expect("assemble certificate");
1897
1898 let certificate_verifier =
1899 Scheme::<V>::certificate_verifier(NAMESPACE, *schemes[0].identity());
1900 assert!(
1901 certificate_verifier
1902 .sign(Subject::Finalize {
1903 proposal: &proposal,
1904 })
1905 .is_none(),
1906 "certificate verifier should not produce votes"
1907 );
1908 assert!(certificate_verifier.verify_certificate::<_, Sha256Digest>(
1909 &mut test_rng(),
1910 Subject::Finalize {
1911 proposal: &proposal,
1912 },
1913 &certificate,
1914 &Sequential,
1915 ));
1916 }
1917
1918 #[test]
1919 fn test_certificate_verifier_accepts_certificates() {
1920 certificate_verifier_accepts_certificates::<MinPk>();
1921 certificate_verifier_accepts_certificates::<MinSig>();
1922 }
1923
1924 fn certificate_verifier_panics_on_vote<V: Variant>() {
1925 let (schemes, _) = setup_signers::<V>(4, 37);
1926 let certificate_verifier =
1927 Scheme::<V>::certificate_verifier(NAMESPACE, *schemes[0].identity());
1928 let proposal = sample_proposal(Epoch::new(0), View::new(15), 8);
1929 let vote = schemes[1]
1930 .sign(Subject::Finalize {
1931 proposal: &proposal,
1932 })
1933 .unwrap();
1934
1935 certificate_verifier.verify_attestation::<_, Sha256Digest>(
1936 &mut test_rng(),
1937 Subject::Finalize {
1938 proposal: &proposal,
1939 },
1940 &vote,
1941 &Sequential,
1942 );
1943 }
1944
1945 #[test]
1946 #[should_panic(expected = "can only be called for signer and verifier")]
1947 fn test_certificate_verifier_panics_on_vote_min_pk() {
1948 certificate_verifier_panics_on_vote::<MinPk>();
1949 }
1950
1951 #[test]
1952 #[should_panic(expected = "can only be called for signer and verifier")]
1953 fn test_certificate_verifier_panics_on_vote_min_sig() {
1954 certificate_verifier_panics_on_vote::<MinSig>();
1955 }
1956
1957 fn verify_certificate_returns_seed_randomness<V: Variant>() {
1958 let (schemes, _) = setup_signers::<V>(4, 43);
1959 let quorum = N3f1::quorum(schemes.len()) as usize;
1960 let proposal = sample_proposal(Epoch::new(0), View::new(19), 10);
1961
1962 let votes: Vec<_> = schemes
1963 .iter()
1964 .take(quorum)
1965 .map(|scheme| {
1966 scheme
1967 .sign(Subject::Notarize {
1968 proposal: &proposal,
1969 })
1970 .unwrap()
1971 })
1972 .collect();
1973
1974 let certificate = schemes[0]
1975 .assemble(non_empty![@votes], &Sequential)
1976 .expect("assemble certificate");
1977 let cert = certificate.get().unwrap();
1978
1979 let seed = Seed::<V>::new(proposal.round, cert.seed_signature);
1980 assert_eq!(seed.signature, cert.seed_signature);
1981 }
1982
1983 #[test]
1984 fn test_verify_certificate_returns_seed_randomness() {
1985 verify_certificate_returns_seed_randomness::<MinPk>();
1986 verify_certificate_returns_seed_randomness::<MinSig>();
1987 }
1988
1989 fn certificate_decode_rejects_length_mismatch<V: Variant>() {
1990 let (schemes, _) = setup_signers::<V>(4, 47);
1991 let quorum = N3f1::quorum(schemes.len()) as usize;
1992 let proposal = sample_proposal(Epoch::new(0), View::new(21), 11);
1993
1994 let votes: Vec<_> = schemes
1995 .iter()
1996 .take(quorum)
1997 .map(|scheme| {
1998 scheme
1999 .sign::<Sha256Digest>(Subject::Nullify {
2000 round: proposal.round,
2001 })
2002 .unwrap()
2003 })
2004 .collect();
2005
2006 let certificate = schemes[0]
2007 .assemble(non_empty![@votes], &Sequential)
2008 .expect("assemble certificate");
2009
2010 let mut encoded = certificate.encode();
2011 let truncated = encoded.split_to(encoded.len() - 1);
2012 assert!(Signature::<V>::decode_cfg(truncated, &()).is_err());
2013 }
2014
2015 #[test]
2016 fn test_certificate_decode_rejects_length_mismatch() {
2017 certificate_decode_rejects_length_mismatch::<MinPk>();
2018 certificate_decode_rejects_length_mismatch::<MinSig>();
2019 }
2020
2021 fn sign_vote_partial_matches_share<V: Variant>() {
2022 let (schemes, _) = setup_signers::<V>(4, 53);
2023 let scheme = &schemes[0];
2024 let share = scheme.share().expect("has share");
2025
2026 let proposal = sample_proposal(Epoch::new(0), View::new(23), 12);
2027 let vote = scheme
2028 .sign(Subject::Notarize {
2029 proposal: &proposal,
2030 })
2031 .unwrap();
2032
2033 let notarize_namespace = notarize_namespace(NAMESPACE);
2034 let notarize_message = proposal.encode();
2035 let expected_message = threshold::sign_message::<V>(
2036 share,
2037 notarize_namespace.as_ref(),
2038 notarize_message.as_ref(),
2039 )
2040 .value;
2041
2042 let seed_namespace = seed_namespace(NAMESPACE);
2043 let seed_message = proposal.round.encode();
2044 let expected_seed =
2045 threshold::sign_message::<V>(share, seed_namespace.as_ref(), seed_message.as_ref())
2046 .value;
2047
2048 assert_eq!(vote.signer, share.index);
2049 let sig = vote.signature.get().unwrap();
2050 assert_eq!(sig.vote_signature, expected_message);
2051 assert_eq!(sig.seed_signature, expected_seed);
2052 }
2053
2054 #[test]
2055 fn test_sign_vote_partial_matches_share() {
2056 sign_vote_partial_matches_share::<MinPk>();
2057 sign_vote_partial_matches_share::<MinSig>();
2058 }
2059
2060 fn verify_certificate_detects_seed_corruption<V: Variant>() {
2061 let mut rng = test_rng();
2062 let (schemes, verifier) = setup_signers::<V>(4, 59);
2063 let quorum = N3f1::quorum(schemes.len()) as usize;
2064 let proposal = sample_proposal(Epoch::new(0), View::new(25), 13);
2065
2066 let votes: Vec<_> = schemes
2067 .iter()
2068 .take(quorum)
2069 .map(|scheme| {
2070 scheme
2071 .sign::<Sha256Digest>(Subject::Nullify {
2072 round: proposal.round,
2073 })
2074 .unwrap()
2075 })
2076 .collect();
2077
2078 let certificate = schemes[0]
2079 .assemble(non_empty![@votes], &Sequential)
2080 .expect("assemble certificate");
2081
2082 assert!(verifier.verify_certificate::<_, Sha256Digest>(
2083 &mut rng,
2084 Subject::Nullify {
2085 round: proposal.round,
2086 },
2087 &certificate,
2088 &Sequential,
2089 ));
2090
2091 let cert = certificate.get().unwrap();
2092 let corrupted: Certificate<V> = Signature {
2093 vote_signature: cert.vote_signature,
2094 seed_signature: cert.vote_signature,
2095 }
2096 .into();
2097 assert!(!verifier.verify_certificate::<_, Sha256Digest>(
2098 &mut rng,
2099 Subject::Nullify {
2100 round: proposal.round,
2101 },
2102 &corrupted,
2103 &Sequential,
2104 ));
2105 }
2106
2107 #[test]
2108 fn test_verify_certificate_detects_seed_corruption() {
2109 verify_certificate_detects_seed_corruption::<MinPk>();
2110 verify_certificate_detects_seed_corruption::<MinSig>();
2111 }
2112
2113 fn encrypt_decrypt<V: Variant>() {
2114 let mut rng = test_rng();
2115 let (schemes, verifier) = setup_signers::<V>(4, 61);
2116 let quorum = N3f1::quorum(schemes.len()) as usize;
2117
2118 let message = b"Secret message for future view10";
2120
2121 let target = Round::new(Epoch::new(333), View::new(10));
2123
2124 let ciphertext = schemes[0]
2126 .encrypt(&mut rng, target, *message)
2127 .expect("valid TLE encryption inputs");
2128
2129 let ciphertext_verifier = verifier
2131 .encrypt(&mut rng, target, *message)
2132 .expect("valid TLE encryption inputs");
2133
2134 let proposal = sample_proposal(target.epoch(), target.view(), 14);
2136 let notarizes: Vec<_> = schemes
2137 .iter()
2138 .take(quorum)
2139 .map(|scheme| Notarize::sign(scheme, proposal.clone()).unwrap())
2140 .collect();
2141
2142 let notarization =
2143 Notarization::from_notarizes(&schemes[0], non_empty![@¬arizes], &Sequential)
2144 .unwrap();
2145
2146 let seed = notarization.seed();
2148 let decrypted = seed.decrypt(&ciphertext).unwrap();
2149 assert_eq!(message, decrypted.as_ref());
2150
2151 let decrypted_verifier = seed.decrypt(&ciphertext_verifier).unwrap();
2152 assert_eq!(message, decrypted_verifier.as_ref());
2153 }
2154
2155 #[test]
2156 fn test_encrypt_decrypt() {
2157 encrypt_decrypt::<MinPk>();
2158 encrypt_decrypt::<MinSig>();
2159 }
2160
2161 fn encrypt_with_identity_returns_error<V: Variant>() {
2162 let mut rng = test_rng();
2163 let verifier = Scheme::<V>::certificate_verifier(NAMESPACE, V::Public::zero());
2164 let target = Round::new(Epoch::new(333), View::new(10));
2165
2166 assert!(matches!(
2167 verifier.encrypt(&mut rng, target, [0u8; 32]),
2168 Err(tle::Error::InvalidPublicKey)
2169 ));
2170 }
2171
2172 #[test]
2173 fn test_encrypt_with_identity_returns_error() {
2174 encrypt_with_identity_returns_error::<MinPk>();
2175 encrypt_with_identity_returns_error::<MinSig>();
2176 }
2177
2178 fn verify_attestation_rejects_malleability<V: Variant>() {
2179 let mut rng = test_rng();
2180 let (schemes, _) = setup_signers::<V>(4, 67);
2181 let proposal = sample_proposal(Epoch::new(0), View::new(27), 14);
2182
2183 let attestation = schemes[0]
2184 .sign(Subject::Notarize {
2185 proposal: &proposal,
2186 })
2187 .unwrap();
2188
2189 assert!(schemes[0].verify_attestation::<_, Sha256Digest>(
2190 &mut rng,
2191 Subject::Notarize {
2192 proposal: &proposal,
2193 },
2194 &attestation,
2195 &Sequential,
2196 ));
2197
2198 let random_scalar = Scalar::random(&mut rng);
2199 let delta = V::Signature::generator() * &random_scalar;
2200 let att_sig = attestation.signature.get().unwrap();
2201 let forged_attestation: Attestation<Scheme<V>> = Attestation {
2202 signer: attestation.signer,
2203 signature: Signature {
2204 vote_signature: att_sig.vote_signature - &delta,
2205 seed_signature: att_sig.seed_signature + &delta,
2206 }
2207 .into(),
2208 };
2209
2210 let forged_sig = forged_attestation.signature.get().unwrap();
2211 let forged_sum = forged_sig.vote_signature + &forged_sig.seed_signature;
2212 let valid_sum = att_sig.vote_signature + &att_sig.seed_signature;
2213 assert_eq!(forged_sum, valid_sum, "signature sums should be equal");
2214
2215 assert!(
2216 !schemes[0].verify_attestation::<_, Sha256Digest>(
2217 &mut rng,
2218 Subject::Notarize {
2219 proposal: &proposal,
2220 },
2221 &forged_attestation,
2222 &Sequential,
2223 ),
2224 "forged attestation should be rejected"
2225 );
2226 }
2227
2228 #[test]
2229 fn test_verify_attestation_rejects_malleability() {
2230 verify_attestation_rejects_malleability::<MinPk>();
2231 verify_attestation_rejects_malleability::<MinSig>();
2232 }
2233
2234 fn verify_attestations_rejects_malleability<V: Variant>() {
2235 let mut rng = test_rng();
2236 let (schemes, _) = setup_signers::<V>(4, 71);
2237 let proposal = sample_proposal(Epoch::new(0), View::new(29), 15);
2238
2239 let attestation1 = schemes[0]
2240 .sign(Subject::Notarize {
2241 proposal: &proposal,
2242 })
2243 .unwrap();
2244 let attestation2 = schemes[1]
2245 .sign(Subject::Notarize {
2246 proposal: &proposal,
2247 })
2248 .unwrap();
2249
2250 let verification = schemes[0].verify_attestations(
2251 &mut rng,
2252 Subject::Notarize {
2253 proposal: &proposal,
2254 },
2255 vec![attestation1.clone(), attestation2.clone()],
2256 &Sequential,
2257 );
2258 assert!(verification.invalid.is_empty());
2259 assert_eq!(verification.verified.len(), 2);
2260
2261 let random_scalar = Scalar::random(&mut rng);
2262 let delta = V::Signature::generator() * &random_scalar;
2263 let att1_sig = attestation1.signature.get().unwrap();
2264 let att2_sig = attestation2.signature.get().unwrap();
2265 let forged_attestation1: Attestation<Scheme<V>> = Attestation {
2266 signer: attestation1.signer,
2267 signature: Signature {
2268 vote_signature: att1_sig.vote_signature - &delta,
2269 seed_signature: att1_sig.seed_signature,
2270 }
2271 .into(),
2272 };
2273 let forged_attestation2: Attestation<Scheme<V>> = Attestation {
2274 signer: attestation2.signer,
2275 signature: Signature {
2276 vote_signature: att2_sig.vote_signature + &delta,
2277 seed_signature: att2_sig.seed_signature,
2278 }
2279 .into(),
2280 };
2281
2282 let forged1_sig = forged_attestation1.signature.get().unwrap();
2283 let forged2_sig = forged_attestation2.signature.get().unwrap();
2284 let forged_vote_sum = forged1_sig.vote_signature + &forged2_sig.vote_signature;
2285 let valid_vote_sum = att1_sig.vote_signature + &att2_sig.vote_signature;
2286 assert_eq!(
2287 forged_vote_sum, valid_vote_sum,
2288 "vote signature sums should be equal"
2289 );
2290
2291 let verification = schemes[0].verify_attestations(
2292 &mut rng,
2293 Subject::Notarize {
2294 proposal: &proposal,
2295 },
2296 vec![forged_attestation1, forged_attestation2],
2297 &Sequential,
2298 );
2299 assert!(
2300 !verification.invalid.is_empty(),
2301 "forged attestations should be detected"
2302 );
2303 }
2304
2305 #[test]
2306 fn test_verify_attestations_rejects_malleability() {
2307 verify_attestations_rejects_malleability::<MinPk>();
2308 verify_attestations_rejects_malleability::<MinSig>();
2309 }
2310
2311 fn optimistic_assemble_verifies_both_components<V: Variant>(
2314 strategy: &impl Strategy,
2315 ) -> Vec<bytes::Bytes> {
2316 let mut outputs = Vec::new();
2317 let mut rng = test_rng();
2318 let (schemes, verifier) = setup_signers::<V>(7, 72);
2319 let quorum = N3f1::quorum(schemes.len()) as usize;
2320 let subject = Subject::Nullify {
2321 round: Round::new(Epoch::new(0), View::new(30)),
2322 };
2323 let other_subject = Subject::Nullify {
2324 round: Round::new(Epoch::new(0), View::new(31)),
2325 };
2326 let votes: Vec<_> = schemes[..quorum]
2327 .iter()
2328 .map(|scheme| scheme.sign::<Sha256Digest>(subject).unwrap())
2329 .collect();
2330
2331 for pending_len in [1, quorum] {
2332 let prior_len = quorum - pending_len;
2334 let prior = verifier.verify_attestations::<_, Sha256Digest, _>(
2335 &mut rng,
2336 subject,
2337 votes[..prior_len].iter().cloned(),
2338 strategy,
2339 );
2340 assert!(prior.invalid.is_empty());
2341
2342 for corrupt_vote in [false, true] {
2343 let mut pending = votes[prior_len..].to_vec();
2344 let positions = if pending_len == 1 {
2345 vec![(0, corrupt_vote)]
2346 } else {
2347 vec![(0, corrupt_vote), (pending_len.div_ceil(2), !corrupt_vote)]
2348 };
2349 let mut invalid = Vec::new();
2350
2351 for (position, corrupt_vote) in positions {
2354 let other = schemes[prior_len + position]
2355 .sign::<Sha256Digest>(other_subject)
2356 .unwrap();
2357 let original = pending[position].signature.get().unwrap();
2358 let other = other.signature.get().unwrap();
2359 pending[position].signature = Signature {
2360 vote_signature: if corrupt_vote {
2361 other.vote_signature
2362 } else {
2363 original.vote_signature
2364 },
2365 seed_signature: if corrupt_vote {
2366 original.seed_signature
2367 } else {
2368 other.seed_signature
2369 },
2370 }
2371 .into();
2372 invalid.push(pending[position].signer);
2373 }
2374 invalid.sort_unstable();
2375 let accepted: Vec<_> = votes[prior_len..]
2376 .iter()
2377 .filter(|vote| !invalid.contains(&vote.signer))
2378 .cloned()
2379 .collect();
2380
2381 let pending: Vec<_> = pending
2383 .into_iter()
2384 .map(|vote| Attestation::<Scheme<V>>::decode_cfg(vote.encode(), &()).unwrap())
2385 .collect();
2386 let result = verifier
2387 .optimistic_assemble::<_, Sha256Digest, _, _>(
2388 &mut rng,
2389 subject,
2390 pending,
2391 &prior.verified,
2392 strategy,
2393 )
2394 .unwrap_err();
2395 assert_eq!(result.verified, accepted);
2396 let mut rejected = result.invalid;
2397 rejected.sort_unstable();
2398 assert_eq!(rejected, invalid);
2399 }
2400
2401 let certificate = verifier
2403 .optimistic_assemble::<_, Sha256Digest, _, _>(
2404 &mut rng,
2405 subject,
2406 votes[prior_len..].iter().cloned(),
2407 &prior.verified,
2408 strategy,
2409 )
2410 .ok()
2411 .expect("valid quorum must certify");
2412 assert!(verifier.verify_certificate::<_, Sha256Digest>(
2413 &mut rng,
2414 subject,
2415 &certificate,
2416 strategy,
2417 ));
2418 assert!(!verifier.verify_certificate::<_, Sha256Digest>(
2419 &mut rng,
2420 other_subject,
2421 &certificate,
2422 strategy,
2423 ));
2424 outputs.push(certificate.encode());
2425 }
2426 outputs
2427 }
2428
2429 #[test]
2430 fn test_optimistic_assemble_verifies_both_components() {
2431 explore(optimistic_assemble_verifies_both_components::<MinPk>);
2432 explore(optimistic_assemble_verifies_both_components::<MinSig>);
2433 }
2434
2435 fn verify_certificate_rejects_malleability<V: Variant>() {
2436 let mut rng = test_rng();
2437 let (schemes, verifier) = setup_signers::<V>(4, 73);
2438 let quorum = N3f1::quorum(schemes.len()) as usize;
2439 let proposal = sample_proposal(Epoch::new(0), View::new(31), 16);
2440
2441 let votes: Vec<_> = schemes
2442 .iter()
2443 .take(quorum)
2444 .map(|scheme| {
2445 scheme
2446 .sign(Subject::Notarize {
2447 proposal: &proposal,
2448 })
2449 .unwrap()
2450 })
2451 .collect();
2452
2453 let certificate = schemes[0]
2454 .assemble(non_empty![@votes], &Sequential)
2455 .expect("assemble certificate");
2456
2457 assert!(verifier.verify_certificate::<_, Sha256Digest>(
2458 &mut rng,
2459 Subject::Notarize {
2460 proposal: &proposal,
2461 },
2462 &certificate,
2463 &Sequential,
2464 ));
2465
2466 let cert = certificate.get().unwrap();
2467 let random_scalar = Scalar::random(&mut rng);
2468 let delta = V::Signature::generator() * &random_scalar;
2469 let forged_certificate: Certificate<V> = Signature {
2470 vote_signature: cert.vote_signature - &delta,
2471 seed_signature: cert.seed_signature + &delta,
2472 }
2473 .into();
2474
2475 let forged_cert = forged_certificate.get().unwrap();
2476 let forged_sum = forged_cert.vote_signature + &forged_cert.seed_signature;
2477 let valid_sum = cert.vote_signature + &cert.seed_signature;
2478 assert_eq!(forged_sum, valid_sum, "signature sums should be equal");
2479
2480 assert!(
2481 !verifier.verify_certificate::<_, Sha256Digest>(
2482 &mut rng,
2483 Subject::Notarize {
2484 proposal: &proposal,
2485 },
2486 &forged_certificate,
2487 &Sequential,
2488 ),
2489 "forged certificate should be rejected"
2490 );
2491 }
2492
2493 #[test]
2494 fn test_verify_certificate_rejects_malleability() {
2495 verify_certificate_rejects_malleability::<MinPk>();
2496 verify_certificate_rejects_malleability::<MinSig>();
2497 }
2498
2499 fn verify_certificates_rejects_malleability<V: Variant>() {
2500 let mut rng = test_rng();
2501 let (schemes, verifier) = setup_signers::<V>(4, 79);
2502 let quorum = N3f1::quorum(schemes.len()) as usize;
2503 let proposal1 = sample_proposal(Epoch::new(0), View::new(33), 17);
2504 let proposal2 = sample_proposal(Epoch::new(0), View::new(34), 18);
2505
2506 let votes1: Vec<_> = schemes
2507 .iter()
2508 .take(quorum)
2509 .map(|scheme| {
2510 scheme
2511 .sign(Subject::Notarize {
2512 proposal: &proposal1,
2513 })
2514 .unwrap()
2515 })
2516 .collect();
2517 let votes2: Vec<_> = schemes
2518 .iter()
2519 .take(quorum)
2520 .map(|scheme| {
2521 scheme
2522 .sign(Subject::Notarize {
2523 proposal: &proposal2,
2524 })
2525 .unwrap()
2526 })
2527 .collect();
2528
2529 let certificate1 = schemes[0]
2530 .assemble(non_empty![@votes1], &Sequential)
2531 .expect("assemble certificate1");
2532 let certificate2 = schemes[0]
2533 .assemble(non_empty![@votes2], &Sequential)
2534 .expect("assemble certificate2");
2535
2536 assert!(verifier.verify_certificates::<_, Sha256Digest, _>(
2537 &mut rng,
2538 non_empty![
2539 (
2540 Subject::Notarize {
2541 proposal: &proposal1,
2542 },
2543 &certificate1
2544 ),
2545 (
2546 Subject::Notarize {
2547 proposal: &proposal2,
2548 },
2549 &certificate2
2550 ),
2551 ],
2552 &Sequential,
2553 ));
2554
2555 let cert1 = certificate1.get().unwrap();
2556 let cert2 = certificate2.get().unwrap();
2557 let random_scalar = Scalar::random(&mut rng);
2558 let delta = V::Signature::generator() * &random_scalar;
2559 let forged_certificate1: Certificate<V> = Signature {
2560 vote_signature: cert1.vote_signature - &delta,
2561 seed_signature: cert1.seed_signature,
2562 }
2563 .into();
2564 let forged_certificate2: Certificate<V> = Signature {
2565 vote_signature: cert2.vote_signature + &delta,
2566 seed_signature: cert2.seed_signature,
2567 }
2568 .into();
2569
2570 let forged1 = forged_certificate1.get().unwrap();
2571 let forged2 = forged_certificate2.get().unwrap();
2572 let forged_vote_sum = forged1.vote_signature + &forged2.vote_signature;
2573 let valid_vote_sum = cert1.vote_signature + &cert2.vote_signature;
2574 assert_eq!(
2575 forged_vote_sum, valid_vote_sum,
2576 "vote signature sums should be equal"
2577 );
2578
2579 assert!(
2580 !verifier.verify_certificates::<_, Sha256Digest, _>(
2581 &mut rng,
2582 non_empty![
2583 (
2584 Subject::Notarize {
2585 proposal: &proposal1,
2586 },
2587 &forged_certificate1
2588 ),
2589 (
2590 Subject::Notarize {
2591 proposal: &proposal2,
2592 },
2593 &forged_certificate2
2594 ),
2595 ],
2596 &Sequential,
2597 ),
2598 "forged certificates should be rejected"
2599 );
2600 }
2601
2602 #[test]
2603 fn test_verify_certificates_rejects_malleability() {
2604 verify_certificates_rejects_malleability::<MinPk>();
2605 verify_certificates_rejects_malleability::<MinSig>();
2606 }
2607
2608 fn assemble_notarization_certificate<V: Variant>(
2609 schemes: &[Scheme<V>],
2610 proposal: &Proposal<Sha256Digest>,
2611 ) -> Certificate<V> {
2612 let quorum = N3f1::quorum(schemes.len()) as usize;
2613 let votes: Vec<_> = schemes
2614 .iter()
2615 .take(quorum)
2616 .map(|scheme| scheme.sign(Subject::Notarize { proposal }).unwrap())
2617 .collect();
2618
2619 schemes[0]
2620 .assemble(non_empty![@votes], &Sequential)
2621 .expect("assemble notarization certificate")
2622 }
2623
2624 fn assemble_finalization_certificate<V: Variant>(
2625 schemes: &[Scheme<V>],
2626 proposal: &Proposal<Sha256Digest>,
2627 ) -> Certificate<V> {
2628 let quorum = N3f1::quorum(schemes.len()) as usize;
2629 let votes: Vec<_> = schemes
2630 .iter()
2631 .skip(schemes.len() - quorum)
2632 .map(|scheme| scheme.sign(Subject::Finalize { proposal }).unwrap())
2633 .collect();
2634
2635 schemes[0]
2636 .assemble(non_empty![@votes], &Sequential)
2637 .expect("assemble finalization certificate")
2638 }
2639
2640 fn verify_certificates_accepts_shared_round_seed<V: Variant>() {
2641 let mut rng = test_rng();
2642 let (schemes, verifier) = setup_signers::<V>(4, 81);
2643 let proposal = sample_proposal(Epoch::new(1), View::new(35), 19);
2644 let notarization_certificate = assemble_notarization_certificate(&schemes, &proposal);
2645 let finalization_certificate = assemble_finalization_certificate(&schemes, &proposal);
2646
2647 assert!(verifier.verify_certificates::<_, Sha256Digest, _>(
2648 &mut rng,
2649 non_empty![
2650 (
2651 Subject::Notarize {
2652 proposal: &proposal,
2653 },
2654 ¬arization_certificate,
2655 ),
2656 (
2657 Subject::Finalize {
2658 proposal: &proposal,
2659 },
2660 &finalization_certificate,
2661 ),
2662 ],
2663 &Sequential,
2664 ));
2665 }
2666
2667 #[test]
2668 fn test_verify_certificates_accepts_shared_round_seed() {
2669 verify_certificates_accepts_shared_round_seed::<MinPk>();
2670 verify_certificates_accepts_shared_round_seed::<MinSig>();
2671 }
2672
2673 fn verify_certificates_rejects_cross_epoch_seed_replay<V: Variant>() {
2674 let mut rng = test_rng();
2675 let (schemes, verifier) = setup_signers::<V>(4, 83);
2676 let view = View::new(35);
2677 let proposal1 = sample_proposal(Epoch::new(1), view, 19);
2678 let proposal2 = sample_proposal(Epoch::new(2), view, 20);
2679 let certificate1 = assemble_notarization_certificate(&schemes, &proposal1);
2680 let certificate2 = assemble_notarization_certificate(&schemes, &proposal2);
2681
2682 assert!(verifier.verify_certificates::<_, Sha256Digest, _>(
2683 &mut rng,
2684 non_empty![
2685 (
2686 Subject::Notarize {
2687 proposal: &proposal1,
2688 },
2689 &certificate1,
2690 ),
2691 (
2692 Subject::Notarize {
2693 proposal: &proposal2,
2694 },
2695 &certificate2,
2696 ),
2697 ],
2698 &Sequential,
2699 ));
2700
2701 let cert1 = certificate1.get().unwrap();
2702 let cert2 = certificate2.get().unwrap();
2703 let forged_certificate2: Certificate<V> = Signature {
2704 vote_signature: cert2.vote_signature,
2705 seed_signature: cert1.seed_signature,
2706 }
2707 .into();
2708
2709 assert!(!verifier.verify_certificate::<_, Sha256Digest>(
2710 &mut rng,
2711 Subject::Notarize {
2712 proposal: &proposal2,
2713 },
2714 &forged_certificate2,
2715 &Sequential,
2716 ));
2717
2718 let batch = [
2719 (
2720 Subject::Notarize {
2721 proposal: &proposal1,
2722 },
2723 &certificate1,
2724 ),
2725 (
2726 Subject::Notarize {
2727 proposal: &proposal2,
2728 },
2729 &forged_certificate2,
2730 ),
2731 ];
2732
2733 assert!(!verifier.verify_certificates::<_, Sha256Digest, _>(
2734 &mut rng,
2735 non_empty![@batch.iter().copied()],
2736 &Sequential,
2737 ));
2738 assert_eq!(
2739 verifier.verify_certificates_bisect::<_, Sha256Digest>(&mut rng, &batch, &Sequential,),
2740 vec![true, false],
2741 );
2742 }
2743
2744 #[test]
2745 fn test_verify_certificates_rejects_cross_epoch_seed_replay() {
2746 verify_certificates_rejects_cross_epoch_seed_replay::<MinPk>();
2747 verify_certificates_rejects_cross_epoch_seed_replay::<MinSig>();
2748 }
2749
2750 #[cfg(feature = "arbitrary")]
2751 mod conformance {
2752 use super::*;
2753 use commonware_codec::conformance::CodecConformance;
2754
2755 commonware_conformance::conformance_tests! {
2756 CodecConformance<Signature<MinSig>>,
2757 CodecConformance<Certificate<MinSig>>,
2758 CodecConformance<Seed<MinSig>>,
2759 }
2760 }
2761}