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