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;
63use rand_core::{CryptoRng, SeedableRng};
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: CryptoRng>(
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: CryptoRng, 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_core::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::Verifier for Scheme<P, V> {
553    type Subject<'a, D: Digest> = Subject<'a, D>;
554    type PublicKey = P;
555    type Certificate = Certificate<V>;
556
557    fn verify_certificate<R, D, M>(
558        &self,
559        rng: &mut R,
560        subject: Subject<'_, D>,
561        certificate: &Self::Certificate,
562        strategy: &impl Strategy,
563    ) -> bool
564    where
565        R: CryptoRng,
566        D: Digest,
567        M: Faults,
568    {
569        let Some(cert) = certificate.get() else {
570            return false;
571        };
572
573        let identity = self.identity();
574        let namespace = self.namespace();
575
576        let vote_namespace = subject.namespace(namespace);
577        let vote_message = subject.message();
578        let seed_message = seed_message_from_subject(&subject);
579
580        let entries = &[
581            (vote_namespace, vote_message.as_ref(), cert.vote_signature),
582            (&namespace.seed, seed_message.as_ref(), cert.seed_signature),
583        ];
584        batch::verify_same_signer::<_, V, _>(rng, identity, entries, strategy).is_ok()
585    }
586
587    fn verify_certificates<'a, R, D, I, M>(
588        &self,
589        rng: &mut R,
590        certificates: I,
591        strategy: &impl Strategy,
592    ) -> bool
593    where
594        R: CryptoRng,
595        D: Digest,
596        I: Iterator<Item = (Subject<'a, D>, &'a Self::Certificate)>,
597        M: Faults,
598    {
599        let identity = self.identity();
600        let namespace = self.namespace();
601
602        let mut seeds = HashMap::new();
603        let mut entries: Vec<_> = Vec::new();
604
605        for (context, certificate) in certificates {
606            let Some(cert) = certificate.get() else {
607                return false;
608            };
609
610            // Prepare vote message with context-specific namespace
611            let vote_namespace = context.namespace(namespace);
612            let vote_message = context.message();
613            entries.push((vote_namespace, vote_message, cert.vote_signature));
614
615            // Seed signatures are per-round, so multiple certificates for the same round
616            // (e.g., notarization and finalization) share the same seed. We only include
617            // each unique seed once in the aggregate, but verify all certificates for a
618            // round have matching seeds.
619            let seed_message = seed_message_from_subject(&context);
620            if let Some(previous) = seeds.get(&seed_message) {
621                if *previous != cert.seed_signature {
622                    return false;
623                }
624            } else {
625                entries.push((&namespace.seed, seed_message.clone(), cert.seed_signature));
626                seeds.insert(seed_message, cert.seed_signature);
627            }
628        }
629
630        // We care about the correctness of each signature, so we use batch verification rather
631        // than computing the aggregate signature and verifying it.
632        let entries_refs: Vec<_> = entries
633            .iter()
634            .map(|(ns, msg, sig)| (*ns, msg.as_ref(), *sig))
635            .collect();
636        batch::verify_same_signer::<_, V, _>(rng, identity, &entries_refs, strategy).is_ok()
637    }
638
639    fn is_batchable() -> bool {
640        true
641    }
642
643    fn certificate_codec_config(&self) -> <Self::Certificate as Read>::Cfg {}
644
645    fn certificate_codec_config_unbounded() -> <Self::Certificate as Read>::Cfg {}
646}
647
648impl<P: PublicKey, V: Variant> certificate::Scheme for Scheme<P, V> {
649    type Signature = Signature<V>;
650
651    fn me(&self) -> Option<Participant> {
652        match &self.role {
653            Role::Signer { share, .. } => Some(share.index),
654            _ => None,
655        }
656    }
657
658    fn participants(&self) -> &Set<Self::PublicKey> {
659        self.participants()
660    }
661
662    fn sign<D: Digest>(&self, subject: Subject<'_, D>) -> Option<Attestation<Self>> {
663        let share = self.share()?;
664
665        let namespace = self.namespace();
666        let vote_namespace = subject.namespace(namespace);
667        let vote_message = subject.message();
668        let vote_signature =
669            threshold::sign_message::<V>(share, vote_namespace, &vote_message).value;
670
671        let seed_message = seed_message_from_subject(&subject);
672        let seed_signature =
673            threshold::sign_message::<V>(share, &namespace.seed, &seed_message).value;
674
675        let signature = Signature {
676            vote_signature,
677            seed_signature,
678        };
679
680        Some(Attestation {
681            signer: share.index,
682            signature: signature.into(),
683        })
684    }
685
686    fn verify_attestation<R, D>(
687        &self,
688        rng: &mut R,
689        subject: Subject<'_, D>,
690        attestation: &Attestation<Self>,
691        strategy: &impl Strategy,
692    ) -> bool
693    where
694        R: CryptoRng,
695        D: Digest,
696    {
697        let Ok(evaluated) = self.polynomial().partial_public(attestation.signer) else {
698            return false;
699        };
700
701        let namespace = self.namespace();
702        let vote_namespace = subject.namespace(namespace);
703        let vote_message = subject.message();
704        let seed_message = seed_message_from_subject(&subject);
705
706        let Some(signature) = attestation.signature.get() else {
707            return false;
708        };
709
710        let entries = &[
711            (
712                vote_namespace,
713                vote_message.as_ref(),
714                signature.vote_signature,
715            ),
716            (
717                &namespace.seed,
718                seed_message.as_ref(),
719                signature.seed_signature,
720            ),
721        ];
722        batch::verify_same_signer::<_, V, _>(rng, &evaluated, entries, strategy).is_ok()
723    }
724
725    fn verify_attestations<R, D, I>(
726        &self,
727        rng: &mut R,
728        subject: Subject<'_, D>,
729        attestations: I,
730        strategy: &impl Strategy,
731    ) -> Verification<Self>
732    where
733        R: CryptoRng,
734        D: Digest,
735        I: IntoIterator<Item = Attestation<Self>>,
736        I::IntoIter: Send,
737    {
738        let namespace = self.namespace();
739        let (partials, failures) =
740            strategy.map_partition_collect_vec(attestations.into_iter(), |attestation| {
741                let index = attestation.signer;
742                let value = attestation.signature.get().map(|sig| {
743                    (
744                        PartialSignature::<V> {
745                            index,
746                            value: sig.vote_signature,
747                        },
748                        PartialSignature::<V> {
749                            index,
750                            value: sig.seed_signature,
751                        },
752                    )
753                });
754                (index, value)
755            });
756
757        let polynomial = self.polynomial();
758        let vote_namespace = subject.namespace(namespace);
759        let vote_message = subject.message();
760        let seed_message = seed_message_from_subject(&subject);
761
762        // Generate independent RNG seeds for concurrent verification
763        let mut vote_rng_seed = [0u8; 32];
764        let mut seed_rng_seed = [0u8; 32];
765        rng.fill_bytes(&mut vote_rng_seed);
766        rng.fill_bytes(&mut seed_rng_seed);
767
768        // Verify vote and seed signatures concurrently.
769        let (vote_invalid, seed_invalid) = strategy.join(
770            || {
771                let mut vote_rng = StdRng::from_seed(vote_rng_seed);
772                match threshold::batch_verify_same_message::<_, V, _>(
773                    &mut vote_rng,
774                    polynomial,
775                    vote_namespace,
776                    &vote_message,
777                    partials.iter().map(|(vote, _)| vote),
778                    strategy,
779                ) {
780                    Ok(()) => BTreeSet::new(),
781                    Err(errs) => errs.into_iter().map(|p| p.index).collect(),
782                }
783            },
784            || {
785                let mut seed_rng = StdRng::from_seed(seed_rng_seed);
786                match threshold::batch_verify_same_message::<_, V, _>(
787                    &mut seed_rng,
788                    polynomial,
789                    &namespace.seed,
790                    &seed_message,
791                    partials.iter().map(|(_, seed)| seed),
792                    strategy,
793                ) {
794                    Ok(()) => BTreeSet::new(),
795                    Err(errs) => errs.into_iter().map(|p| p.index).collect(),
796                }
797            },
798        );
799
800        // Merge invalid sets and add decode failures
801        let mut invalid: BTreeSet<_> = vote_invalid.union(&seed_invalid).copied().collect();
802        invalid.extend(failures);
803
804        // Filter out cryptographically invalid signatures (partials only excludes decode failures)
805        let verified = partials
806            .into_iter()
807            .filter(|(vote, _)| !invalid.contains(&vote.index))
808            .map(|(vote, seed)| Attestation {
809                signer: vote.index,
810                signature: Signature {
811                    vote_signature: vote.value,
812                    seed_signature: seed.value,
813                }
814                .into(),
815            })
816            .collect();
817
818        Verification::new(verified, invalid.into_iter().collect())
819    }
820
821    fn assemble<I, M>(&self, attestations: I, strategy: &impl Strategy) -> Option<Self::Certificate>
822    where
823        I: IntoIterator<Item = Attestation<Self>>,
824        I::IntoIter: Send,
825        M: Faults,
826    {
827        let (partials, failures) =
828            strategy.map_partition_collect_vec(attestations.into_iter(), |attestation| {
829                let index = attestation.signer;
830                let value = attestation.signature.get().map(|sig| {
831                    (
832                        PartialSignature::<V> {
833                            index,
834                            value: sig.vote_signature,
835                        },
836                        PartialSignature::<V> {
837                            index,
838                            value: sig.seed_signature,
839                        },
840                    )
841                });
842                (index, value)
843            });
844        if !failures.is_empty() {
845            return None;
846        }
847        let (vote_partials, seed_partials): (Vec<_>, Vec<_>) = partials.into_iter().unzip();
848
849        let quorum = self.polynomial();
850        if vote_partials.len() < quorum.required::<M>() as usize {
851            return None;
852        }
853
854        let (vote_signature, seed_signature) = threshold::recover_pair::<V, _, M>(
855            quorum,
856            vote_partials.iter(),
857            seed_partials.iter(),
858            strategy,
859        )
860        .ok()?;
861
862        Some(
863            Signature {
864                vote_signature,
865                seed_signature,
866            }
867            .into(),
868        )
869    }
870
871    fn is_attributable() -> bool {
872        false
873    }
874}
875
876#[cfg(test)]
877mod tests {
878    use super::*;
879    use crate::{
880        simplex::{
881            scheme::{notarize_namespace, seed_namespace},
882            types::{Finalization, Finalize, Notarization, Notarize, Proposal, Subject},
883        },
884        types::{Round, View},
885    };
886    use commonware_codec::{Decode, Encode};
887    use commonware_cryptography::{
888        bls12381::{
889            dkg::feldman_desmedt as dkg,
890            primitives::{
891                group::Scalar,
892                ops::threshold,
893                variant::{MinPk, MinSig, Variant},
894            },
895        },
896        certificate::{mocks::Fixture, Scheme as _, Verifier as _},
897        ed25519,
898        ed25519::certificate::mocks::participants as ed25519_participants,
899        sha256::Digest as Sha256Digest,
900        Hasher, Sha256,
901    };
902    use commonware_math::algebra::{CryptoGroup, Random};
903    use commonware_parallel::Sequential;
904    use commonware_utils::{test_rng, Faults, N3f1, NZU32};
905    use rand::{rngs::StdRng, SeedableRng};
906
907    const NAMESPACE: &[u8] = b"bls-threshold-signing-scheme";
908
909    type Scheme<V> = super::Scheme<ed25519::PublicKey, V>;
910    type Signature<V> = super::Signature<V>;
911
912    fn setup_signers<V: Variant>(n: u32, seed: u64) -> (Vec<Scheme<V>>, Scheme<V>) {
913        let mut rng = StdRng::seed_from_u64(seed);
914        let Fixture {
915            schemes, verifier, ..
916        } = fixture::<V, _>(&mut rng, NAMESPACE, n);
917
918        (schemes, verifier)
919    }
920
921    fn sample_proposal(epoch: Epoch, view: View, tag: u8) -> Proposal<Sha256Digest> {
922        Proposal::new(
923            Round::new(epoch, view),
924            view.previous().unwrap(),
925            Sha256::hash(&[tag]),
926        )
927    }
928
929    fn signer_shares_must_match_participant_indices<V: Variant>() {
930        let mut rng = test_rng();
931        let participants = ed25519_participants(&mut rng, 4);
932        let (polynomial, mut shares) =
933            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(4));
934        shares[0].index = Participant::new(999);
935        Scheme::<V>::signer(
936            NAMESPACE,
937            participants.keys().clone(),
938            polynomial,
939            shares[0].clone(),
940        );
941    }
942
943    #[test]
944    #[should_panic(expected = "share index must match participant indices")]
945    fn test_signer_shares_must_match_participant_indices_min_pk() {
946        signer_shares_must_match_participant_indices::<MinPk>();
947    }
948
949    #[test]
950    #[should_panic(expected = "share index must match participant indices")]
951    fn test_signer_shares_must_match_participant_indices_min_sig() {
952        signer_shares_must_match_participant_indices::<MinSig>();
953    }
954    fn scheme_polynomial_threshold_must_equal_quorum<V: Variant>() {
955        let mut rng = test_rng();
956        let participants = ed25519_participants(&mut rng, 5);
957        let (polynomial, shares) =
958            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(4));
959        Scheme::<V>::signer(
960            NAMESPACE,
961            participants.keys().clone(),
962            polynomial,
963            shares[0].clone(),
964        );
965    }
966
967    #[test]
968    #[should_panic(expected = "polynomial total must equal participant len")]
969    fn test_scheme_polynomial_threshold_must_equal_quorum_min_pk() {
970        scheme_polynomial_threshold_must_equal_quorum::<MinPk>();
971    }
972
973    #[test]
974    #[should_panic(expected = "polynomial total must equal participant len")]
975    fn test_scheme_polynomial_threshold_must_equal_quorum_min_sig() {
976        scheme_polynomial_threshold_must_equal_quorum::<MinSig>();
977    }
978
979    fn verifier_polynomial_threshold_must_equal_quorum<V: Variant>() {
980        let mut rng = test_rng();
981        let participants = ed25519_participants(&mut rng, 5);
982        let (polynomial, _) =
983            dkg::deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(4));
984        Scheme::<V>::verifier(NAMESPACE, participants.keys().clone(), polynomial);
985    }
986
987    #[test]
988    #[should_panic(expected = "polynomial total must equal participant len")]
989    fn test_verifier_polynomial_threshold_must_equal_quorum_min_pk() {
990        verifier_polynomial_threshold_must_equal_quorum::<MinPk>();
991    }
992
993    #[test]
994    #[should_panic(expected = "polynomial total must equal participant len")]
995    fn test_verifier_polynomial_threshold_must_equal_quorum_min_sig() {
996        verifier_polynomial_threshold_must_equal_quorum::<MinSig>();
997    }
998
999    #[test]
1000    fn test_is_not_attributable() {
1001        assert!(!Scheme::<MinPk>::is_attributable());
1002        assert!(!Scheme::<MinSig>::is_attributable());
1003    }
1004
1005    #[test]
1006    fn test_is_batchable() {
1007        assert!(Scheme::<MinPk>::is_batchable());
1008        assert!(Scheme::<MinSig>::is_batchable());
1009    }
1010
1011    fn sign_vote_roundtrip_for_each_context<V: Variant>() {
1012        let (schemes, _) = setup_signers::<V>(4, 7);
1013        let scheme = &schemes[0];
1014        let mut rng = test_rng();
1015
1016        let proposal = sample_proposal(Epoch::new(0), View::new(2), 1);
1017        let notarize_vote = scheme
1018            .sign(Subject::Notarize {
1019                proposal: &proposal,
1020            })
1021            .unwrap();
1022        assert!(scheme.verify_attestation::<_, Sha256Digest>(
1023            &mut rng,
1024            Subject::Notarize {
1025                proposal: &proposal,
1026            },
1027            &notarize_vote,
1028            &Sequential,
1029        ));
1030
1031        let nullify_vote = scheme
1032            .sign::<Sha256Digest>(Subject::Nullify {
1033                round: proposal.round,
1034            })
1035            .unwrap();
1036        assert!(scheme.verify_attestation::<_, Sha256Digest>(
1037            &mut rng,
1038            Subject::Nullify {
1039                round: proposal.round,
1040            },
1041            &nullify_vote,
1042            &Sequential,
1043        ));
1044
1045        let finalize_vote = scheme
1046            .sign(Subject::Finalize {
1047                proposal: &proposal,
1048            })
1049            .unwrap();
1050        assert!(scheme.verify_attestation::<_, Sha256Digest>(
1051            &mut rng,
1052            Subject::Finalize {
1053                proposal: &proposal,
1054            },
1055            &finalize_vote,
1056            &Sequential,
1057        ));
1058    }
1059
1060    #[test]
1061    fn test_sign_vote_roundtrip_for_each_context() {
1062        sign_vote_roundtrip_for_each_context::<MinPk>();
1063        sign_vote_roundtrip_for_each_context::<MinSig>();
1064    }
1065
1066    fn verifier_cannot_sign<V: Variant>() {
1067        let (_, verifier) = setup_signers::<V>(4, 11);
1068
1069        let proposal = sample_proposal(Epoch::new(0), View::new(3), 2);
1070        assert!(
1071            verifier
1072                .sign(Subject::Notarize {
1073                    proposal: &proposal,
1074                })
1075                .is_none(),
1076            "verifier should not produce signatures"
1077        );
1078    }
1079
1080    #[test]
1081    fn test_verifier_cannot_sign() {
1082        verifier_cannot_sign::<MinPk>();
1083        verifier_cannot_sign::<MinSig>();
1084    }
1085
1086    fn verifier_accepts_votes<V: Variant>() {
1087        let (schemes, verifier) = setup_signers::<V>(4, 11);
1088        let proposal = sample_proposal(Epoch::new(0), View::new(3), 2);
1089        let vote = schemes[1]
1090            .sign(Subject::Notarize {
1091                proposal: &proposal,
1092            })
1093            .unwrap();
1094        assert!(verifier.verify_attestation::<_, Sha256Digest>(
1095            &mut test_rng(),
1096            Subject::Notarize {
1097                proposal: &proposal,
1098            },
1099            &vote,
1100            &Sequential,
1101        ));
1102    }
1103
1104    #[test]
1105    fn test_verifier_accepts_votes() {
1106        verifier_accepts_votes::<MinPk>();
1107        verifier_accepts_votes::<MinSig>();
1108    }
1109
1110    fn verify_votes_filters_bad_signers<V: Variant>() {
1111        let mut rng = test_rng();
1112        let (schemes, _) = setup_signers::<V>(5, 13);
1113        let quorum = N3f1::quorum(schemes.len()) as usize;
1114        let proposal = sample_proposal(Epoch::new(0), View::new(5), 3);
1115
1116        let mut votes: Vec<_> = schemes
1117            .iter()
1118            .take(quorum)
1119            .map(|scheme| {
1120                scheme
1121                    .sign(Subject::Notarize {
1122                        proposal: &proposal,
1123                    })
1124                    .unwrap()
1125            })
1126            .collect();
1127
1128        let verification = schemes[0].verify_attestations(
1129            &mut rng,
1130            Subject::Notarize {
1131                proposal: &proposal,
1132            },
1133            votes.clone(),
1134            &Sequential,
1135        );
1136        assert!(verification.invalid.is_empty());
1137        assert_eq!(verification.verified.len(), quorum);
1138
1139        votes[0].signer = Participant::new(999);
1140        let verification = schemes[0].verify_attestations(
1141            &mut rng,
1142            Subject::Notarize {
1143                proposal: &proposal,
1144            },
1145            votes,
1146            &Sequential,
1147        );
1148        assert_eq!(verification.invalid, vec![Participant::new(999)]);
1149        assert_eq!(verification.verified.len(), quorum - 1);
1150    }
1151
1152    #[test]
1153    fn test_verify_votes_filters_bad_signers() {
1154        verify_votes_filters_bad_signers::<MinPk>();
1155        verify_votes_filters_bad_signers::<MinSig>();
1156    }
1157
1158    fn assemble_certificate_requires_quorum<V: Variant>() {
1159        let (schemes, _) = setup_signers::<V>(4, 17);
1160        let quorum = N3f1::quorum(schemes.len()) as usize;
1161        let proposal = sample_proposal(Epoch::new(0), View::new(7), 4);
1162
1163        let votes: Vec<_> = schemes
1164            .iter()
1165            .take(quorum - 1)
1166            .map(|scheme| {
1167                scheme
1168                    .sign(Subject::Notarize {
1169                        proposal: &proposal,
1170                    })
1171                    .unwrap()
1172            })
1173            .collect();
1174
1175        assert!(schemes[0].assemble::<_, N3f1>(votes, &Sequential).is_none());
1176    }
1177
1178    #[test]
1179    fn test_assemble_certificate_requires_quorum() {
1180        assemble_certificate_requires_quorum::<MinPk>();
1181        assemble_certificate_requires_quorum::<MinSig>();
1182    }
1183
1184    fn verify_certificate<V: Variant>() {
1185        let (schemes, verifier) = setup_signers::<V>(4, 19);
1186        let quorum = N3f1::quorum(schemes.len()) as usize;
1187        let proposal = sample_proposal(Epoch::new(0), View::new(9), 5);
1188
1189        let votes: Vec<_> = schemes
1190            .iter()
1191            .take(quorum)
1192            .map(|scheme| {
1193                scheme
1194                    .sign(Subject::Finalize {
1195                        proposal: &proposal,
1196                    })
1197                    .unwrap()
1198            })
1199            .collect();
1200
1201        let certificate = schemes[0]
1202            .assemble::<_, N3f1>(votes, &Sequential)
1203            .expect("assemble certificate");
1204
1205        assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1206            &mut test_rng(),
1207            Subject::Finalize {
1208                proposal: &proposal,
1209            },
1210            &certificate,
1211            &Sequential,
1212        ));
1213    }
1214
1215    #[test]
1216    fn test_verify_certificate() {
1217        verify_certificate::<MinPk>();
1218        verify_certificate::<MinSig>();
1219    }
1220
1221    fn verify_certificate_detects_corruption<V: Variant>() {
1222        let mut rng = test_rng();
1223        let (schemes, verifier) = setup_signers::<V>(4, 23);
1224        let quorum = N3f1::quorum(schemes.len()) as usize;
1225        let proposal = sample_proposal(Epoch::new(0), View::new(11), 6);
1226
1227        let votes: Vec<_> = schemes
1228            .iter()
1229            .take(quorum)
1230            .map(|scheme| {
1231                scheme
1232                    .sign(Subject::Notarize {
1233                        proposal: &proposal,
1234                    })
1235                    .unwrap()
1236            })
1237            .collect();
1238
1239        let certificate = schemes[0]
1240            .assemble::<_, N3f1>(votes, &Sequential)
1241            .expect("assemble certificate");
1242
1243        assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1244            &mut rng,
1245            Subject::Notarize {
1246                proposal: &proposal,
1247            },
1248            &certificate,
1249            &Sequential,
1250        ));
1251
1252        let cert = certificate.get().unwrap();
1253        let corrupted: Certificate<V> = Signature {
1254            vote_signature: cert.seed_signature,
1255            seed_signature: cert.seed_signature,
1256        }
1257        .into();
1258        assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1259            &mut rng,
1260            Subject::Notarize {
1261                proposal: &proposal,
1262            },
1263            &corrupted,
1264            &Sequential,
1265        ));
1266    }
1267
1268    #[test]
1269    fn test_verify_certificate_detects_corruption() {
1270        verify_certificate_detects_corruption::<MinPk>();
1271        verify_certificate_detects_corruption::<MinSig>();
1272    }
1273
1274    fn certificate_codec_roundtrip<V: Variant>() {
1275        let (schemes, _) = setup_signers::<V>(5, 29);
1276        let quorum = N3f1::quorum(schemes.len()) as usize;
1277        let proposal = sample_proposal(Epoch::new(0), View::new(13), 7);
1278
1279        let votes: Vec<_> = schemes
1280            .iter()
1281            .take(quorum)
1282            .map(|scheme| {
1283                scheme
1284                    .sign(Subject::Notarize {
1285                        proposal: &proposal,
1286                    })
1287                    .unwrap()
1288            })
1289            .collect();
1290
1291        let certificate = schemes[0]
1292            .assemble::<_, N3f1>(votes, &Sequential)
1293            .expect("assemble certificate");
1294
1295        let encoded = certificate.encode();
1296        let decoded = Certificate::<V>::decode_cfg(encoded, &()).expect("decode certificate");
1297        assert_eq!(decoded, certificate);
1298    }
1299
1300    #[test]
1301    fn test_certificate_codec_roundtrip() {
1302        certificate_codec_roundtrip::<MinPk>();
1303        certificate_codec_roundtrip::<MinSig>();
1304    }
1305
1306    fn seed_codec_roundtrip<V: Variant>() {
1307        let (schemes, _) = setup_signers::<V>(4, 5);
1308        let quorum = N3f1::quorum(schemes.len()) as usize;
1309        let proposal = sample_proposal(Epoch::new(0), View::new(1), 0);
1310
1311        let votes: Vec<_> = schemes
1312            .iter()
1313            .take(quorum)
1314            .map(|scheme| {
1315                scheme
1316                    .sign(Subject::Finalize {
1317                        proposal: &proposal,
1318                    })
1319                    .unwrap()
1320            })
1321            .collect();
1322
1323        let certificate = schemes[0]
1324            .assemble::<_, N3f1>(votes, &Sequential)
1325            .expect("assemble certificate");
1326        let cert = certificate.get().unwrap();
1327
1328        let seed = Seed::new(proposal.round, cert.seed_signature);
1329
1330        let encoded = seed.encode();
1331        let decoded = Seed::<V>::decode_cfg(encoded, &()).expect("decode seed");
1332        assert_eq!(decoded, seed);
1333    }
1334
1335    #[test]
1336    fn test_seed_codec_roundtrip() {
1337        seed_codec_roundtrip::<MinPk>();
1338        seed_codec_roundtrip::<MinSig>();
1339    }
1340
1341    fn seed_verify<V: Variant>() {
1342        let (schemes, _) = setup_signers::<V>(4, 5);
1343        let quorum = N3f1::quorum(schemes.len()) as usize;
1344        let proposal = sample_proposal(Epoch::new(0), View::new(1), 0);
1345
1346        let votes: Vec<_> = schemes
1347            .iter()
1348            .take(quorum)
1349            .map(|scheme| {
1350                scheme
1351                    .sign(Subject::Finalize {
1352                        proposal: &proposal,
1353                    })
1354                    .unwrap()
1355            })
1356            .collect();
1357
1358        let certificate = schemes[0]
1359            .assemble::<_, N3f1>(votes, &Sequential)
1360            .expect("assemble certificate");
1361        let cert = certificate.get().unwrap();
1362
1363        let seed = Seed::new(proposal.round, cert.seed_signature);
1364
1365        assert!(seed.verify(&schemes[0]));
1366
1367        // Create an invalid seed with a mismatched round
1368        let invalid_seed = Seed::new(
1369            Round::new(proposal.epoch(), proposal.view().next()),
1370            cert.seed_signature,
1371        );
1372
1373        assert!(!invalid_seed.verify(&schemes[0]));
1374    }
1375
1376    #[test]
1377    fn test_seed_verify() {
1378        seed_verify::<MinPk>();
1379        seed_verify::<MinSig>();
1380    }
1381
1382    fn seedable<V: Variant>() {
1383        let (schemes, _) = setup_signers::<V>(4, 5);
1384        let quorum = N3f1::quorum(schemes.len()) as usize;
1385        let proposal = sample_proposal(Epoch::new(0), View::new(1), 0);
1386
1387        let notarizes: Vec<_> = schemes
1388            .iter()
1389            .take(quorum)
1390            .map(|scheme| Notarize::sign(scheme, proposal.clone()).unwrap())
1391            .collect();
1392
1393        let notarization =
1394            Notarization::from_notarizes(&schemes[0], &notarizes, &Sequential).unwrap();
1395
1396        let finalizes: Vec<_> = schemes
1397            .iter()
1398            .take(quorum)
1399            .map(|scheme| Finalize::sign(scheme, proposal.clone()).unwrap())
1400            .collect();
1401
1402        let finalization =
1403            Finalization::from_finalizes(&schemes[0], &finalizes, &Sequential).unwrap();
1404
1405        assert_eq!(notarization.seed(), finalization.seed());
1406        assert!(notarization.seed().verify(&schemes[0]));
1407    }
1408
1409    #[test]
1410    fn test_seedable() {
1411        seedable::<MinPk>();
1412        seedable::<MinSig>();
1413    }
1414
1415    fn scheme_clone_and_verifier<V: Variant>() {
1416        let (schemes, verifier) = setup_signers::<V>(4, 31);
1417        let signer = schemes[0].clone();
1418        let proposal = sample_proposal(Epoch::new(0), View::new(21), 9);
1419
1420        assert!(
1421            signer
1422                .sign(Subject::Notarize {
1423                    proposal: &proposal,
1424                })
1425                .is_some(),
1426            "signer should produce votes"
1427        );
1428
1429        assert!(
1430            verifier
1431                .sign(Subject::Notarize {
1432                    proposal: &proposal,
1433                })
1434                .is_none(),
1435            "verifier should not produce votes"
1436        );
1437    }
1438
1439    #[test]
1440    fn test_scheme_clone_and_verifier() {
1441        scheme_clone_and_verifier::<MinPk>();
1442        scheme_clone_and_verifier::<MinSig>();
1443    }
1444
1445    fn certificate_verifier_accepts_certificates<V: Variant>() {
1446        let (schemes, _) = setup_signers::<V>(4, 37);
1447        let quorum = N3f1::quorum(schemes.len()) as usize;
1448        let proposal = sample_proposal(Epoch::new(0), View::new(15), 8);
1449
1450        let votes: Vec<_> = schemes
1451            .iter()
1452            .take(quorum)
1453            .map(|scheme| {
1454                scheme
1455                    .sign(Subject::Finalize {
1456                        proposal: &proposal,
1457                    })
1458                    .unwrap()
1459            })
1460            .collect();
1461
1462        let certificate = schemes[0]
1463            .assemble::<_, N3f1>(votes, &Sequential)
1464            .expect("assemble certificate");
1465
1466        let certificate_verifier =
1467            Scheme::<V>::certificate_verifier(NAMESPACE, *schemes[0].identity());
1468        assert!(
1469            certificate_verifier
1470                .sign(Subject::Finalize {
1471                    proposal: &proposal,
1472                })
1473                .is_none(),
1474            "certificate verifier should not produce votes"
1475        );
1476        assert!(
1477            certificate_verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1478                &mut test_rng(),
1479                Subject::Finalize {
1480                    proposal: &proposal,
1481                },
1482                &certificate,
1483                &Sequential,
1484            )
1485        );
1486    }
1487
1488    #[test]
1489    fn test_certificate_verifier_accepts_certificates() {
1490        certificate_verifier_accepts_certificates::<MinPk>();
1491        certificate_verifier_accepts_certificates::<MinSig>();
1492    }
1493
1494    fn certificate_verifier_panics_on_vote<V: Variant>() {
1495        let (schemes, _) = setup_signers::<V>(4, 37);
1496        let certificate_verifier =
1497            Scheme::<V>::certificate_verifier(NAMESPACE, *schemes[0].identity());
1498        let proposal = sample_proposal(Epoch::new(0), View::new(15), 8);
1499        let vote = schemes[1]
1500            .sign(Subject::Finalize {
1501                proposal: &proposal,
1502            })
1503            .unwrap();
1504
1505        certificate_verifier.verify_attestation::<_, Sha256Digest>(
1506            &mut test_rng(),
1507            Subject::Finalize {
1508                proposal: &proposal,
1509            },
1510            &vote,
1511            &Sequential,
1512        );
1513    }
1514
1515    #[test]
1516    #[should_panic(expected = "can only be called for signer and verifier")]
1517    fn test_certificate_verifier_panics_on_vote_min_pk() {
1518        certificate_verifier_panics_on_vote::<MinPk>();
1519    }
1520
1521    #[test]
1522    #[should_panic(expected = "can only be called for signer and verifier")]
1523    fn test_certificate_verifier_panics_on_vote_min_sig() {
1524        certificate_verifier_panics_on_vote::<MinSig>();
1525    }
1526
1527    fn verify_certificate_returns_seed_randomness<V: Variant>() {
1528        let (schemes, _) = setup_signers::<V>(4, 43);
1529        let quorum = N3f1::quorum(schemes.len()) as usize;
1530        let proposal = sample_proposal(Epoch::new(0), View::new(19), 10);
1531
1532        let votes: Vec<_> = schemes
1533            .iter()
1534            .take(quorum)
1535            .map(|scheme| {
1536                scheme
1537                    .sign(Subject::Notarize {
1538                        proposal: &proposal,
1539                    })
1540                    .unwrap()
1541            })
1542            .collect();
1543
1544        let certificate = schemes[0]
1545            .assemble::<_, N3f1>(votes, &Sequential)
1546            .expect("assemble certificate");
1547        let cert = certificate.get().unwrap();
1548
1549        let seed = Seed::<V>::new(proposal.round, cert.seed_signature);
1550        assert_eq!(seed.signature, cert.seed_signature);
1551    }
1552
1553    #[test]
1554    fn test_verify_certificate_returns_seed_randomness() {
1555        verify_certificate_returns_seed_randomness::<MinPk>();
1556        verify_certificate_returns_seed_randomness::<MinSig>();
1557    }
1558
1559    fn certificate_decode_rejects_length_mismatch<V: Variant>() {
1560        let (schemes, _) = setup_signers::<V>(4, 47);
1561        let quorum = N3f1::quorum(schemes.len()) as usize;
1562        let proposal = sample_proposal(Epoch::new(0), View::new(21), 11);
1563
1564        let votes: Vec<_> = schemes
1565            .iter()
1566            .take(quorum)
1567            .map(|scheme| {
1568                scheme
1569                    .sign::<Sha256Digest>(Subject::Nullify {
1570                        round: proposal.round,
1571                    })
1572                    .unwrap()
1573            })
1574            .collect();
1575
1576        let certificate = schemes[0]
1577            .assemble::<_, N3f1>(votes, &Sequential)
1578            .expect("assemble certificate");
1579
1580        let mut encoded = certificate.encode();
1581        let truncated = encoded.split_to(encoded.len() - 1);
1582        assert!(Signature::<V>::decode_cfg(truncated, &()).is_err());
1583    }
1584
1585    #[test]
1586    fn test_certificate_decode_rejects_length_mismatch() {
1587        certificate_decode_rejects_length_mismatch::<MinPk>();
1588        certificate_decode_rejects_length_mismatch::<MinSig>();
1589    }
1590
1591    fn sign_vote_partial_matches_share<V: Variant>() {
1592        let (schemes, _) = setup_signers::<V>(4, 53);
1593        let scheme = &schemes[0];
1594        let share = scheme.share().expect("has share");
1595
1596        let proposal = sample_proposal(Epoch::new(0), View::new(23), 12);
1597        let vote = scheme
1598            .sign(Subject::Notarize {
1599                proposal: &proposal,
1600            })
1601            .unwrap();
1602
1603        let notarize_namespace = notarize_namespace(NAMESPACE);
1604        let notarize_message = proposal.encode();
1605        let expected_message = threshold::sign_message::<V>(
1606            share,
1607            notarize_namespace.as_ref(),
1608            notarize_message.as_ref(),
1609        )
1610        .value;
1611
1612        let seed_namespace = seed_namespace(NAMESPACE);
1613        let seed_message = proposal.round.encode();
1614        let expected_seed =
1615            threshold::sign_message::<V>(share, seed_namespace.as_ref(), seed_message.as_ref())
1616                .value;
1617
1618        assert_eq!(vote.signer, share.index);
1619        let sig = vote.signature.get().unwrap();
1620        assert_eq!(sig.vote_signature, expected_message);
1621        assert_eq!(sig.seed_signature, expected_seed);
1622    }
1623
1624    #[test]
1625    fn test_sign_vote_partial_matches_share() {
1626        sign_vote_partial_matches_share::<MinPk>();
1627        sign_vote_partial_matches_share::<MinSig>();
1628    }
1629
1630    fn verify_certificate_detects_seed_corruption<V: Variant>() {
1631        let mut rng = test_rng();
1632        let (schemes, verifier) = setup_signers::<V>(4, 59);
1633        let quorum = N3f1::quorum(schemes.len()) as usize;
1634        let proposal = sample_proposal(Epoch::new(0), View::new(25), 13);
1635
1636        let votes: Vec<_> = schemes
1637            .iter()
1638            .take(quorum)
1639            .map(|scheme| {
1640                scheme
1641                    .sign::<Sha256Digest>(Subject::Nullify {
1642                        round: proposal.round,
1643                    })
1644                    .unwrap()
1645            })
1646            .collect();
1647
1648        let certificate = schemes[0]
1649            .assemble::<_, N3f1>(votes, &Sequential)
1650            .expect("assemble certificate");
1651
1652        assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1653            &mut rng,
1654            Subject::Nullify {
1655                round: proposal.round,
1656            },
1657            &certificate,
1658            &Sequential,
1659        ));
1660
1661        let cert = certificate.get().unwrap();
1662        let corrupted: Certificate<V> = Signature {
1663            vote_signature: cert.vote_signature,
1664            seed_signature: cert.vote_signature,
1665        }
1666        .into();
1667        assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1668            &mut rng,
1669            Subject::Nullify {
1670                round: proposal.round,
1671            },
1672            &corrupted,
1673            &Sequential,
1674        ));
1675    }
1676
1677    #[test]
1678    fn test_verify_certificate_detects_seed_corruption() {
1679        verify_certificate_detects_seed_corruption::<MinPk>();
1680        verify_certificate_detects_seed_corruption::<MinSig>();
1681    }
1682
1683    fn encrypt_decrypt<V: Variant>() {
1684        let mut rng = test_rng();
1685        let (schemes, verifier) = setup_signers::<V>(4, 61);
1686        let quorum = N3f1::quorum(schemes.len()) as usize;
1687
1688        // Prepare a message to encrypt
1689        let message = b"Secret message for future view10";
1690
1691        // Target round for encryption
1692        let target = Round::new(Epoch::new(333), View::new(10));
1693
1694        // Encrypt using the scheme
1695        let ciphertext = schemes[0].encrypt(&mut rng, target, *message);
1696
1697        // Can also encrypt with the verifier scheme
1698        let ciphertext_verifier = verifier.encrypt(&mut rng, target, *message);
1699
1700        // Generate notarization for the target round to get the seed
1701        let proposal = sample_proposal(target.epoch(), target.view(), 14);
1702        let notarizes: Vec<_> = schemes
1703            .iter()
1704            .take(quorum)
1705            .map(|scheme| Notarize::sign(scheme, proposal.clone()).unwrap())
1706            .collect();
1707
1708        let notarization =
1709            Notarization::from_notarizes(&schemes[0], &notarizes, &Sequential).unwrap();
1710
1711        // Decrypt using the seed
1712        let seed = notarization.seed();
1713        let decrypted = seed.decrypt(&ciphertext).unwrap();
1714        assert_eq!(message, decrypted.as_ref());
1715
1716        let decrypted_verifier = seed.decrypt(&ciphertext_verifier).unwrap();
1717        assert_eq!(message, decrypted_verifier.as_ref());
1718    }
1719
1720    #[test]
1721    fn test_encrypt_decrypt() {
1722        encrypt_decrypt::<MinPk>();
1723        encrypt_decrypt::<MinSig>();
1724    }
1725
1726    fn verify_attestation_rejects_malleability<V: Variant>() {
1727        let mut rng = test_rng();
1728        let (schemes, _) = setup_signers::<V>(4, 67);
1729        let proposal = sample_proposal(Epoch::new(0), View::new(27), 14);
1730
1731        let attestation = schemes[0]
1732            .sign(Subject::Notarize {
1733                proposal: &proposal,
1734            })
1735            .unwrap();
1736
1737        assert!(schemes[0].verify_attestation::<_, Sha256Digest>(
1738            &mut rng,
1739            Subject::Notarize {
1740                proposal: &proposal,
1741            },
1742            &attestation,
1743            &Sequential,
1744        ));
1745
1746        let random_scalar = Scalar::random(&mut rng);
1747        let delta = V::Signature::generator() * &random_scalar;
1748        let att_sig = attestation.signature.get().unwrap();
1749        let forged_attestation: Attestation<Scheme<V>> = Attestation {
1750            signer: attestation.signer,
1751            signature: Signature {
1752                vote_signature: att_sig.vote_signature - &delta,
1753                seed_signature: att_sig.seed_signature + &delta,
1754            }
1755            .into(),
1756        };
1757
1758        let forged_sig = forged_attestation.signature.get().unwrap();
1759        let forged_sum = forged_sig.vote_signature + &forged_sig.seed_signature;
1760        let valid_sum = att_sig.vote_signature + &att_sig.seed_signature;
1761        assert_eq!(forged_sum, valid_sum, "signature sums should be equal");
1762
1763        assert!(
1764            !schemes[0].verify_attestation::<_, Sha256Digest>(
1765                &mut rng,
1766                Subject::Notarize {
1767                    proposal: &proposal,
1768                },
1769                &forged_attestation,
1770                &Sequential,
1771            ),
1772            "forged attestation should be rejected"
1773        );
1774    }
1775
1776    #[test]
1777    fn test_verify_attestation_rejects_malleability() {
1778        verify_attestation_rejects_malleability::<MinPk>();
1779        verify_attestation_rejects_malleability::<MinSig>();
1780    }
1781
1782    fn verify_attestations_rejects_malleability<V: Variant>() {
1783        let mut rng = test_rng();
1784        let (schemes, _) = setup_signers::<V>(4, 71);
1785        let proposal = sample_proposal(Epoch::new(0), View::new(29), 15);
1786
1787        let attestation1 = schemes[0]
1788            .sign(Subject::Notarize {
1789                proposal: &proposal,
1790            })
1791            .unwrap();
1792        let attestation2 = schemes[1]
1793            .sign(Subject::Notarize {
1794                proposal: &proposal,
1795            })
1796            .unwrap();
1797
1798        let verification = schemes[0].verify_attestations(
1799            &mut rng,
1800            Subject::Notarize {
1801                proposal: &proposal,
1802            },
1803            vec![attestation1.clone(), attestation2.clone()],
1804            &Sequential,
1805        );
1806        assert!(verification.invalid.is_empty());
1807        assert_eq!(verification.verified.len(), 2);
1808
1809        let random_scalar = Scalar::random(&mut rng);
1810        let delta = V::Signature::generator() * &random_scalar;
1811        let att1_sig = attestation1.signature.get().unwrap();
1812        let att2_sig = attestation2.signature.get().unwrap();
1813        let forged_attestation1: Attestation<Scheme<V>> = Attestation {
1814            signer: attestation1.signer,
1815            signature: Signature {
1816                vote_signature: att1_sig.vote_signature - &delta,
1817                seed_signature: att1_sig.seed_signature,
1818            }
1819            .into(),
1820        };
1821        let forged_attestation2: Attestation<Scheme<V>> = Attestation {
1822            signer: attestation2.signer,
1823            signature: Signature {
1824                vote_signature: att2_sig.vote_signature + &delta,
1825                seed_signature: att2_sig.seed_signature,
1826            }
1827            .into(),
1828        };
1829
1830        let forged1_sig = forged_attestation1.signature.get().unwrap();
1831        let forged2_sig = forged_attestation2.signature.get().unwrap();
1832        let forged_vote_sum = forged1_sig.vote_signature + &forged2_sig.vote_signature;
1833        let valid_vote_sum = att1_sig.vote_signature + &att2_sig.vote_signature;
1834        assert_eq!(
1835            forged_vote_sum, valid_vote_sum,
1836            "vote signature sums should be equal"
1837        );
1838
1839        let verification = schemes[0].verify_attestations(
1840            &mut rng,
1841            Subject::Notarize {
1842                proposal: &proposal,
1843            },
1844            vec![forged_attestation1, forged_attestation2],
1845            &Sequential,
1846        );
1847        assert!(
1848            !verification.invalid.is_empty(),
1849            "forged attestations should be detected"
1850        );
1851    }
1852
1853    #[test]
1854    fn test_verify_attestations_rejects_malleability() {
1855        verify_attestations_rejects_malleability::<MinPk>();
1856        verify_attestations_rejects_malleability::<MinSig>();
1857    }
1858
1859    fn verify_certificate_rejects_malleability<V: Variant>() {
1860        let mut rng = test_rng();
1861        let (schemes, verifier) = setup_signers::<V>(4, 73);
1862        let quorum = N3f1::quorum(schemes.len()) as usize;
1863        let proposal = sample_proposal(Epoch::new(0), View::new(31), 16);
1864
1865        let votes: Vec<_> = schemes
1866            .iter()
1867            .take(quorum)
1868            .map(|scheme| {
1869                scheme
1870                    .sign(Subject::Notarize {
1871                        proposal: &proposal,
1872                    })
1873                    .unwrap()
1874            })
1875            .collect();
1876
1877        let certificate = schemes[0]
1878            .assemble::<_, N3f1>(votes, &Sequential)
1879            .expect("assemble certificate");
1880
1881        assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1882            &mut rng,
1883            Subject::Notarize {
1884                proposal: &proposal,
1885            },
1886            &certificate,
1887            &Sequential,
1888        ));
1889
1890        let cert = certificate.get().unwrap();
1891        let random_scalar = Scalar::random(&mut rng);
1892        let delta = V::Signature::generator() * &random_scalar;
1893        let forged_certificate: Certificate<V> = Signature {
1894            vote_signature: cert.vote_signature - &delta,
1895            seed_signature: cert.seed_signature + &delta,
1896        }
1897        .into();
1898
1899        let forged_cert = forged_certificate.get().unwrap();
1900        let forged_sum = forged_cert.vote_signature + &forged_cert.seed_signature;
1901        let valid_sum = cert.vote_signature + &cert.seed_signature;
1902        assert_eq!(forged_sum, valid_sum, "signature sums should be equal");
1903
1904        assert!(
1905            !verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1906                &mut rng,
1907                Subject::Notarize {
1908                    proposal: &proposal,
1909                },
1910                &forged_certificate,
1911                &Sequential,
1912            ),
1913            "forged certificate should be rejected"
1914        );
1915    }
1916
1917    #[test]
1918    fn test_verify_certificate_rejects_malleability() {
1919        verify_certificate_rejects_malleability::<MinPk>();
1920        verify_certificate_rejects_malleability::<MinSig>();
1921    }
1922
1923    fn verify_certificates_rejects_malleability<V: Variant>() {
1924        let mut rng = test_rng();
1925        let (schemes, verifier) = setup_signers::<V>(4, 79);
1926        let quorum = N3f1::quorum(schemes.len()) as usize;
1927        let proposal1 = sample_proposal(Epoch::new(0), View::new(33), 17);
1928        let proposal2 = sample_proposal(Epoch::new(0), View::new(34), 18);
1929
1930        let votes1: Vec<_> = schemes
1931            .iter()
1932            .take(quorum)
1933            .map(|scheme| {
1934                scheme
1935                    .sign(Subject::Notarize {
1936                        proposal: &proposal1,
1937                    })
1938                    .unwrap()
1939            })
1940            .collect();
1941        let votes2: Vec<_> = schemes
1942            .iter()
1943            .take(quorum)
1944            .map(|scheme| {
1945                scheme
1946                    .sign(Subject::Notarize {
1947                        proposal: &proposal2,
1948                    })
1949                    .unwrap()
1950            })
1951            .collect();
1952
1953        let certificate1 = schemes[0]
1954            .assemble::<_, N3f1>(votes1, &Sequential)
1955            .expect("assemble certificate1");
1956        let certificate2 = schemes[0]
1957            .assemble::<_, N3f1>(votes2, &Sequential)
1958            .expect("assemble certificate2");
1959
1960        assert!(verifier.verify_certificates::<_, Sha256Digest, _, N3f1>(
1961            &mut rng,
1962            [
1963                (
1964                    Subject::Notarize {
1965                        proposal: &proposal1,
1966                    },
1967                    &certificate1
1968                ),
1969                (
1970                    Subject::Notarize {
1971                        proposal: &proposal2,
1972                    },
1973                    &certificate2
1974                ),
1975            ]
1976            .into_iter(),
1977            &Sequential,
1978        ));
1979
1980        let cert1 = certificate1.get().unwrap();
1981        let cert2 = certificate2.get().unwrap();
1982        let random_scalar = Scalar::random(&mut rng);
1983        let delta = V::Signature::generator() * &random_scalar;
1984        let forged_certificate1: Certificate<V> = Signature {
1985            vote_signature: cert1.vote_signature - &delta,
1986            seed_signature: cert1.seed_signature,
1987        }
1988        .into();
1989        let forged_certificate2: Certificate<V> = Signature {
1990            vote_signature: cert2.vote_signature + &delta,
1991            seed_signature: cert2.seed_signature,
1992        }
1993        .into();
1994
1995        let forged1 = forged_certificate1.get().unwrap();
1996        let forged2 = forged_certificate2.get().unwrap();
1997        let forged_vote_sum = forged1.vote_signature + &forged2.vote_signature;
1998        let valid_vote_sum = cert1.vote_signature + &cert2.vote_signature;
1999        assert_eq!(
2000            forged_vote_sum, valid_vote_sum,
2001            "vote signature sums should be equal"
2002        );
2003
2004        assert!(
2005            !verifier.verify_certificates::<_, Sha256Digest, _, N3f1>(
2006                &mut rng,
2007                [
2008                    (
2009                        Subject::Notarize {
2010                            proposal: &proposal1,
2011                        },
2012                        &forged_certificate1
2013                    ),
2014                    (
2015                        Subject::Notarize {
2016                            proposal: &proposal2,
2017                        },
2018                        &forged_certificate2
2019                    ),
2020                ]
2021                .into_iter(),
2022                &Sequential,
2023            ),
2024            "forged certificates should be rejected"
2025        );
2026    }
2027
2028    #[test]
2029    fn test_verify_certificates_rejects_malleability() {
2030        verify_certificates_rejects_malleability::<MinPk>();
2031        verify_certificates_rejects_malleability::<MinSig>();
2032    }
2033
2034    fn assemble_notarization_certificate<V: Variant>(
2035        schemes: &[Scheme<V>],
2036        proposal: &Proposal<Sha256Digest>,
2037    ) -> Certificate<V> {
2038        let quorum = N3f1::quorum(schemes.len()) as usize;
2039        let votes: Vec<_> = schemes
2040            .iter()
2041            .take(quorum)
2042            .map(|scheme| scheme.sign(Subject::Notarize { proposal }).unwrap())
2043            .collect();
2044
2045        schemes[0]
2046            .assemble::<_, N3f1>(votes, &Sequential)
2047            .expect("assemble notarization certificate")
2048    }
2049
2050    fn assemble_finalization_certificate<V: Variant>(
2051        schemes: &[Scheme<V>],
2052        proposal: &Proposal<Sha256Digest>,
2053    ) -> Certificate<V> {
2054        let quorum = N3f1::quorum(schemes.len()) as usize;
2055        let votes: Vec<_> = schemes
2056            .iter()
2057            .skip(schemes.len() - quorum)
2058            .map(|scheme| scheme.sign(Subject::Finalize { proposal }).unwrap())
2059            .collect();
2060
2061        schemes[0]
2062            .assemble::<_, N3f1>(votes, &Sequential)
2063            .expect("assemble finalization certificate")
2064    }
2065
2066    fn verify_certificates_accepts_shared_round_seed<V: Variant>() {
2067        let mut rng = test_rng();
2068        let (schemes, verifier) = setup_signers::<V>(4, 81);
2069        let proposal = sample_proposal(Epoch::new(1), View::new(35), 19);
2070        let notarization_certificate = assemble_notarization_certificate(&schemes, &proposal);
2071        let finalization_certificate = assemble_finalization_certificate(&schemes, &proposal);
2072
2073        assert!(verifier.verify_certificates::<_, Sha256Digest, _, N3f1>(
2074            &mut rng,
2075            [
2076                (
2077                    Subject::Notarize {
2078                        proposal: &proposal,
2079                    },
2080                    &notarization_certificate,
2081                ),
2082                (
2083                    Subject::Finalize {
2084                        proposal: &proposal,
2085                    },
2086                    &finalization_certificate,
2087                ),
2088            ]
2089            .into_iter(),
2090            &Sequential,
2091        ));
2092    }
2093
2094    #[test]
2095    fn test_verify_certificates_accepts_shared_round_seed() {
2096        verify_certificates_accepts_shared_round_seed::<MinPk>();
2097        verify_certificates_accepts_shared_round_seed::<MinSig>();
2098    }
2099
2100    fn verify_certificates_rejects_cross_epoch_seed_replay<V: Variant>() {
2101        let mut rng = test_rng();
2102        let (schemes, verifier) = setup_signers::<V>(4, 83);
2103        let view = View::new(35);
2104        let proposal1 = sample_proposal(Epoch::new(1), view, 19);
2105        let proposal2 = sample_proposal(Epoch::new(2), view, 20);
2106        let certificate1 = assemble_notarization_certificate(&schemes, &proposal1);
2107        let certificate2 = assemble_notarization_certificate(&schemes, &proposal2);
2108
2109        assert!(verifier.verify_certificates::<_, Sha256Digest, _, N3f1>(
2110            &mut rng,
2111            [
2112                (
2113                    Subject::Notarize {
2114                        proposal: &proposal1,
2115                    },
2116                    &certificate1,
2117                ),
2118                (
2119                    Subject::Notarize {
2120                        proposal: &proposal2,
2121                    },
2122                    &certificate2,
2123                ),
2124            ]
2125            .into_iter(),
2126            &Sequential,
2127        ));
2128
2129        let cert1 = certificate1.get().unwrap();
2130        let cert2 = certificate2.get().unwrap();
2131        let forged_certificate2: Certificate<V> = Signature {
2132            vote_signature: cert2.vote_signature,
2133            seed_signature: cert1.seed_signature,
2134        }
2135        .into();
2136
2137        assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
2138            &mut rng,
2139            Subject::Notarize {
2140                proposal: &proposal2,
2141            },
2142            &forged_certificate2,
2143            &Sequential,
2144        ));
2145
2146        let batch = [
2147            (
2148                Subject::Notarize {
2149                    proposal: &proposal1,
2150                },
2151                &certificate1,
2152            ),
2153            (
2154                Subject::Notarize {
2155                    proposal: &proposal2,
2156                },
2157                &forged_certificate2,
2158            ),
2159        ];
2160
2161        assert!(!verifier.verify_certificates::<_, Sha256Digest, _, N3f1>(
2162            &mut rng,
2163            batch.iter().copied(),
2164            &Sequential,
2165        ));
2166        assert_eq!(
2167            verifier.verify_certificates_bisect::<_, Sha256Digest, N3f1>(
2168                &mut rng,
2169                &batch,
2170                &Sequential,
2171            ),
2172            vec![true, false],
2173        );
2174    }
2175
2176    #[test]
2177    fn test_verify_certificates_rejects_cross_epoch_seed_replay() {
2178        verify_certificates_rejects_cross_epoch_seed_replay::<MinPk>();
2179        verify_certificates_rejects_cross_epoch_seed_replay::<MinSig>();
2180    }
2181
2182    #[cfg(feature = "arbitrary")]
2183    mod conformance {
2184        use super::*;
2185        use commonware_codec::conformance::CodecConformance;
2186
2187        commonware_conformance::conformance_tests! {
2188            CodecConformance<Signature<MinSig>>,
2189            CodecConformance<Certificate<MinSig>>,
2190            CodecConformance<Seed<MinSig>>,
2191        }
2192    }
2193}