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