Skip to main content

commonware_consensus/simplex/scheme/bls12381_threshold/
vrf.rs

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