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 view 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-view 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-view, so multiple certificates for the same view
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            // view have matching seeds.
840            if let Some(previous) = seeds.get(&context.view()) {
841                if *previous != cert.seed_signature {
842                    return false;
843                }
844            } else {
845                let seed_message = seed_message_from_subject(&context);
846                entries.push((&namespace.seed, seed_message, cert.seed_signature));
847                seeds.insert(context.view(), 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::{self, deal_anonymous},
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            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, _) = deal_anonymous::<V, N3f1>(&mut rng, Default::default(), NZU32!(4));
980        Scheme::<V>::verifier(NAMESPACE, participants.keys().clone(), polynomial);
981    }
982
983    #[test]
984    #[should_panic(expected = "polynomial total must equal participant len")]
985    fn test_verifier_polynomial_threshold_must_equal_quorum_min_pk() {
986        verifier_polynomial_threshold_must_equal_quorum::<MinPk>();
987    }
988
989    #[test]
990    #[should_panic(expected = "polynomial total must equal participant len")]
991    fn test_verifier_polynomial_threshold_must_equal_quorum_min_sig() {
992        verifier_polynomial_threshold_must_equal_quorum::<MinSig>();
993    }
994
995    #[test]
996    fn test_is_not_attributable() {
997        assert!(!Scheme::<MinPk>::is_attributable());
998        assert!(!Scheme::<MinSig>::is_attributable());
999    }
1000
1001    #[test]
1002    fn test_is_batchable() {
1003        assert!(Scheme::<MinPk>::is_batchable());
1004        assert!(Scheme::<MinSig>::is_batchable());
1005    }
1006
1007    fn sign_vote_roundtrip_for_each_context<V: Variant>() {
1008        let (schemes, _) = setup_signers::<V>(4, 7);
1009        let scheme = &schemes[0];
1010        let mut rng = test_rng();
1011
1012        let proposal = sample_proposal(Epoch::new(0), View::new(2), 1);
1013        let notarize_vote = scheme
1014            .sign(Subject::Notarize {
1015                proposal: &proposal,
1016            })
1017            .unwrap();
1018        assert!(scheme.verify_attestation::<_, Sha256Digest>(
1019            &mut rng,
1020            Subject::Notarize {
1021                proposal: &proposal,
1022            },
1023            &notarize_vote,
1024            &Sequential,
1025        ));
1026
1027        let nullify_vote = scheme
1028            .sign::<Sha256Digest>(Subject::Nullify {
1029                round: proposal.round,
1030            })
1031            .unwrap();
1032        assert!(scheme.verify_attestation::<_, Sha256Digest>(
1033            &mut rng,
1034            Subject::Nullify {
1035                round: proposal.round,
1036            },
1037            &nullify_vote,
1038            &Sequential,
1039        ));
1040
1041        let finalize_vote = scheme
1042            .sign(Subject::Finalize {
1043                proposal: &proposal,
1044            })
1045            .unwrap();
1046        assert!(scheme.verify_attestation::<_, Sha256Digest>(
1047            &mut rng,
1048            Subject::Finalize {
1049                proposal: &proposal,
1050            },
1051            &finalize_vote,
1052            &Sequential,
1053        ));
1054    }
1055
1056    #[test]
1057    fn test_sign_vote_roundtrip_for_each_context() {
1058        sign_vote_roundtrip_for_each_context::<MinPk>();
1059        sign_vote_roundtrip_for_each_context::<MinSig>();
1060    }
1061
1062    fn verifier_cannot_sign<V: Variant>() {
1063        let (_, verifier) = setup_signers::<V>(4, 11);
1064
1065        let proposal = sample_proposal(Epoch::new(0), View::new(3), 2);
1066        assert!(
1067            verifier
1068                .sign(Subject::Notarize {
1069                    proposal: &proposal,
1070                })
1071                .is_none(),
1072            "verifier should not produce signatures"
1073        );
1074    }
1075
1076    #[test]
1077    fn test_verifier_cannot_sign() {
1078        verifier_cannot_sign::<MinPk>();
1079        verifier_cannot_sign::<MinSig>();
1080    }
1081
1082    fn verifier_accepts_votes<V: Variant>() {
1083        let (schemes, verifier) = setup_signers::<V>(4, 11);
1084        let proposal = sample_proposal(Epoch::new(0), View::new(3), 2);
1085        let vote = schemes[1]
1086            .sign(Subject::Notarize {
1087                proposal: &proposal,
1088            })
1089            .unwrap();
1090        assert!(verifier.verify_attestation::<_, Sha256Digest>(
1091            &mut test_rng(),
1092            Subject::Notarize {
1093                proposal: &proposal,
1094            },
1095            &vote,
1096            &Sequential,
1097        ));
1098    }
1099
1100    #[test]
1101    fn test_verifier_accepts_votes() {
1102        verifier_accepts_votes::<MinPk>();
1103        verifier_accepts_votes::<MinSig>();
1104    }
1105
1106    fn verify_votes_filters_bad_signers<V: Variant>() {
1107        let mut rng = test_rng();
1108        let (schemes, _) = setup_signers::<V>(5, 13);
1109        let quorum = N3f1::quorum(schemes.len()) as usize;
1110        let proposal = sample_proposal(Epoch::new(0), View::new(5), 3);
1111
1112        let mut votes: Vec<_> = schemes
1113            .iter()
1114            .take(quorum)
1115            .map(|scheme| {
1116                scheme
1117                    .sign(Subject::Notarize {
1118                        proposal: &proposal,
1119                    })
1120                    .unwrap()
1121            })
1122            .collect();
1123
1124        let verification = schemes[0].verify_attestations(
1125            &mut rng,
1126            Subject::Notarize {
1127                proposal: &proposal,
1128            },
1129            votes.clone(),
1130            &Sequential,
1131        );
1132        assert!(verification.invalid.is_empty());
1133        assert_eq!(verification.verified.len(), quorum);
1134
1135        votes[0].signer = Participant::new(999);
1136        let verification = schemes[0].verify_attestations(
1137            &mut rng,
1138            Subject::Notarize {
1139                proposal: &proposal,
1140            },
1141            votes,
1142            &Sequential,
1143        );
1144        assert_eq!(verification.invalid, vec![Participant::new(999)]);
1145        assert_eq!(verification.verified.len(), quorum - 1);
1146    }
1147
1148    #[test]
1149    fn test_verify_votes_filters_bad_signers() {
1150        verify_votes_filters_bad_signers::<MinPk>();
1151        verify_votes_filters_bad_signers::<MinSig>();
1152    }
1153
1154    fn assemble_certificate_requires_quorum<V: Variant>() {
1155        let (schemes, _) = setup_signers::<V>(4, 17);
1156        let quorum = N3f1::quorum(schemes.len()) as usize;
1157        let proposal = sample_proposal(Epoch::new(0), View::new(7), 4);
1158
1159        let votes: Vec<_> = schemes
1160            .iter()
1161            .take(quorum - 1)
1162            .map(|scheme| {
1163                scheme
1164                    .sign(Subject::Notarize {
1165                        proposal: &proposal,
1166                    })
1167                    .unwrap()
1168            })
1169            .collect();
1170
1171        assert!(schemes[0].assemble::<_, N3f1>(votes, &Sequential).is_none());
1172    }
1173
1174    #[test]
1175    fn test_assemble_certificate_requires_quorum() {
1176        assemble_certificate_requires_quorum::<MinPk>();
1177        assemble_certificate_requires_quorum::<MinSig>();
1178    }
1179
1180    fn verify_certificate<V: Variant>() {
1181        let (schemes, verifier) = setup_signers::<V>(4, 19);
1182        let quorum = N3f1::quorum(schemes.len()) as usize;
1183        let proposal = sample_proposal(Epoch::new(0), View::new(9), 5);
1184
1185        let votes: Vec<_> = schemes
1186            .iter()
1187            .take(quorum)
1188            .map(|scheme| {
1189                scheme
1190                    .sign(Subject::Finalize {
1191                        proposal: &proposal,
1192                    })
1193                    .unwrap()
1194            })
1195            .collect();
1196
1197        let certificate = schemes[0]
1198            .assemble::<_, N3f1>(votes, &Sequential)
1199            .expect("assemble certificate");
1200
1201        assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1202            &mut test_rng(),
1203            Subject::Finalize {
1204                proposal: &proposal,
1205            },
1206            &certificate,
1207            &Sequential,
1208        ));
1209    }
1210
1211    #[test]
1212    fn test_verify_certificate() {
1213        verify_certificate::<MinPk>();
1214        verify_certificate::<MinSig>();
1215    }
1216
1217    fn verify_certificate_detects_corruption<V: Variant>() {
1218        let mut rng = test_rng();
1219        let (schemes, verifier) = setup_signers::<V>(4, 23);
1220        let quorum = N3f1::quorum(schemes.len()) as usize;
1221        let proposal = sample_proposal(Epoch::new(0), View::new(11), 6);
1222
1223        let votes: Vec<_> = schemes
1224            .iter()
1225            .take(quorum)
1226            .map(|scheme| {
1227                scheme
1228                    .sign(Subject::Notarize {
1229                        proposal: &proposal,
1230                    })
1231                    .unwrap()
1232            })
1233            .collect();
1234
1235        let certificate = schemes[0]
1236            .assemble::<_, N3f1>(votes, &Sequential)
1237            .expect("assemble certificate");
1238
1239        assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1240            &mut rng,
1241            Subject::Notarize {
1242                proposal: &proposal,
1243            },
1244            &certificate,
1245            &Sequential,
1246        ));
1247
1248        let cert = certificate.get().unwrap();
1249        let corrupted: Certificate<V> = Signature {
1250            vote_signature: cert.seed_signature,
1251            seed_signature: cert.seed_signature,
1252        }
1253        .into();
1254        assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1255            &mut rng,
1256            Subject::Notarize {
1257                proposal: &proposal,
1258            },
1259            &corrupted,
1260            &Sequential,
1261        ));
1262    }
1263
1264    #[test]
1265    fn test_verify_certificate_detects_corruption() {
1266        verify_certificate_detects_corruption::<MinPk>();
1267        verify_certificate_detects_corruption::<MinSig>();
1268    }
1269
1270    fn certificate_codec_roundtrip<V: Variant>() {
1271        let (schemes, _) = setup_signers::<V>(5, 29);
1272        let quorum = N3f1::quorum(schemes.len()) as usize;
1273        let proposal = sample_proposal(Epoch::new(0), View::new(13), 7);
1274
1275        let votes: Vec<_> = schemes
1276            .iter()
1277            .take(quorum)
1278            .map(|scheme| {
1279                scheme
1280                    .sign(Subject::Notarize {
1281                        proposal: &proposal,
1282                    })
1283                    .unwrap()
1284            })
1285            .collect();
1286
1287        let certificate = schemes[0]
1288            .assemble::<_, N3f1>(votes, &Sequential)
1289            .expect("assemble certificate");
1290
1291        let encoded = certificate.encode();
1292        let decoded = Certificate::<V>::decode_cfg(encoded, &()).expect("decode certificate");
1293        assert_eq!(decoded, certificate);
1294    }
1295
1296    #[test]
1297    fn test_certificate_codec_roundtrip() {
1298        certificate_codec_roundtrip::<MinPk>();
1299        certificate_codec_roundtrip::<MinSig>();
1300    }
1301
1302    fn seed_codec_roundtrip<V: Variant>() {
1303        let (schemes, _) = setup_signers::<V>(4, 5);
1304        let quorum = N3f1::quorum(schemes.len()) as usize;
1305        let proposal = sample_proposal(Epoch::new(0), View::new(1), 0);
1306
1307        let votes: Vec<_> = schemes
1308            .iter()
1309            .take(quorum)
1310            .map(|scheme| {
1311                scheme
1312                    .sign(Subject::Finalize {
1313                        proposal: &proposal,
1314                    })
1315                    .unwrap()
1316            })
1317            .collect();
1318
1319        let certificate = schemes[0]
1320            .assemble::<_, N3f1>(votes, &Sequential)
1321            .expect("assemble certificate");
1322        let cert = certificate.get().unwrap();
1323
1324        let seed = Seed::new(proposal.round, cert.seed_signature);
1325
1326        let encoded = seed.encode();
1327        let decoded = Seed::<V>::decode_cfg(encoded, &()).expect("decode seed");
1328        assert_eq!(decoded, seed);
1329    }
1330
1331    #[test]
1332    fn test_seed_codec_roundtrip() {
1333        seed_codec_roundtrip::<MinPk>();
1334        seed_codec_roundtrip::<MinSig>();
1335    }
1336
1337    fn seed_verify<V: Variant>() {
1338        let (schemes, _) = setup_signers::<V>(4, 5);
1339        let quorum = N3f1::quorum(schemes.len()) as usize;
1340        let proposal = sample_proposal(Epoch::new(0), View::new(1), 0);
1341
1342        let votes: Vec<_> = schemes
1343            .iter()
1344            .take(quorum)
1345            .map(|scheme| {
1346                scheme
1347                    .sign(Subject::Finalize {
1348                        proposal: &proposal,
1349                    })
1350                    .unwrap()
1351            })
1352            .collect();
1353
1354        let certificate = schemes[0]
1355            .assemble::<_, N3f1>(votes, &Sequential)
1356            .expect("assemble certificate");
1357        let cert = certificate.get().unwrap();
1358
1359        let seed = Seed::new(proposal.round, cert.seed_signature);
1360
1361        assert!(seed.verify(&schemes[0]));
1362
1363        // Create an invalid seed with a mismatched round
1364        let invalid_seed = Seed::new(
1365            Round::new(proposal.epoch(), proposal.view().next()),
1366            cert.seed_signature,
1367        );
1368
1369        assert!(!invalid_seed.verify(&schemes[0]));
1370    }
1371
1372    #[test]
1373    fn test_seed_verify() {
1374        seed_verify::<MinPk>();
1375        seed_verify::<MinSig>();
1376    }
1377
1378    fn seedable<V: Variant>() {
1379        let (schemes, _) = setup_signers::<V>(4, 5);
1380        let quorum = N3f1::quorum(schemes.len()) as usize;
1381        let proposal = sample_proposal(Epoch::new(0), View::new(1), 0);
1382
1383        let notarizes: Vec<_> = schemes
1384            .iter()
1385            .take(quorum)
1386            .map(|scheme| Notarize::sign(scheme, proposal.clone()).unwrap())
1387            .collect();
1388
1389        let notarization =
1390            Notarization::from_notarizes(&schemes[0], &notarizes, &Sequential).unwrap();
1391
1392        let finalizes: Vec<_> = schemes
1393            .iter()
1394            .take(quorum)
1395            .map(|scheme| Finalize::sign(scheme, proposal.clone()).unwrap())
1396            .collect();
1397
1398        let finalization =
1399            Finalization::from_finalizes(&schemes[0], &finalizes, &Sequential).unwrap();
1400
1401        assert_eq!(notarization.seed(), finalization.seed());
1402        assert!(notarization.seed().verify(&schemes[0]));
1403    }
1404
1405    #[test]
1406    fn test_seedable() {
1407        seedable::<MinPk>();
1408        seedable::<MinSig>();
1409    }
1410
1411    fn scheme_clone_and_verifier<V: Variant>() {
1412        let (schemes, verifier) = setup_signers::<V>(4, 31);
1413        let signer = schemes[0].clone();
1414        let proposal = sample_proposal(Epoch::new(0), View::new(21), 9);
1415
1416        assert!(
1417            signer
1418                .sign(Subject::Notarize {
1419                    proposal: &proposal,
1420                })
1421                .is_some(),
1422            "signer should produce votes"
1423        );
1424
1425        assert!(
1426            verifier
1427                .sign(Subject::Notarize {
1428                    proposal: &proposal,
1429                })
1430                .is_none(),
1431            "verifier should not produce votes"
1432        );
1433    }
1434
1435    #[test]
1436    fn test_scheme_clone_and_verifier() {
1437        scheme_clone_and_verifier::<MinPk>();
1438        scheme_clone_and_verifier::<MinSig>();
1439    }
1440
1441    fn certificate_verifier_accepts_certificates<V: Variant>() {
1442        let (schemes, _) = setup_signers::<V>(4, 37);
1443        let quorum = N3f1::quorum(schemes.len()) as usize;
1444        let proposal = sample_proposal(Epoch::new(0), View::new(15), 8);
1445
1446        let votes: Vec<_> = schemes
1447            .iter()
1448            .take(quorum)
1449            .map(|scheme| {
1450                scheme
1451                    .sign(Subject::Finalize {
1452                        proposal: &proposal,
1453                    })
1454                    .unwrap()
1455            })
1456            .collect();
1457
1458        let certificate = schemes[0]
1459            .assemble::<_, N3f1>(votes, &Sequential)
1460            .expect("assemble certificate");
1461
1462        let certificate_verifier =
1463            Scheme::<V>::certificate_verifier(NAMESPACE, *schemes[0].identity());
1464        assert!(
1465            certificate_verifier
1466                .sign(Subject::Finalize {
1467                    proposal: &proposal,
1468                })
1469                .is_none(),
1470            "certificate verifier should not produce votes"
1471        );
1472        assert!(
1473            certificate_verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1474                &mut test_rng(),
1475                Subject::Finalize {
1476                    proposal: &proposal,
1477                },
1478                &certificate,
1479                &Sequential,
1480            )
1481        );
1482    }
1483
1484    #[test]
1485    fn test_certificate_verifier_accepts_certificates() {
1486        certificate_verifier_accepts_certificates::<MinPk>();
1487        certificate_verifier_accepts_certificates::<MinSig>();
1488    }
1489
1490    fn certificate_verifier_panics_on_vote<V: Variant>() {
1491        let (schemes, _) = setup_signers::<V>(4, 37);
1492        let certificate_verifier =
1493            Scheme::<V>::certificate_verifier(NAMESPACE, *schemes[0].identity());
1494        let proposal = sample_proposal(Epoch::new(0), View::new(15), 8);
1495        let vote = schemes[1]
1496            .sign(Subject::Finalize {
1497                proposal: &proposal,
1498            })
1499            .unwrap();
1500
1501        certificate_verifier.verify_attestation::<_, Sha256Digest>(
1502            &mut test_rng(),
1503            Subject::Finalize {
1504                proposal: &proposal,
1505            },
1506            &vote,
1507            &Sequential,
1508        );
1509    }
1510
1511    #[test]
1512    #[should_panic(expected = "can only be called for signer and verifier")]
1513    fn test_certificate_verifier_panics_on_vote_min_pk() {
1514        certificate_verifier_panics_on_vote::<MinPk>();
1515    }
1516
1517    #[test]
1518    #[should_panic(expected = "can only be called for signer and verifier")]
1519    fn test_certificate_verifier_panics_on_vote_min_sig() {
1520        certificate_verifier_panics_on_vote::<MinSig>();
1521    }
1522
1523    fn verify_certificate_returns_seed_randomness<V: Variant>() {
1524        let (schemes, _) = setup_signers::<V>(4, 43);
1525        let quorum = N3f1::quorum(schemes.len()) as usize;
1526        let proposal = sample_proposal(Epoch::new(0), View::new(19), 10);
1527
1528        let votes: Vec<_> = schemes
1529            .iter()
1530            .take(quorum)
1531            .map(|scheme| {
1532                scheme
1533                    .sign(Subject::Notarize {
1534                        proposal: &proposal,
1535                    })
1536                    .unwrap()
1537            })
1538            .collect();
1539
1540        let certificate = schemes[0]
1541            .assemble::<_, N3f1>(votes, &Sequential)
1542            .expect("assemble certificate");
1543        let cert = certificate.get().unwrap();
1544
1545        let seed = Seed::<V>::new(proposal.round, cert.seed_signature);
1546        assert_eq!(seed.signature, cert.seed_signature);
1547    }
1548
1549    #[test]
1550    fn test_verify_certificate_returns_seed_randomness() {
1551        verify_certificate_returns_seed_randomness::<MinPk>();
1552        verify_certificate_returns_seed_randomness::<MinSig>();
1553    }
1554
1555    fn certificate_decode_rejects_length_mismatch<V: Variant>() {
1556        let (schemes, _) = setup_signers::<V>(4, 47);
1557        let quorum = N3f1::quorum(schemes.len()) as usize;
1558        let proposal = sample_proposal(Epoch::new(0), View::new(21), 11);
1559
1560        let votes: Vec<_> = schemes
1561            .iter()
1562            .take(quorum)
1563            .map(|scheme| {
1564                scheme
1565                    .sign::<Sha256Digest>(Subject::Nullify {
1566                        round: proposal.round,
1567                    })
1568                    .unwrap()
1569            })
1570            .collect();
1571
1572        let certificate = schemes[0]
1573            .assemble::<_, N3f1>(votes, &Sequential)
1574            .expect("assemble certificate");
1575
1576        let mut encoded = certificate.encode();
1577        let truncated = encoded.split_to(encoded.len() - 1);
1578        assert!(Signature::<V>::decode_cfg(truncated, &()).is_err());
1579    }
1580
1581    #[test]
1582    fn test_certificate_decode_rejects_length_mismatch() {
1583        certificate_decode_rejects_length_mismatch::<MinPk>();
1584        certificate_decode_rejects_length_mismatch::<MinSig>();
1585    }
1586
1587    fn sign_vote_partial_matches_share<V: Variant>() {
1588        let (schemes, _) = setup_signers::<V>(4, 53);
1589        let scheme = &schemes[0];
1590        let share = scheme.share().expect("has share");
1591
1592        let proposal = sample_proposal(Epoch::new(0), View::new(23), 12);
1593        let vote = scheme
1594            .sign(Subject::Notarize {
1595                proposal: &proposal,
1596            })
1597            .unwrap();
1598
1599        let notarize_namespace = notarize_namespace(NAMESPACE);
1600        let notarize_message = proposal.encode();
1601        let expected_message = threshold::sign_message::<V>(
1602            share,
1603            notarize_namespace.as_ref(),
1604            notarize_message.as_ref(),
1605        )
1606        .value;
1607
1608        let seed_namespace = seed_namespace(NAMESPACE);
1609        let seed_message = proposal.round.encode();
1610        let expected_seed =
1611            threshold::sign_message::<V>(share, seed_namespace.as_ref(), seed_message.as_ref())
1612                .value;
1613
1614        assert_eq!(vote.signer, share.index);
1615        let sig = vote.signature.get().unwrap();
1616        assert_eq!(sig.vote_signature, expected_message);
1617        assert_eq!(sig.seed_signature, expected_seed);
1618    }
1619
1620    #[test]
1621    fn test_sign_vote_partial_matches_share() {
1622        sign_vote_partial_matches_share::<MinPk>();
1623        sign_vote_partial_matches_share::<MinSig>();
1624    }
1625
1626    fn verify_certificate_detects_seed_corruption<V: Variant>() {
1627        let mut rng = test_rng();
1628        let (schemes, verifier) = setup_signers::<V>(4, 59);
1629        let quorum = N3f1::quorum(schemes.len()) as usize;
1630        let proposal = sample_proposal(Epoch::new(0), View::new(25), 13);
1631
1632        let votes: Vec<_> = schemes
1633            .iter()
1634            .take(quorum)
1635            .map(|scheme| {
1636                scheme
1637                    .sign::<Sha256Digest>(Subject::Nullify {
1638                        round: proposal.round,
1639                    })
1640                    .unwrap()
1641            })
1642            .collect();
1643
1644        let certificate = schemes[0]
1645            .assemble::<_, N3f1>(votes, &Sequential)
1646            .expect("assemble certificate");
1647
1648        assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1649            &mut rng,
1650            Subject::Nullify {
1651                round: proposal.round,
1652            },
1653            &certificate,
1654            &Sequential,
1655        ));
1656
1657        let cert = certificate.get().unwrap();
1658        let corrupted: Certificate<V> = Signature {
1659            vote_signature: cert.vote_signature,
1660            seed_signature: cert.vote_signature,
1661        }
1662        .into();
1663        assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1664            &mut rng,
1665            Subject::Nullify {
1666                round: proposal.round,
1667            },
1668            &corrupted,
1669            &Sequential,
1670        ));
1671    }
1672
1673    #[test]
1674    fn test_verify_certificate_detects_seed_corruption() {
1675        verify_certificate_detects_seed_corruption::<MinPk>();
1676        verify_certificate_detects_seed_corruption::<MinSig>();
1677    }
1678
1679    fn encrypt_decrypt<V: Variant>() {
1680        let mut rng = test_rng();
1681        let (schemes, verifier) = setup_signers::<V>(4, 61);
1682        let quorum = N3f1::quorum(schemes.len()) as usize;
1683
1684        // Prepare a message to encrypt
1685        let message = b"Secret message for future view10";
1686
1687        // Target round for encryption
1688        let target = Round::new(Epoch::new(333), View::new(10));
1689
1690        // Encrypt using the scheme
1691        let ciphertext = schemes[0].encrypt(&mut rng, target, *message);
1692
1693        // Can also encrypt with the verifier scheme
1694        let ciphertext_verifier = verifier.encrypt(&mut rng, target, *message);
1695
1696        // Generate notarization for the target round to get the seed
1697        let proposal = sample_proposal(target.epoch(), target.view(), 14);
1698        let notarizes: Vec<_> = schemes
1699            .iter()
1700            .take(quorum)
1701            .map(|scheme| Notarize::sign(scheme, proposal.clone()).unwrap())
1702            .collect();
1703
1704        let notarization =
1705            Notarization::from_notarizes(&schemes[0], &notarizes, &Sequential).unwrap();
1706
1707        // Decrypt using the seed
1708        let seed = notarization.seed();
1709        let decrypted = seed.decrypt(&ciphertext).unwrap();
1710        assert_eq!(message, decrypted.as_ref());
1711
1712        let decrypted_verifier = seed.decrypt(&ciphertext_verifier).unwrap();
1713        assert_eq!(message, decrypted_verifier.as_ref());
1714    }
1715
1716    #[test]
1717    fn test_encrypt_decrypt() {
1718        encrypt_decrypt::<MinPk>();
1719        encrypt_decrypt::<MinSig>();
1720    }
1721
1722    fn verify_attestation_rejects_malleability<V: Variant>() {
1723        let mut rng = test_rng();
1724        let (schemes, _) = setup_signers::<V>(4, 67);
1725        let proposal = sample_proposal(Epoch::new(0), View::new(27), 14);
1726
1727        let attestation = schemes[0]
1728            .sign(Subject::Notarize {
1729                proposal: &proposal,
1730            })
1731            .unwrap();
1732
1733        assert!(schemes[0].verify_attestation::<_, Sha256Digest>(
1734            &mut rng,
1735            Subject::Notarize {
1736                proposal: &proposal,
1737            },
1738            &attestation,
1739            &Sequential,
1740        ));
1741
1742        let random_scalar = Scalar::random(&mut rng);
1743        let delta = V::Signature::generator() * &random_scalar;
1744        let att_sig = attestation.signature.get().unwrap();
1745        let forged_attestation: Attestation<Scheme<V>> = Attestation {
1746            signer: attestation.signer,
1747            signature: Signature {
1748                vote_signature: att_sig.vote_signature - &delta,
1749                seed_signature: att_sig.seed_signature + &delta,
1750            }
1751            .into(),
1752        };
1753
1754        let forged_sig = forged_attestation.signature.get().unwrap();
1755        let forged_sum = forged_sig.vote_signature + &forged_sig.seed_signature;
1756        let valid_sum = att_sig.vote_signature + &att_sig.seed_signature;
1757        assert_eq!(forged_sum, valid_sum, "signature sums should be equal");
1758
1759        assert!(
1760            !schemes[0].verify_attestation::<_, Sha256Digest>(
1761                &mut rng,
1762                Subject::Notarize {
1763                    proposal: &proposal,
1764                },
1765                &forged_attestation,
1766                &Sequential,
1767            ),
1768            "forged attestation should be rejected"
1769        );
1770    }
1771
1772    #[test]
1773    fn test_verify_attestation_rejects_malleability() {
1774        verify_attestation_rejects_malleability::<MinPk>();
1775        verify_attestation_rejects_malleability::<MinSig>();
1776    }
1777
1778    fn verify_attestations_rejects_malleability<V: Variant>() {
1779        let mut rng = test_rng();
1780        let (schemes, _) = setup_signers::<V>(4, 71);
1781        let proposal = sample_proposal(Epoch::new(0), View::new(29), 15);
1782
1783        let attestation1 = schemes[0]
1784            .sign(Subject::Notarize {
1785                proposal: &proposal,
1786            })
1787            .unwrap();
1788        let attestation2 = schemes[1]
1789            .sign(Subject::Notarize {
1790                proposal: &proposal,
1791            })
1792            .unwrap();
1793
1794        let verification = schemes[0].verify_attestations(
1795            &mut rng,
1796            Subject::Notarize {
1797                proposal: &proposal,
1798            },
1799            vec![attestation1.clone(), attestation2.clone()],
1800            &Sequential,
1801        );
1802        assert!(verification.invalid.is_empty());
1803        assert_eq!(verification.verified.len(), 2);
1804
1805        let random_scalar = Scalar::random(&mut rng);
1806        let delta = V::Signature::generator() * &random_scalar;
1807        let att1_sig = attestation1.signature.get().unwrap();
1808        let att2_sig = attestation2.signature.get().unwrap();
1809        let forged_attestation1: Attestation<Scheme<V>> = Attestation {
1810            signer: attestation1.signer,
1811            signature: Signature {
1812                vote_signature: att1_sig.vote_signature - &delta,
1813                seed_signature: att1_sig.seed_signature,
1814            }
1815            .into(),
1816        };
1817        let forged_attestation2: Attestation<Scheme<V>> = Attestation {
1818            signer: attestation2.signer,
1819            signature: Signature {
1820                vote_signature: att2_sig.vote_signature + &delta,
1821                seed_signature: att2_sig.seed_signature,
1822            }
1823            .into(),
1824        };
1825
1826        let forged1_sig = forged_attestation1.signature.get().unwrap();
1827        let forged2_sig = forged_attestation2.signature.get().unwrap();
1828        let forged_vote_sum = forged1_sig.vote_signature + &forged2_sig.vote_signature;
1829        let valid_vote_sum = att1_sig.vote_signature + &att2_sig.vote_signature;
1830        assert_eq!(
1831            forged_vote_sum, valid_vote_sum,
1832            "vote signature sums should be equal"
1833        );
1834
1835        let verification = schemes[0].verify_attestations(
1836            &mut rng,
1837            Subject::Notarize {
1838                proposal: &proposal,
1839            },
1840            vec![forged_attestation1, forged_attestation2],
1841            &Sequential,
1842        );
1843        assert!(
1844            !verification.invalid.is_empty(),
1845            "forged attestations should be detected"
1846        );
1847    }
1848
1849    #[test]
1850    fn test_verify_attestations_rejects_malleability() {
1851        verify_attestations_rejects_malleability::<MinPk>();
1852        verify_attestations_rejects_malleability::<MinSig>();
1853    }
1854
1855    fn verify_certificate_rejects_malleability<V: Variant>() {
1856        let mut rng = test_rng();
1857        let (schemes, verifier) = setup_signers::<V>(4, 73);
1858        let quorum = N3f1::quorum(schemes.len()) as usize;
1859        let proposal = sample_proposal(Epoch::new(0), View::new(31), 16);
1860
1861        let votes: Vec<_> = schemes
1862            .iter()
1863            .take(quorum)
1864            .map(|scheme| {
1865                scheme
1866                    .sign(Subject::Notarize {
1867                        proposal: &proposal,
1868                    })
1869                    .unwrap()
1870            })
1871            .collect();
1872
1873        let certificate = schemes[0]
1874            .assemble::<_, N3f1>(votes, &Sequential)
1875            .expect("assemble certificate");
1876
1877        assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1878            &mut rng,
1879            Subject::Notarize {
1880                proposal: &proposal,
1881            },
1882            &certificate,
1883            &Sequential,
1884        ));
1885
1886        let cert = certificate.get().unwrap();
1887        let random_scalar = Scalar::random(&mut rng);
1888        let delta = V::Signature::generator() * &random_scalar;
1889        let forged_certificate: Certificate<V> = Signature {
1890            vote_signature: cert.vote_signature - &delta,
1891            seed_signature: cert.seed_signature + &delta,
1892        }
1893        .into();
1894
1895        let forged_cert = forged_certificate.get().unwrap();
1896        let forged_sum = forged_cert.vote_signature + &forged_cert.seed_signature;
1897        let valid_sum = cert.vote_signature + &cert.seed_signature;
1898        assert_eq!(forged_sum, valid_sum, "signature sums should be equal");
1899
1900        assert!(
1901            !verifier.verify_certificate::<_, Sha256Digest, N3f1>(
1902                &mut rng,
1903                Subject::Notarize {
1904                    proposal: &proposal,
1905                },
1906                &forged_certificate,
1907                &Sequential,
1908            ),
1909            "forged certificate should be rejected"
1910        );
1911    }
1912
1913    #[test]
1914    fn test_verify_certificate_rejects_malleability() {
1915        verify_certificate_rejects_malleability::<MinPk>();
1916        verify_certificate_rejects_malleability::<MinSig>();
1917    }
1918
1919    fn verify_certificates_rejects_malleability<V: Variant>() {
1920        let mut rng = test_rng();
1921        let (schemes, verifier) = setup_signers::<V>(4, 79);
1922        let quorum = N3f1::quorum(schemes.len()) as usize;
1923        let proposal1 = sample_proposal(Epoch::new(0), View::new(33), 17);
1924        let proposal2 = sample_proposal(Epoch::new(0), View::new(34), 18);
1925
1926        let votes1: Vec<_> = schemes
1927            .iter()
1928            .take(quorum)
1929            .map(|scheme| {
1930                scheme
1931                    .sign(Subject::Notarize {
1932                        proposal: &proposal1,
1933                    })
1934                    .unwrap()
1935            })
1936            .collect();
1937        let votes2: Vec<_> = schemes
1938            .iter()
1939            .take(quorum)
1940            .map(|scheme| {
1941                scheme
1942                    .sign(Subject::Notarize {
1943                        proposal: &proposal2,
1944                    })
1945                    .unwrap()
1946            })
1947            .collect();
1948
1949        let certificate1 = schemes[0]
1950            .assemble::<_, N3f1>(votes1, &Sequential)
1951            .expect("assemble certificate1");
1952        let certificate2 = schemes[0]
1953            .assemble::<_, N3f1>(votes2, &Sequential)
1954            .expect("assemble certificate2");
1955
1956        assert!(verifier.verify_certificates::<_, Sha256Digest, _, N3f1>(
1957            &mut rng,
1958            [
1959                (
1960                    Subject::Notarize {
1961                        proposal: &proposal1,
1962                    },
1963                    &certificate1
1964                ),
1965                (
1966                    Subject::Notarize {
1967                        proposal: &proposal2,
1968                    },
1969                    &certificate2
1970                ),
1971            ]
1972            .into_iter(),
1973            &Sequential,
1974        ));
1975
1976        let cert1 = certificate1.get().unwrap();
1977        let cert2 = certificate2.get().unwrap();
1978        let random_scalar = Scalar::random(&mut rng);
1979        let delta = V::Signature::generator() * &random_scalar;
1980        let forged_certificate1: Certificate<V> = Signature {
1981            vote_signature: cert1.vote_signature - &delta,
1982            seed_signature: cert1.seed_signature,
1983        }
1984        .into();
1985        let forged_certificate2: Certificate<V> = Signature {
1986            vote_signature: cert2.vote_signature + &delta,
1987            seed_signature: cert2.seed_signature,
1988        }
1989        .into();
1990
1991        let forged1 = forged_certificate1.get().unwrap();
1992        let forged2 = forged_certificate2.get().unwrap();
1993        let forged_vote_sum = forged1.vote_signature + &forged2.vote_signature;
1994        let valid_vote_sum = cert1.vote_signature + &cert2.vote_signature;
1995        assert_eq!(
1996            forged_vote_sum, valid_vote_sum,
1997            "vote signature sums should be equal"
1998        );
1999
2000        assert!(
2001            !verifier.verify_certificates::<_, Sha256Digest, _, N3f1>(
2002                &mut rng,
2003                [
2004                    (
2005                        Subject::Notarize {
2006                            proposal: &proposal1,
2007                        },
2008                        &forged_certificate1
2009                    ),
2010                    (
2011                        Subject::Notarize {
2012                            proposal: &proposal2,
2013                        },
2014                        &forged_certificate2
2015                    ),
2016                ]
2017                .into_iter(),
2018                &Sequential,
2019            ),
2020            "forged certificates should be rejected"
2021        );
2022    }
2023
2024    #[test]
2025    fn test_verify_certificates_rejects_malleability() {
2026        verify_certificates_rejects_malleability::<MinPk>();
2027        verify_certificates_rejects_malleability::<MinSig>();
2028    }
2029
2030    #[cfg(feature = "arbitrary")]
2031    mod conformance {
2032        use super::*;
2033        use commonware_codec::conformance::CodecConformance;
2034
2035        commonware_conformance::conformance_tests! {
2036            CodecConformance<Signature<MinSig>>,
2037            CodecConformance<Certificate<MinSig>>,
2038            CodecConformance<Seed<MinSig>>,
2039        }
2040    }
2041}