commonware_consensus/simplex/scheme/
bls12381_threshold.rs

1//! BLS12-381 threshold implementation of the [`Scheme`] trait for `simplex`.
2//!
3//! [`Scheme`] is **non-attributable**: exposing partial signatures
4//! as evidence of either liveness or of committing a fault is not safe. With threshold signatures,
5//! any `t` valid partial signatures can be used to forge a partial signature for any other player,
6//! enabling equivocation attacks. Because peer connections are authenticated, evidence can be used locally
7//! (as it must be sent by said participant) but can't be used by an external observer.
8
9use crate::{
10    simplex::{
11        scheme::{
12            finalize_namespace, notarize_namespace, nullify_namespace, seed_namespace,
13            seed_namespace_and_message, vote_namespace_and_message,
14        },
15        types::{Finalization, Notarization, Subject},
16    },
17    types::{Epoch, Round, View},
18    Epochable, Viewable,
19};
20use bytes::{Buf, BufMut};
21use commonware_codec::{Encode, EncodeSize, Error, FixedSize, Read, ReadExt, Write};
22use commonware_cryptography::{
23    bls12381::{
24        primitives::{
25            group::Share,
26            ops::{
27                aggregate_signatures, aggregate_verify_multiple_messages, partial_sign_message,
28                partial_verify_multiple_public_keys, threshold_signature_recover_pair,
29                verify_message,
30            },
31            sharing::Sharing,
32            variant::{PartialSignature, Variant},
33        },
34        tle,
35    },
36    certificate::{self, Attestation, Verification},
37    Digest, PublicKey,
38};
39use commonware_utils::ordered::Set;
40use rand::{CryptoRng, Rng};
41use std::{
42    collections::{BTreeSet, HashMap},
43    fmt::Debug,
44};
45
46/// BLS12-381 threshold implementation of the [`Scheme`] trait.
47///
48/// It is possible for a node to play one of the following roles: a signer (with its share),
49/// a verifier (with evaluated public polynomial), or an external verifier that
50/// only checks recovered certificates.
51#[derive(Clone, Debug)]
52pub enum Scheme<P: PublicKey, V: Variant> {
53    Signer {
54        /// Participants in the committee.
55        participants: Set<P>,
56        /// The public polynomial, used for the group identity, and partial signatures.
57        polynomial: Sharing<V>,
58        /// Local share used to generate partial signatures.
59        share: Share,
60    },
61    Verifier {
62        /// Participants in the committee.
63        participants: Set<P>,
64        /// The public polynomial, used for the group identity, and partial signatures.
65        polynomial: Sharing<V>,
66    },
67    CertificateVerifier {
68        /// Public identity of the committee (constant across reshares).
69        identity: V::Public,
70    },
71}
72
73impl<P: PublicKey, V: Variant> Scheme<P, V> {
74    /// Constructs a signer instance with a private share and evaluated public polynomial.
75    ///
76    /// The participant identity keys are used for committee ordering and indexing.
77    /// The polynomial can be evaluated to obtain public verification keys for partial
78    /// signatures produced by committee members.
79    ///
80    /// Returns `None` if the share's public key does not match any participant.
81    ///
82    /// * `participants` - ordered set of participant identity keys
83    /// * `polynomial` - public polynomial for threshold verification
84    /// * `share` - local threshold share for signing
85    pub fn signer(participants: Set<P>, polynomial: Sharing<V>, share: Share) -> Option<Self> {
86        assert_eq!(
87            polynomial.total().get() as usize,
88            participants.len(),
89            "polynomial total must equal participant len"
90        );
91        polynomial.precompute_partial_publics();
92        let partial_public = polynomial
93            .partial_public(share.index)
94            .expect("share index must match participant indices");
95        if partial_public == share.public::<V>() {
96            Some(Self::Signer {
97                participants,
98                polynomial,
99                share,
100            })
101        } else {
102            None
103        }
104    }
105
106    /// Produces a verifier that can authenticate votes but does not hold signing state.
107    ///
108    /// The participant identity keys are used for committee ordering and indexing.
109    /// The polynomial can be evaluated to obtain public verification keys for partial
110    /// signatures produced by committee members.
111    ///
112    /// * `participants` - ordered set of participant identity keys
113    /// * `polynomial` - public polynomial for threshold verification
114    pub fn verifier(participants: Set<P>, polynomial: Sharing<V>) -> Self {
115        assert_eq!(
116            polynomial.total().get() as usize,
117            participants.len(),
118            "polynomial total must equal participant len"
119        );
120        polynomial.precompute_partial_publics();
121
122        Self::Verifier {
123            participants,
124            polynomial,
125        }
126    }
127
128    /// Creates a verifier that only checks recovered certificates.
129    ///
130    /// This lightweight verifier can authenticate recovered threshold certificates but cannot
131    /// verify individual votes or partial signatures.
132    ///
133    /// * `identity` - public identity of the committee (constant across reshares)
134    pub const fn certificate_verifier(identity: V::Public) -> Self {
135        Self::CertificateVerifier { identity }
136    }
137
138    /// Returns the ordered set of participant public identity keys in the committee.
139    pub fn participants(&self) -> &Set<P> {
140        match self {
141            Self::Signer { participants, .. } => participants,
142            Self::Verifier { participants, .. } => participants,
143            _ => panic!("can only be called for signer and verifier"),
144        }
145    }
146
147    /// Returns the public identity of the committee (constant across reshares).
148    pub fn identity(&self) -> &V::Public {
149        match self {
150            Self::Signer { polynomial, .. } => polynomial.public(),
151            Self::Verifier { polynomial, .. } => polynomial.public(),
152            Self::CertificateVerifier { identity, .. } => identity,
153        }
154    }
155
156    /// Returns the local share if this instance can generate partial signatures.
157    pub const fn share(&self) -> Option<&Share> {
158        match self {
159            Self::Signer { share, .. } => Some(share),
160            _ => None,
161        }
162    }
163
164    /// Returns the evaluated public polynomial for validating partial signatures produced by committee members.
165    pub fn polynomial(&self) -> &Sharing<V> {
166        match self {
167            Self::Signer { polynomial, .. } => polynomial,
168            Self::Verifier { polynomial, .. } => polynomial,
169            _ => panic!("can only be called for signer and verifier"),
170        }
171    }
172
173    /// Encrypts a message for a target round using Timelock Encryption ([TLE](tle)).
174    ///
175    /// The encrypted message can only be decrypted using the seed signature
176    /// from a certificate of the target round (i.e. notarization, finalization,
177    /// or nullification).
178    pub fn encrypt<R: Rng + CryptoRng>(
179        &self,
180        rng: &mut R,
181        namespace: &[u8],
182        target: Round,
183        message: impl Into<tle::Block>,
184    ) -> tle::Ciphertext<V> {
185        encrypt(rng, *self.identity(), namespace, target, message)
186    }
187}
188
189/// Encrypts a message for a future round using Timelock Encryption ([TLE](tle)).
190///
191/// The encrypted message can only be decrypted using the seed signature
192/// from a certificate of the target round (i.e. notarization, finalization,
193/// or nullification).
194pub fn encrypt<R: Rng + CryptoRng, V: Variant>(
195    rng: &mut R,
196    identity: V::Public,
197    namespace: &[u8],
198    target: Round,
199    message: impl Into<tle::Block>,
200) -> tle::Ciphertext<V> {
201    let block = message.into();
202    let seed_ns = seed_namespace(namespace);
203    let target_message = target.encode();
204    tle::encrypt(rng, identity, (Some(&seed_ns), &target_message), &block)
205}
206
207/// Generates a test fixture with Ed25519 identities and BLS12-381 threshold schemes.
208///
209/// Returns a [`commonware_cryptography::certificate::mocks::Fixture`] whose keys and
210/// scheme instances share a consistent ordering.
211#[cfg(feature = "mocks")]
212pub fn fixture<V, R>(
213    rng: &mut R,
214    n: u32,
215) -> commonware_cryptography::certificate::mocks::Fixture<
216    Scheme<commonware_cryptography::ed25519::PublicKey, V>,
217>
218where
219    V: Variant,
220    R: rand::RngCore + rand::CryptoRng,
221{
222    commonware_cryptography::bls12381::certificate::threshold::mocks::fixture::<_, V, _>(
223        rng,
224        n,
225        Scheme::signer,
226        Scheme::verifier,
227    )
228}
229
230/// Combined vote/seed signature pair emitted by the BLS12-381 threshold scheme.
231#[derive(Clone, Debug, PartialEq, Eq, Hash)]
232pub struct Signature<V: Variant> {
233    /// Signature over the consensus vote message (partial or recovered aggregate).
234    pub vote_signature: V::Signature,
235    /// Signature over the per-view seed (partial or recovered aggregate).
236    pub seed_signature: V::Signature,
237}
238
239impl<V: Variant> Write for Signature<V> {
240    fn write(&self, writer: &mut impl BufMut) {
241        self.vote_signature.write(writer);
242        self.seed_signature.write(writer);
243    }
244}
245
246impl<V: Variant> Read for Signature<V> {
247    type Cfg = ();
248
249    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
250        let vote_signature = V::Signature::read(reader)?;
251        let seed_signature = V::Signature::read(reader)?;
252
253        Ok(Self {
254            vote_signature,
255            seed_signature,
256        })
257    }
258}
259
260impl<V: Variant> FixedSize for Signature<V> {
261    const SIZE: usize = V::Signature::SIZE * 2;
262}
263
264#[cfg(feature = "arbitrary")]
265impl<V: Variant> arbitrary::Arbitrary<'_> for Signature<V>
266where
267    V::Signature: for<'a> arbitrary::Arbitrary<'a>,
268{
269    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
270        Ok(Self {
271            vote_signature: u.arbitrary()?,
272            seed_signature: u.arbitrary()?,
273        })
274    }
275}
276
277/// Seed represents a threshold signature over the current view.
278#[derive(Clone, Debug, PartialEq, Hash, Eq)]
279pub struct Seed<V: Variant> {
280    /// The round for which this seed is generated
281    pub round: Round,
282    /// The threshold signature on the seed.
283    pub signature: V::Signature,
284}
285
286impl<V: Variant> Seed<V> {
287    /// Creates a new seed with the given view and signature.
288    pub const fn new(round: Round, signature: V::Signature) -> Self {
289        Self { round, signature }
290    }
291
292    /// Verifies the threshold signature on this [Seed].
293    pub fn verify<P: PublicKey>(&self, scheme: &Scheme<P, V>, namespace: &[u8]) -> bool {
294        let seed_namespace = seed_namespace(namespace);
295        let seed_message = self.round.encode();
296
297        verify_message::<V>(
298            scheme.identity(),
299            Some(&seed_namespace),
300            &seed_message,
301            &self.signature,
302        )
303        .is_ok()
304    }
305
306    /// Returns the round associated with this seed.
307    pub const fn round(&self) -> Round {
308        self.round
309    }
310
311    /// Decrypts a [TLE](tle) ciphertext using this seed.
312    ///
313    /// Returns `None` if the ciphertext is invalid or encrypted for a different
314    /// round than this seed.
315    pub fn decrypt(&self, ciphertext: &tle::Ciphertext<V>) -> Option<tle::Block> {
316        decrypt(self, ciphertext)
317    }
318}
319
320/// Decrypts a [TLE](tle) ciphertext using the seed from a certificate (i.e.
321/// notarization, finalization, or nullification).
322///
323/// Returns `None` if the ciphertext is invalid or encrypted for a different
324/// round than the given seed.
325pub fn decrypt<V: Variant>(seed: &Seed<V>, ciphertext: &tle::Ciphertext<V>) -> Option<tle::Block> {
326    tle::decrypt(&seed.signature, ciphertext)
327}
328
329impl<V: Variant> Epochable for Seed<V> {
330    fn epoch(&self) -> Epoch {
331        self.round.epoch()
332    }
333}
334
335impl<V: Variant> Viewable for Seed<V> {
336    fn view(&self) -> View {
337        self.round.view()
338    }
339}
340
341impl<V: Variant> Write for Seed<V> {
342    fn write(&self, writer: &mut impl BufMut) {
343        self.round.write(writer);
344        self.signature.write(writer);
345    }
346}
347
348impl<V: Variant> Read for Seed<V> {
349    type Cfg = ();
350
351    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
352        let round = Round::read(reader)?;
353        let signature = V::Signature::read(reader)?;
354
355        Ok(Self { round, signature })
356    }
357}
358
359impl<V: Variant> EncodeSize for Seed<V> {
360    fn encode_size(&self) -> usize {
361        self.round.encode_size() + self.signature.encode_size()
362    }
363}
364
365#[cfg(feature = "arbitrary")]
366impl<V: Variant> arbitrary::Arbitrary<'_> for Seed<V>
367where
368    V::Signature: for<'a> arbitrary::Arbitrary<'a>,
369{
370    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
371        Ok(Self {
372            round: u.arbitrary()?,
373            signature: u.arbitrary()?,
374        })
375    }
376}
377
378/// Seedable is a trait that provides access to the seed associated with a message.
379pub trait Seedable<V: Variant> {
380    /// Returns the seed associated with this object.
381    fn seed(&self) -> Seed<V>;
382}
383
384impl<P: PublicKey, V: Variant, D: Digest> Seedable<V> for Notarization<Scheme<P, V>, D> {
385    fn seed(&self) -> Seed<V> {
386        Seed::new(self.proposal.round, self.certificate.seed_signature)
387    }
388}
389
390impl<P: PublicKey, V: Variant, D: Digest> Seedable<V> for Finalization<Scheme<P, V>, D> {
391    fn seed(&self) -> Seed<V> {
392        Seed::new(self.proposal.round, self.certificate.seed_signature)
393    }
394}
395
396impl<P: PublicKey, V: Variant + Send + Sync> certificate::Scheme for Scheme<P, V> {
397    type Subject<'a, D: Digest> = Subject<'a, D>;
398    type PublicKey = P;
399    type Signature = Signature<V>;
400    type Certificate = Signature<V>;
401
402    fn me(&self) -> Option<u32> {
403        match self {
404            Self::Signer { share, .. } => Some(share.index),
405            _ => None,
406        }
407    }
408
409    fn participants(&self) -> &Set<Self::PublicKey> {
410        self.participants()
411    }
412
413    fn sign<D: Digest>(
414        &self,
415        namespace: &[u8],
416        subject: Subject<'_, D>,
417    ) -> Option<Attestation<Self>> {
418        let share = self.share()?;
419
420        let (vote_namespace, vote_message) = vote_namespace_and_message(namespace, &subject);
421        let vote_signature =
422            partial_sign_message::<V>(share, Some(vote_namespace.as_ref()), vote_message.as_ref())
423                .value;
424
425        let (seed_namespace, seed_message) = seed_namespace_and_message(namespace, &subject);
426        let seed_signature =
427            partial_sign_message::<V>(share, Some(seed_namespace.as_ref()), seed_message.as_ref())
428                .value;
429
430        let signature = Signature {
431            vote_signature,
432            seed_signature,
433        };
434
435        Some(Attestation {
436            signer: share.index,
437            signature,
438        })
439    }
440
441    fn verify_attestation<D: Digest>(
442        &self,
443        namespace: &[u8],
444        subject: Subject<'_, D>,
445        attestation: &Attestation<Self>,
446    ) -> bool {
447        let Ok(evaluated) = self.polynomial().partial_public(attestation.signer) else {
448            return false;
449        };
450
451        let (vote_namespace, vote_message) = vote_namespace_and_message(namespace, &subject);
452        let (seed_namespace, seed_message) = seed_namespace_and_message(namespace, &subject);
453
454        let sig = aggregate_signatures::<V, _>(&[
455            attestation.signature.vote_signature,
456            attestation.signature.seed_signature,
457        ]);
458
459        aggregate_verify_multiple_messages::<V, _>(
460            &evaluated,
461            &[
462                (Some(vote_namespace.as_ref()), vote_message.as_ref()),
463                (Some(seed_namespace.as_ref()), seed_message.as_ref()),
464            ],
465            &sig,
466            1,
467        )
468        .is_ok()
469    }
470
471    fn verify_attestations<R, D, I>(
472        &self,
473        _rng: &mut R,
474        namespace: &[u8],
475        subject: Subject<'_, D>,
476        attestations: I,
477    ) -> Verification<Self>
478    where
479        R: Rng + CryptoRng,
480        D: Digest,
481        I: IntoIterator<Item = Attestation<Self>>,
482    {
483        let mut invalid = BTreeSet::new();
484        let (vote_partials, seed_partials): (Vec<_>, Vec<_>) = attestations
485            .into_iter()
486            .map(|attestation| {
487                (
488                    PartialSignature::<V> {
489                        index: attestation.signer,
490                        value: attestation.signature.vote_signature,
491                    },
492                    PartialSignature::<V> {
493                        index: attestation.signer,
494                        value: attestation.signature.seed_signature,
495                    },
496                )
497            })
498            .unzip();
499
500        let polynomial = self.polynomial();
501        let (vote_namespace, vote_message) = vote_namespace_and_message(namespace, &subject);
502        if let Err(errs) = partial_verify_multiple_public_keys::<V, _>(
503            polynomial,
504            Some(vote_namespace.as_ref()),
505            vote_message.as_ref(),
506            vote_partials.iter(),
507        ) {
508            for partial in errs {
509                invalid.insert(partial.index);
510            }
511        }
512
513        let (seed_namespace, seed_message) = seed_namespace_and_message(namespace, &subject);
514        if let Err(errs) = partial_verify_multiple_public_keys::<V, _>(
515            polynomial,
516            Some(seed_namespace.as_ref()),
517            seed_message.as_ref(),
518            seed_partials
519                .iter()
520                .filter(|partial| !invalid.contains(&partial.index)),
521        ) {
522            for partial in errs {
523                invalid.insert(partial.index);
524            }
525        }
526
527        let verified = vote_partials
528            .into_iter()
529            .zip(seed_partials)
530            .map(|(vote, seed)| Attestation {
531                signer: vote.index,
532                signature: Signature {
533                    vote_signature: vote.value,
534                    seed_signature: seed.value,
535                },
536            })
537            .filter(|attestation| !invalid.contains(&attestation.signer))
538            .collect();
539
540        Verification::new(verified, invalid.into_iter().collect())
541    }
542
543    fn assemble<I>(&self, attestations: I) -> Option<Self::Certificate>
544    where
545        I: IntoIterator<Item = Attestation<Self>>,
546    {
547        let (vote_partials, seed_partials): (Vec<_>, Vec<_>) = attestations
548            .into_iter()
549            .map(|attestation| {
550                (
551                    PartialSignature::<V> {
552                        index: attestation.signer,
553                        value: attestation.signature.vote_signature,
554                    },
555                    PartialSignature::<V> {
556                        index: attestation.signer,
557                        value: attestation.signature.seed_signature,
558                    },
559                )
560            })
561            .unzip();
562
563        let quorum = self.polynomial();
564        if vote_partials.len() < quorum.required() as usize {
565            return None;
566        }
567
568        let (vote_signature, seed_signature) = threshold_signature_recover_pair::<V, _>(
569            quorum,
570            vote_partials.iter(),
571            seed_partials.iter(),
572        )
573        .ok()?;
574
575        Some(Signature {
576            vote_signature,
577            seed_signature,
578        })
579    }
580
581    fn verify_certificate<R: Rng + CryptoRng, D: Digest>(
582        &self,
583        _rng: &mut R,
584        namespace: &[u8],
585        subject: Subject<'_, D>,
586        certificate: &Self::Certificate,
587    ) -> bool {
588        let identity = self.identity();
589
590        let (vote_namespace, vote_message) = vote_namespace_and_message(namespace, &subject);
591        let (seed_namespace, seed_message) = seed_namespace_and_message(namespace, &subject);
592
593        let signature =
594            aggregate_signatures::<V, _>(&[certificate.vote_signature, certificate.seed_signature]);
595
596        aggregate_verify_multiple_messages::<V, _>(
597            identity,
598            &[
599                (Some(vote_namespace.as_ref()), vote_message.as_ref()),
600                (Some(seed_namespace.as_ref()), seed_message.as_ref()),
601            ],
602            &signature,
603            1,
604        )
605        .is_ok()
606    }
607
608    fn verify_certificates<'a, R, D, I>(
609        &self,
610        _rng: &mut R,
611        namespace: &[u8],
612        certificates: I,
613    ) -> bool
614    where
615        R: Rng + CryptoRng,
616        D: Digest,
617        I: Iterator<Item = (Subject<'a, D>, &'a Self::Certificate)>,
618    {
619        let identity = self.identity();
620
621        let mut seeds = HashMap::new();
622        let mut messages = Vec::new();
623        let mut signatures = Vec::new();
624
625        let notarize_namespace = notarize_namespace(namespace);
626        let nullify_namespace = nullify_namespace(namespace);
627        let finalize_namespace = finalize_namespace(namespace);
628        let seed_namespace = seed_namespace(namespace);
629
630        for (context, certificate) in certificates {
631            match context {
632                Subject::Notarize { proposal } => {
633                    // Prepare notarize message
634                    let notarize_message = proposal.encode();
635                    let notarize_message = (Some(notarize_namespace.as_slice()), notarize_message);
636                    messages.push(notarize_message);
637                }
638                Subject::Nullify { round } => {
639                    // Prepare nullify message
640                    let nullify_encoded = round.encode();
641                    let nullify_message =
642                        (Some(nullify_namespace.as_slice()), nullify_encoded.clone());
643                    messages.push(nullify_message);
644                }
645                Subject::Finalize { proposal } => {
646                    // Prepare finalize message
647                    let finalize_message = proposal.encode();
648                    let finalize_message = (Some(finalize_namespace.as_slice()), finalize_message);
649                    messages.push(finalize_message);
650                }
651            }
652            signatures.push(&certificate.vote_signature);
653
654            // Seed signatures are per-view, so multiple certificates for the same view
655            // (e.g., notarization and finalization) share the same seed. We only include
656            // each unique seed once in the aggregate, but verify all certificates for a
657            // view have matching seeds.
658            if let Some(previous) = seeds.get(&context.view()) {
659                if *previous != &certificate.seed_signature {
660                    return false;
661                }
662            } else {
663                let seed_message = match context {
664                    Subject::Notarize { proposal } | Subject::Finalize { proposal } => {
665                        proposal.round.encode()
666                    }
667                    Subject::Nullify { round } => round.encode(),
668                };
669
670                messages.push((Some(seed_namespace.as_slice()), seed_message));
671                signatures.push(&certificate.seed_signature);
672                seeds.insert(context.view(), &certificate.seed_signature);
673            }
674        }
675
676        // Aggregate signatures
677        let signature = aggregate_signatures::<V, _>(signatures);
678        aggregate_verify_multiple_messages::<V, _>(
679            identity,
680            &messages
681                .iter()
682                .map(|(namespace, message)| (namespace.as_deref(), message.as_ref()))
683                .collect::<Vec<_>>(),
684            &signature,
685            1,
686        )
687        .is_ok()
688    }
689
690    fn is_attributable(&self) -> bool {
691        false
692    }
693
694    fn certificate_codec_config(&self) -> <Self::Certificate as Read>::Cfg {}
695
696    fn certificate_codec_config_unbounded() -> <Self::Certificate as Read>::Cfg {}
697}
698
699#[cfg(test)]
700mod tests {
701    use super::*;
702    use crate::{
703        simplex::{
704            scheme::{bls12381_threshold, notarize_namespace, seed_namespace},
705            types::{Finalization, Finalize, Notarization, Notarize, Proposal, Subject},
706        },
707        types::{Round, View},
708    };
709    use commonware_codec::{Decode, Encode};
710    use commonware_cryptography::{
711        bls12381::{
712            dkg::{self, deal_anonymous},
713            primitives::{
714                ops::partial_sign_message,
715                variant::{MinPk, MinSig, Variant},
716            },
717        },
718        certificate::{mocks::Fixture, Scheme as _},
719        ed25519,
720        ed25519::certificate::mocks::participants as ed25519_participants,
721        sha256::Digest as Sha256Digest,
722        Hasher, Sha256,
723    };
724    use commonware_utils::{quorum_from_slice, NZU32};
725    use rand::{rngs::StdRng, thread_rng, SeedableRng};
726
727    const NAMESPACE: &[u8] = b"bls-threshold-signing-scheme";
728
729    type Scheme<V> = super::Scheme<ed25519::PublicKey, V>;
730    type Signature<V> = super::Signature<V>;
731
732    fn setup_signers<V: Variant>(n: u32, seed: u64) -> (Vec<Scheme<V>>, Scheme<V>) {
733        let mut rng = StdRng::seed_from_u64(seed);
734        let Fixture {
735            schemes, verifier, ..
736        } = bls12381_threshold::fixture::<V, _>(&mut rng, n);
737
738        (schemes, verifier)
739    }
740
741    fn sample_proposal(epoch: Epoch, view: View, tag: u8) -> Proposal<Sha256Digest> {
742        Proposal::new(
743            Round::new(epoch, view),
744            view.previous().unwrap(),
745            Sha256::hash(&[tag]),
746        )
747    }
748
749    fn signer_shares_must_match_participant_indices<V: Variant>() {
750        let mut rng = StdRng::seed_from_u64(7);
751        let participants = ed25519_participants(&mut rng, 4);
752        let (polynomial, mut shares) =
753            dkg::deal_anonymous::<V>(&mut rng, Default::default(), NZU32!(4));
754        shares[0].index = 999;
755        Scheme::<V>::signer(participants.keys().clone(), polynomial, shares[0].clone());
756    }
757
758    #[test]
759    #[should_panic(expected = "share index must match participant indices")]
760    fn test_signer_shares_must_match_participant_indices_min_pk() {
761        signer_shares_must_match_participant_indices::<MinPk>();
762    }
763
764    #[test]
765    #[should_panic(expected = "share index must match participant indices")]
766    fn test_signer_shares_must_match_participant_indices_min_sig() {
767        signer_shares_must_match_participant_indices::<MinSig>();
768    }
769    fn scheme_polynomial_threshold_must_equal_quorum<V: Variant>() {
770        let mut rng = StdRng::seed_from_u64(7);
771        let participants = ed25519_participants(&mut rng, 5);
772        let (polynomial, shares) = deal_anonymous::<V>(&mut rng, Default::default(), NZU32!(4));
773        Scheme::<V>::signer(participants.keys().clone(), polynomial, shares[0].clone());
774    }
775
776    #[test]
777    #[should_panic(expected = "polynomial total must equal participant len")]
778    fn test_scheme_polynomial_threshold_must_equal_quorum_min_pk() {
779        scheme_polynomial_threshold_must_equal_quorum::<MinPk>();
780    }
781
782    #[test]
783    #[should_panic(expected = "polynomial total must equal participant len")]
784    fn test_scheme_polynomial_threshold_must_equal_quorum_min_sig() {
785        scheme_polynomial_threshold_must_equal_quorum::<MinSig>();
786    }
787
788    fn verifier_polynomial_threshold_must_equal_quorum<V: Variant>() {
789        let mut rng = StdRng::seed_from_u64(7);
790        let participants = ed25519_participants(&mut rng, 5);
791        let (polynomial, _) = deal_anonymous::<V>(&mut rng, Default::default(), NZU32!(4));
792        Scheme::<V>::verifier(participants.keys().clone(), polynomial);
793    }
794
795    #[test]
796    #[should_panic(expected = "polynomial total must equal participant len")]
797    fn test_verifier_polynomial_threshold_must_equal_quorum_min_pk() {
798        verifier_polynomial_threshold_must_equal_quorum::<MinPk>();
799    }
800
801    #[test]
802    #[should_panic(expected = "polynomial total must equal participant len")]
803    fn test_verifier_polynomial_threshold_must_equal_quorum_min_sig() {
804        verifier_polynomial_threshold_must_equal_quorum::<MinSig>();
805    }
806
807    fn sign_vote_roundtrip_for_each_context<V: Variant>() {
808        let (schemes, _) = setup_signers::<V>(4, 7);
809        let scheme = &schemes[0];
810
811        let proposal = sample_proposal(Epoch::new(0), View::new(2), 1);
812        let notarize_vote = scheme
813            .sign(
814                NAMESPACE,
815                Subject::Notarize {
816                    proposal: &proposal,
817                },
818            )
819            .unwrap();
820        assert!(scheme.verify_attestation(
821            NAMESPACE,
822            Subject::Notarize {
823                proposal: &proposal,
824            },
825            &notarize_vote
826        ));
827
828        let nullify_vote = scheme
829            .sign::<Sha256Digest>(
830                NAMESPACE,
831                Subject::Nullify {
832                    round: proposal.round,
833                },
834            )
835            .unwrap();
836        assert!(scheme.verify_attestation::<Sha256Digest>(
837            NAMESPACE,
838            Subject::Nullify {
839                round: proposal.round,
840            },
841            &nullify_vote
842        ));
843
844        let finalize_vote = scheme
845            .sign(
846                NAMESPACE,
847                Subject::Finalize {
848                    proposal: &proposal,
849                },
850            )
851            .unwrap();
852        assert!(scheme.verify_attestation(
853            NAMESPACE,
854            Subject::Finalize {
855                proposal: &proposal,
856            },
857            &finalize_vote
858        ));
859    }
860
861    #[test]
862    fn test_sign_vote_roundtrip_for_each_context() {
863        sign_vote_roundtrip_for_each_context::<MinPk>();
864        sign_vote_roundtrip_for_each_context::<MinSig>();
865    }
866
867    fn verifier_cannot_sign<V: Variant>() {
868        let (_, verifier) = setup_signers::<V>(4, 11);
869
870        let proposal = sample_proposal(Epoch::new(0), View::new(3), 2);
871        assert!(
872            verifier
873                .sign(
874                    NAMESPACE,
875                    Subject::Notarize {
876                        proposal: &proposal,
877                    },
878                )
879                .is_none(),
880            "verifier should not produce signatures"
881        );
882    }
883
884    #[test]
885    fn test_verifier_cannot_sign() {
886        verifier_cannot_sign::<MinPk>();
887        verifier_cannot_sign::<MinSig>();
888    }
889
890    fn verifier_accepts_votes<V: Variant>() {
891        let (schemes, verifier) = setup_signers::<V>(4, 11);
892        let proposal = sample_proposal(Epoch::new(0), View::new(3), 2);
893        let vote = schemes[1]
894            .sign(
895                NAMESPACE,
896                Subject::Notarize {
897                    proposal: &proposal,
898                },
899            )
900            .unwrap();
901        assert!(verifier.verify_attestation(
902            NAMESPACE,
903            Subject::Notarize {
904                proposal: &proposal,
905            },
906            &vote
907        ));
908    }
909
910    #[test]
911    fn test_verifier_accepts_votes() {
912        verifier_accepts_votes::<MinPk>();
913        verifier_accepts_votes::<MinSig>();
914    }
915
916    fn verify_votes_filters_bad_signers<V: Variant>() {
917        let (schemes, _) = setup_signers::<V>(5, 13);
918        let quorum = quorum_from_slice(&schemes) as usize;
919        let proposal = sample_proposal(Epoch::new(0), View::new(5), 3);
920
921        let mut votes: Vec<_> = schemes
922            .iter()
923            .take(quorum)
924            .map(|scheme| {
925                scheme
926                    .sign(
927                        NAMESPACE,
928                        Subject::Notarize {
929                            proposal: &proposal,
930                        },
931                    )
932                    .unwrap()
933            })
934            .collect();
935
936        let verification = schemes[0].verify_attestations(
937            &mut thread_rng(),
938            NAMESPACE,
939            Subject::Notarize {
940                proposal: &proposal,
941            },
942            votes.clone(),
943        );
944        assert!(verification.invalid.is_empty());
945        assert_eq!(verification.verified.len(), quorum);
946
947        votes[0].signer = 999;
948        let verification = schemes[0].verify_attestations(
949            &mut thread_rng(),
950            NAMESPACE,
951            Subject::Notarize {
952                proposal: &proposal,
953            },
954            votes,
955        );
956        assert_eq!(verification.invalid, vec![999]);
957        assert_eq!(verification.verified.len(), quorum - 1);
958    }
959
960    #[test]
961    fn test_verify_votes_filters_bad_signers() {
962        verify_votes_filters_bad_signers::<MinPk>();
963        verify_votes_filters_bad_signers::<MinSig>();
964    }
965
966    fn assemble_certificate_requires_quorum<V: Variant>() {
967        let (schemes, _) = setup_signers::<V>(4, 17);
968        let quorum = quorum_from_slice(&schemes) as usize;
969        let proposal = sample_proposal(Epoch::new(0), View::new(7), 4);
970
971        let votes: Vec<_> = schemes
972            .iter()
973            .take(quorum - 1)
974            .map(|scheme| {
975                scheme
976                    .sign(
977                        NAMESPACE,
978                        Subject::Notarize {
979                            proposal: &proposal,
980                        },
981                    )
982                    .unwrap()
983            })
984            .collect();
985
986        assert!(schemes[0].assemble(votes).is_none());
987    }
988
989    #[test]
990    fn test_assemble_certificate_requires_quorum() {
991        assemble_certificate_requires_quorum::<MinPk>();
992        assemble_certificate_requires_quorum::<MinSig>();
993    }
994
995    fn verify_certificate<V: Variant>() {
996        let (schemes, verifier) = setup_signers::<V>(4, 19);
997        let quorum = quorum_from_slice(&schemes) as usize;
998        let proposal = sample_proposal(Epoch::new(0), View::new(9), 5);
999
1000        let votes: Vec<_> = schemes
1001            .iter()
1002            .take(quorum)
1003            .map(|scheme| {
1004                scheme
1005                    .sign(
1006                        NAMESPACE,
1007                        Subject::Finalize {
1008                            proposal: &proposal,
1009                        },
1010                    )
1011                    .unwrap()
1012            })
1013            .collect();
1014
1015        let certificate = schemes[0].assemble(votes).expect("assemble certificate");
1016
1017        assert!(verifier.verify_certificate(
1018            &mut thread_rng(),
1019            NAMESPACE,
1020            Subject::Finalize {
1021                proposal: &proposal,
1022            },
1023            &certificate,
1024        ));
1025    }
1026
1027    #[test]
1028    fn test_verify_certificate() {
1029        verify_certificate::<MinPk>();
1030        verify_certificate::<MinSig>();
1031    }
1032
1033    fn verify_certificate_detects_corruption<V: Variant>() {
1034        let (schemes, verifier) = setup_signers::<V>(4, 23);
1035        let quorum = quorum_from_slice(&schemes) as usize;
1036        let proposal = sample_proposal(Epoch::new(0), View::new(11), 6);
1037
1038        let votes: Vec<_> = schemes
1039            .iter()
1040            .take(quorum)
1041            .map(|scheme| {
1042                scheme
1043                    .sign(
1044                        NAMESPACE,
1045                        Subject::Notarize {
1046                            proposal: &proposal,
1047                        },
1048                    )
1049                    .unwrap()
1050            })
1051            .collect();
1052
1053        let certificate = schemes[0].assemble(votes).expect("assemble certificate");
1054
1055        assert!(verifier.verify_certificate(
1056            &mut thread_rng(),
1057            NAMESPACE,
1058            Subject::Notarize {
1059                proposal: &proposal,
1060            },
1061            &certificate,
1062        ));
1063
1064        let mut corrupted = certificate;
1065        corrupted.vote_signature = corrupted.seed_signature;
1066        assert!(!verifier.verify_certificate(
1067            &mut thread_rng(),
1068            NAMESPACE,
1069            Subject::Notarize {
1070                proposal: &proposal,
1071            },
1072            &corrupted,
1073        ));
1074    }
1075
1076    #[test]
1077    fn test_verify_certificate_detects_corruption() {
1078        verify_certificate_detects_corruption::<MinPk>();
1079        verify_certificate_detects_corruption::<MinSig>();
1080    }
1081
1082    fn certificate_codec_roundtrip<V: Variant>() {
1083        let (schemes, _) = setup_signers::<V>(5, 29);
1084        let quorum = quorum_from_slice(&schemes) as usize;
1085        let proposal = sample_proposal(Epoch::new(0), View::new(13), 7);
1086
1087        let votes: Vec<_> = schemes
1088            .iter()
1089            .take(quorum)
1090            .map(|scheme| {
1091                scheme
1092                    .sign(
1093                        NAMESPACE,
1094                        Subject::Notarize {
1095                            proposal: &proposal,
1096                        },
1097                    )
1098                    .unwrap()
1099            })
1100            .collect();
1101
1102        let certificate = schemes[0].assemble(votes).expect("assemble certificate");
1103
1104        let encoded = certificate.encode();
1105        let decoded =
1106            Signature::<V>::decode_cfg(encoded.freeze(), &()).expect("decode certificate");
1107        assert_eq!(decoded, certificate);
1108    }
1109
1110    #[test]
1111    fn test_certificate_codec_roundtrip() {
1112        certificate_codec_roundtrip::<MinPk>();
1113        certificate_codec_roundtrip::<MinSig>();
1114    }
1115
1116    fn seed_codec_roundtrip<V: Variant>() {
1117        let (schemes, _) = setup_signers::<V>(4, 5);
1118        let quorum = quorum_from_slice(&schemes) as usize;
1119        let proposal = sample_proposal(Epoch::new(0), View::new(1), 0);
1120
1121        let votes: Vec<_> = schemes
1122            .iter()
1123            .take(quorum)
1124            .map(|scheme| {
1125                scheme
1126                    .sign(
1127                        NAMESPACE,
1128                        Subject::Finalize {
1129                            proposal: &proposal,
1130                        },
1131                    )
1132                    .unwrap()
1133            })
1134            .collect();
1135
1136        let certificate = schemes[0].assemble(votes).expect("assemble certificate");
1137
1138        let seed = Seed::new(proposal.round, certificate.seed_signature);
1139
1140        let encoded = seed.encode();
1141        let decoded = Seed::<V>::decode_cfg(encoded, &()).expect("decode seed");
1142        assert_eq!(decoded, seed);
1143    }
1144
1145    #[test]
1146    fn test_seed_codec_roundtrip() {
1147        seed_codec_roundtrip::<MinPk>();
1148        seed_codec_roundtrip::<MinSig>();
1149    }
1150
1151    fn seed_verify<V: Variant>() {
1152        let (schemes, _) = setup_signers::<V>(4, 5);
1153        let quorum = quorum_from_slice(&schemes) as usize;
1154        let proposal = sample_proposal(Epoch::new(0), View::new(1), 0);
1155
1156        let votes: Vec<_> = schemes
1157            .iter()
1158            .take(quorum)
1159            .map(|scheme| {
1160                scheme
1161                    .sign(
1162                        NAMESPACE,
1163                        Subject::Finalize {
1164                            proposal: &proposal,
1165                        },
1166                    )
1167                    .unwrap()
1168            })
1169            .collect();
1170
1171        let certificate = schemes[0].assemble(votes).expect("assemble certificate");
1172
1173        let seed = Seed::new(proposal.round, certificate.seed_signature);
1174
1175        assert!(seed.verify(&schemes[0], NAMESPACE));
1176
1177        // Create an invalid seed with a mismatched round
1178        let invalid_seed = Seed::new(
1179            Round::new(proposal.epoch(), proposal.view().next()),
1180            certificate.seed_signature,
1181        );
1182
1183        assert!(!invalid_seed.verify(&schemes[0], NAMESPACE));
1184    }
1185
1186    #[test]
1187    fn test_seed_verify() {
1188        seed_verify::<MinPk>();
1189        seed_verify::<MinSig>();
1190    }
1191
1192    fn seedable<V: Variant>() {
1193        let (schemes, _) = setup_signers::<V>(4, 5);
1194        let quorum = quorum_from_slice(&schemes) as usize;
1195        let proposal = sample_proposal(Epoch::new(0), View::new(1), 0);
1196
1197        let notarizes: Vec<_> = schemes
1198            .iter()
1199            .take(quorum)
1200            .map(|scheme| Notarize::sign(scheme, NAMESPACE, proposal.clone()).unwrap())
1201            .collect();
1202
1203        let notarization = Notarization::from_notarizes(&schemes[0], &notarizes).unwrap();
1204
1205        let finalizes: Vec<_> = schemes
1206            .iter()
1207            .take(quorum)
1208            .map(|scheme| Finalize::sign(scheme, NAMESPACE, proposal.clone()).unwrap())
1209            .collect();
1210
1211        let finalization = Finalization::from_finalizes(&schemes[0], &finalizes).unwrap();
1212
1213        assert_eq!(notarization.seed(), finalization.seed());
1214        assert!(notarization.seed().verify(&schemes[0], NAMESPACE));
1215    }
1216
1217    #[test]
1218    fn test_seedable() {
1219        seedable::<MinPk>();
1220        seedable::<MinSig>();
1221    }
1222
1223    fn scheme_clone_and_verifier<V: Variant>() {
1224        let (schemes, verifier) = setup_signers::<V>(4, 31);
1225        let signer = schemes[0].clone();
1226        let proposal = sample_proposal(Epoch::new(0), View::new(21), 9);
1227
1228        assert!(
1229            signer
1230                .sign(
1231                    NAMESPACE,
1232                    Subject::Notarize {
1233                        proposal: &proposal,
1234                    },
1235                )
1236                .is_some(),
1237            "signer should produce votes"
1238        );
1239
1240        assert!(
1241            verifier
1242                .sign(
1243                    NAMESPACE,
1244                    Subject::Notarize {
1245                        proposal: &proposal,
1246                    },
1247                )
1248                .is_none(),
1249            "verifier should not produce votes"
1250        );
1251    }
1252
1253    #[test]
1254    fn test_scheme_clone_and_verifier() {
1255        scheme_clone_and_verifier::<MinPk>();
1256        scheme_clone_and_verifier::<MinSig>();
1257    }
1258
1259    fn certificate_verifier_accepts_certificates<V: Variant>() {
1260        let (schemes, _) = setup_signers::<V>(4, 37);
1261        let quorum = quorum_from_slice(&schemes) as usize;
1262        let proposal = sample_proposal(Epoch::new(0), View::new(15), 8);
1263
1264        let votes: Vec<_> = schemes
1265            .iter()
1266            .take(quorum)
1267            .map(|scheme| {
1268                scheme
1269                    .sign(
1270                        NAMESPACE,
1271                        Subject::Finalize {
1272                            proposal: &proposal,
1273                        },
1274                    )
1275                    .unwrap()
1276            })
1277            .collect();
1278
1279        let certificate = schemes[0].assemble(votes).expect("assemble certificate");
1280
1281        let certificate_verifier = Scheme::<V>::certificate_verifier(*schemes[0].identity());
1282        assert!(
1283            certificate_verifier
1284                .sign(
1285                    NAMESPACE,
1286                    Subject::Finalize {
1287                        proposal: &proposal,
1288                    },
1289                )
1290                .is_none(),
1291            "certificate verifier should not produce votes"
1292        );
1293        assert!(certificate_verifier.verify_certificate(
1294            &mut thread_rng(),
1295            NAMESPACE,
1296            Subject::Finalize {
1297                proposal: &proposal,
1298            },
1299            &certificate,
1300        ));
1301    }
1302
1303    #[test]
1304    fn test_certificate_verifier_accepts_certificates() {
1305        certificate_verifier_accepts_certificates::<MinPk>();
1306        certificate_verifier_accepts_certificates::<MinSig>();
1307    }
1308
1309    fn certificate_verifier_panics_on_vote<V: Variant>() {
1310        let (schemes, _) = setup_signers::<V>(4, 37);
1311        let certificate_verifier = Scheme::<V>::certificate_verifier(*schemes[0].identity());
1312        let proposal = sample_proposal(Epoch::new(0), View::new(15), 8);
1313        let vote = schemes[1]
1314            .sign(
1315                NAMESPACE,
1316                Subject::Finalize {
1317                    proposal: &proposal,
1318                },
1319            )
1320            .unwrap();
1321
1322        certificate_verifier.verify_attestation(
1323            NAMESPACE,
1324            Subject::Finalize {
1325                proposal: &proposal,
1326            },
1327            &vote,
1328        );
1329    }
1330
1331    #[test]
1332    #[should_panic(expected = "can only be called for signer and verifier")]
1333    fn test_certificate_verifier_panics_on_vote_min_pk() {
1334        certificate_verifier_panics_on_vote::<MinPk>();
1335    }
1336
1337    #[test]
1338    #[should_panic(expected = "can only be called for signer and verifier")]
1339    fn test_certificate_verifier_panics_on_vote_min_sig() {
1340        certificate_verifier_panics_on_vote::<MinSig>();
1341    }
1342
1343    fn verify_certificate_returns_seed_randomness<V: Variant>() {
1344        let (schemes, _) = setup_signers::<V>(4, 43);
1345        let quorum = quorum_from_slice(&schemes) as usize;
1346        let proposal = sample_proposal(Epoch::new(0), View::new(19), 10);
1347
1348        let votes: Vec<_> = schemes
1349            .iter()
1350            .take(quorum)
1351            .map(|scheme| {
1352                scheme
1353                    .sign(
1354                        NAMESPACE,
1355                        Subject::Notarize {
1356                            proposal: &proposal,
1357                        },
1358                    )
1359                    .unwrap()
1360            })
1361            .collect();
1362
1363        let certificate = schemes[0].assemble(votes).expect("assemble certificate");
1364
1365        let seed = Seed::<V>::new(proposal.round, certificate.seed_signature);
1366        assert_eq!(seed.signature, certificate.seed_signature);
1367    }
1368
1369    #[test]
1370    fn test_verify_certificate_returns_seed_randomness() {
1371        verify_certificate_returns_seed_randomness::<MinPk>();
1372        verify_certificate_returns_seed_randomness::<MinSig>();
1373    }
1374
1375    fn certificate_decode_rejects_length_mismatch<V: Variant>() {
1376        let (schemes, _) = setup_signers::<V>(4, 47);
1377        let quorum = quorum_from_slice(&schemes) as usize;
1378        let proposal = sample_proposal(Epoch::new(0), View::new(21), 11);
1379
1380        let votes: Vec<_> = schemes
1381            .iter()
1382            .take(quorum)
1383            .map(|scheme| {
1384                scheme
1385                    .sign::<Sha256Digest>(
1386                        NAMESPACE,
1387                        Subject::Nullify {
1388                            round: proposal.round,
1389                        },
1390                    )
1391                    .unwrap()
1392            })
1393            .collect();
1394
1395        let certificate = schemes[0].assemble(votes).expect("assemble certificate");
1396
1397        let mut encoded = certificate.encode().freeze();
1398        let truncated = encoded.split_to(encoded.len() - 1);
1399        assert!(Signature::<V>::decode_cfg(truncated, &()).is_err());
1400    }
1401
1402    #[test]
1403    fn test_certificate_decode_rejects_length_mismatch() {
1404        certificate_decode_rejects_length_mismatch::<MinPk>();
1405        certificate_decode_rejects_length_mismatch::<MinSig>();
1406    }
1407
1408    fn sign_vote_partial_matches_share<V: Variant>() {
1409        let (schemes, _) = setup_signers::<V>(4, 53);
1410        let scheme = &schemes[0];
1411        let share = scheme.share().expect("has share");
1412
1413        let proposal = sample_proposal(Epoch::new(0), View::new(23), 12);
1414        let vote = scheme
1415            .sign(
1416                NAMESPACE,
1417                Subject::Notarize {
1418                    proposal: &proposal,
1419                },
1420            )
1421            .unwrap();
1422
1423        let notarize_namespace = notarize_namespace(NAMESPACE);
1424        let notarize_message = proposal.encode();
1425        let expected_message = partial_sign_message::<V>(
1426            share,
1427            Some(notarize_namespace.as_ref()),
1428            notarize_message.as_ref(),
1429        )
1430        .value;
1431
1432        let seed_namespace = seed_namespace(NAMESPACE);
1433        let seed_message = proposal.round.encode();
1434        let expected_seed =
1435            partial_sign_message::<V>(share, Some(seed_namespace.as_ref()), seed_message.as_ref())
1436                .value;
1437
1438        assert_eq!(vote.signer, share.index);
1439        assert_eq!(vote.signature.vote_signature, expected_message);
1440        assert_eq!(vote.signature.seed_signature, expected_seed);
1441    }
1442
1443    #[test]
1444    fn test_sign_vote_partial_matches_share() {
1445        sign_vote_partial_matches_share::<MinPk>();
1446        sign_vote_partial_matches_share::<MinSig>();
1447    }
1448
1449    fn verify_certificate_detects_seed_corruption<V: Variant>() {
1450        let (schemes, verifier) = setup_signers::<V>(4, 59);
1451        let quorum = quorum_from_slice(&schemes) as usize;
1452        let proposal = sample_proposal(Epoch::new(0), View::new(25), 13);
1453
1454        let votes: Vec<_> = schemes
1455            .iter()
1456            .take(quorum)
1457            .map(|scheme| {
1458                scheme
1459                    .sign::<Sha256Digest>(
1460                        NAMESPACE,
1461                        Subject::Nullify {
1462                            round: proposal.round,
1463                        },
1464                    )
1465                    .unwrap()
1466            })
1467            .collect();
1468
1469        let certificate = schemes[0].assemble(votes).expect("assemble certificate");
1470
1471        assert!(verifier.verify_certificate::<_, Sha256Digest>(
1472            &mut thread_rng(),
1473            NAMESPACE,
1474            Subject::Nullify {
1475                round: proposal.round,
1476            },
1477            &certificate,
1478        ));
1479
1480        let mut corrupted = certificate;
1481        corrupted.seed_signature = corrupted.vote_signature;
1482        assert!(!verifier.verify_certificate::<_, Sha256Digest>(
1483            &mut thread_rng(),
1484            NAMESPACE,
1485            Subject::Nullify {
1486                round: proposal.round,
1487            },
1488            &corrupted,
1489        ));
1490    }
1491
1492    #[test]
1493    fn test_verify_certificate_detects_seed_corruption() {
1494        verify_certificate_detects_seed_corruption::<MinPk>();
1495        verify_certificate_detects_seed_corruption::<MinSig>();
1496    }
1497
1498    fn encrypt_decrypt<V: Variant>() {
1499        let (schemes, verifier) = setup_signers::<V>(4, 61);
1500        let quorum = quorum_from_slice(&schemes) as usize;
1501
1502        // Prepare a message to encrypt
1503        let message = b"Secret message for future view10";
1504
1505        // Target round for encryption
1506        let target = Round::new(Epoch::new(333), View::new(10));
1507
1508        // Encrypt using the scheme
1509        let ciphertext = schemes[0].encrypt(&mut thread_rng(), NAMESPACE, target, *message);
1510
1511        // Can also encrypt with the verifier scheme
1512        let ciphertext_verifier = verifier.encrypt(&mut thread_rng(), NAMESPACE, target, *message);
1513
1514        // Generate notarization for the target round to get the seed
1515        let proposal = sample_proposal(target.epoch(), target.view(), 14);
1516        let notarizes: Vec<_> = schemes
1517            .iter()
1518            .take(quorum)
1519            .map(|scheme| Notarize::sign(scheme, NAMESPACE, proposal.clone()).unwrap())
1520            .collect();
1521
1522        let notarization = Notarization::from_notarizes(&schemes[0], &notarizes).unwrap();
1523
1524        // Decrypt using the seed
1525        let seed = notarization.seed();
1526        let decrypted = seed.decrypt(&ciphertext).unwrap();
1527        assert_eq!(message, decrypted.as_ref());
1528
1529        let decrypted_verifier = seed.decrypt(&ciphertext_verifier).unwrap();
1530        assert_eq!(message, decrypted_verifier.as_ref());
1531    }
1532
1533    #[test]
1534    fn test_encrypt_decrypt() {
1535        encrypt_decrypt::<MinPk>();
1536        encrypt_decrypt::<MinSig>();
1537    }
1538
1539    #[cfg(feature = "arbitrary")]
1540    mod conformance {
1541        use super::*;
1542        use commonware_codec::conformance::CodecConformance;
1543
1544        commonware_conformance::conformance_tests! {
1545            CodecConformance<Signature<MinSig>>,
1546            CodecConformance<Seed<MinSig>>,
1547        }
1548    }
1549}