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