Skip to main content

commonware_consensus/simplex/
types.rs

1//! Types used in [crate::simplex].
2
3use crate::{
4    Epochable, Viewable,
5    simplex::scheme::{self, CertificateVerifier},
6    types::{Epoch, Participant, Round, View},
7};
8use bytes::BufMut;
9use commonware_codec::{Buf, EncodeSize, Error, FixedSize, MaxEncodeSize, Read, ReadExt, Write};
10use commonware_cryptography::{
11    Digest, PublicKey,
12    certificate::{AssemblyError, Attestation, Scheme},
13};
14use commonware_parallel::Strategy;
15use commonware_utils::{iter::NonEmpty, max, non_empty};
16use rand_core::CryptoRng;
17use std::{fmt::Debug, hash::Hash};
18
19/// Context is a collection of metadata from consensus about a given payload.
20/// It provides information about the current epoch/view and the parent payload that new proposals are built on.
21#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct Context<D: Digest, P: PublicKey> {
23    /// Current round of consensus.
24    pub round: Round,
25    /// Leader of the current round.
26    pub leader: P,
27    /// Parent the payload is built on.
28    ///
29    /// When the current view is not a term start, the parent must be the immediately
30    /// previous view. When the current view is a term start, the parent may be an older
31    /// certified view as long as the participant possesses nullifications covering every
32    /// skipped term (a nullification covers the view it was created for and the remainder
33    /// of that term); any uncovered view may eventually be finalized and skipping it would
34    /// result in a fork. A certified parent remains valid even if a later nullification in
35    /// its own term covers the parent view.
36    ///
37    /// A pipelined handoff request instead names the previous view as its parent before that
38    /// view certifies (see [`crate::CertifiableAutomaton::prepare`]).
39    ///
40    /// A certified parent may never be finalized. Later proposals can skip it once a nullification
41    /// covers its view, and a competing payload can then be finalized in its place. Only treat the
42    /// parent as final once the [`Reporter`](crate::Reporter) receives an
43    /// [`Activity::Finalization`] for it or one of its descendants.
44    pub parent: (View, D),
45}
46
47impl<D: Digest, P: PublicKey> Epochable for Context<D, P> {
48    fn epoch(&self) -> Epoch {
49        self.round.epoch()
50    }
51}
52
53impl<D: Digest, P: PublicKey> Viewable for Context<D, P> {
54    fn view(&self) -> View {
55        self.round.view()
56    }
57}
58
59impl<D: Digest, P: PublicKey> Write for Context<D, P> {
60    fn write(&self, buf: &mut impl BufMut) {
61        self.round.write(buf);
62        self.leader.write(buf);
63        self.parent.write(buf);
64    }
65}
66
67impl<D: Digest, P: PublicKey> EncodeSize for Context<D, P> {
68    fn encode_size(&self) -> usize {
69        self.round.encode_size() + self.leader.encode_size() + self.parent.encode_size()
70    }
71}
72
73impl<D: Digest, P: PublicKey> MaxEncodeSize for Context<D, P> {
74    const MAX_ENCODE_SIZE: usize = Round::MAX_ENCODE_SIZE + P::SIZE + <(View, D)>::MAX_ENCODE_SIZE;
75}
76
77impl<D: Digest, P: PublicKey> Read for Context<D, P> {
78    type Cfg = ();
79
80    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
81        let round = Round::read(reader)?;
82        let leader = P::read(reader)?;
83        let parent = <(View, D)>::read_cfg(reader, &((), ()))?;
84
85        Ok(Self {
86            round,
87            leader,
88            parent,
89        })
90    }
91}
92
93#[cfg(feature = "arbitrary")]
94impl<D: Digest, P: PublicKey> arbitrary::Arbitrary<'_> for Context<D, P>
95where
96    D: for<'a> arbitrary::Arbitrary<'a>,
97    P: for<'a> arbitrary::Arbitrary<'a>,
98{
99    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
100        Ok(Self {
101            round: Round::arbitrary(u)?,
102            leader: P::arbitrary(u)?,
103            parent: (View::arbitrary(u)?, D::arbitrary(u)?),
104        })
105    }
106}
107
108/// Attributable is a trait that provides access to the signer index.
109/// This is used to identify which participant signed a given message.
110pub trait Attributable {
111    /// Returns the index of the signer (validator) who produced this message.
112    fn signer(&self) -> Participant;
113}
114
115/// A map of [Attributable] items keyed by their signer index.
116///
117/// The key for each item is automatically inferred from [Attributable::signer()].
118/// Each signer can insert at most one item.
119pub struct AttributableMap<T: Attributable> {
120    participants: usize,
121    data: Vec<Option<T>>,
122    added: usize,
123}
124
125impl<T: Attributable> AttributableMap<T> {
126    /// Creates a new [AttributableMap] with the given number of participants.
127    pub const fn new(participants: usize) -> Self {
128        Self {
129            participants,
130            data: Vec::new(),
131            added: 0,
132        }
133    }
134
135    /// Clears all existing items and releases their storage.
136    pub fn clear(&mut self) {
137        self.data = Vec::new();
138        self.added = 0;
139    }
140
141    /// Inserts an item into the map, using [Attributable::signer()] as the key,
142    /// if it has not been added yet.
143    ///
144    /// Returns `true` if the item was inserted, `false` if an item from this
145    /// signer already exists or if the signer index is out of bounds.
146    pub fn insert(&mut self, item: T) -> bool {
147        let index: usize = item.signer().into();
148        if index >= self.participants {
149            return false;
150        }
151        if self.data.is_empty() {
152            // `resize_with` avoids requiring `T: Clone` while pre-filling with `None`.
153            self.data.reserve_exact(self.participants);
154            self.data.resize_with(self.participants, || None);
155        }
156        if self.data[index].is_some() {
157            return false;
158        }
159        self.data[index] = Some(item);
160        self.added += 1;
161        true
162    }
163
164    /// Returns the number of items in the [AttributableMap].
165    pub const fn len(&self) -> usize {
166        self.added
167    }
168
169    /// Returns `true` if the [AttributableMap] is empty.
170    pub const fn is_empty(&self) -> bool {
171        self.added == 0
172    }
173
174    /// Returns a reference to the item associated with the given signer, if present.
175    pub fn get(&self, signer: Participant) -> Option<&T> {
176        self.data.get(<usize>::from(signer))?.as_ref()
177    }
178
179    /// Returns an iterator over items in the map, ordered by signer index
180    /// ([Attributable::signer()]).
181    pub fn iter(&self) -> impl Iterator<Item = &T> {
182        self.data.iter().filter_map(|o| o.as_ref())
183    }
184
185    /// Returns the allocated slot capacity.
186    #[cfg(test)]
187    pub(crate) const fn capacity(&self) -> usize {
188        self.data.capacity()
189    }
190}
191
192/// Identifies the subject of a vote or certificate.
193///
194/// Implementations use the subject to derive domain-separated message bytes for both
195/// individual votes and recovered certificates.
196#[derive(Copy, Clone, Debug)]
197pub enum Subject<'a, D: Digest> {
198    /// Subject for notarize votes and certificates, carrying the proposal.
199    Notarize { proposal: &'a Proposal<D> },
200    /// Subject for nullify votes and certificates, scoped to a round.
201    Nullify { round: Round },
202    /// Subject for finalize votes and certificates, carrying the proposal.
203    Finalize { proposal: &'a Proposal<D> },
204}
205
206impl<D: Digest> Viewable for Subject<'_, D> {
207    fn view(&self) -> View {
208        match self {
209            Subject::Notarize { proposal } => proposal.view(),
210            Subject::Nullify { round } => round.view(),
211            Subject::Finalize { proposal } => proposal.view(),
212        }
213    }
214}
215
216/// Vote represents individual votes ([Notarize], [Nullify], [Finalize]).
217#[derive(Clone, Debug, PartialEq)]
218pub enum Vote<S: Scheme, D: Digest> {
219    /// A validator's notarize vote over a proposal.
220    Notarize(Notarize<S, D>),
221    /// A validator's nullify vote used to skip the current view.
222    Nullify(Nullify<S>),
223    /// A validator's finalize vote over a proposal.
224    Finalize(Finalize<S, D>),
225}
226
227impl<S: Scheme, D: Digest> Write for Vote<S, D> {
228    fn write(&self, writer: &mut impl BufMut) {
229        match self {
230            Self::Notarize(v) => {
231                0u8.write(writer);
232                v.write(writer);
233            }
234            Self::Nullify(v) => {
235                1u8.write(writer);
236                v.write(writer);
237            }
238            Self::Finalize(v) => {
239                2u8.write(writer);
240                v.write(writer);
241            }
242        }
243    }
244}
245
246impl<S: Scheme, D: Digest> EncodeSize for Vote<S, D> {
247    fn encode_size(&self) -> usize {
248        1 + match self {
249            Self::Notarize(v) => v.encode_size(),
250            Self::Nullify(v) => v.encode_size(),
251            Self::Finalize(v) => v.encode_size(),
252        }
253    }
254}
255
256impl<S: Scheme, D: Digest> MaxEncodeSize for Vote<S, D> {
257    // A tag and the widest variant.
258    const MAX_ENCODE_SIZE: usize = u8::SIZE
259        + max(
260            max(
261                Notarize::<S, D>::MAX_ENCODE_SIZE,
262                Nullify::<S>::MAX_ENCODE_SIZE,
263            ),
264            Finalize::<S, D>::MAX_ENCODE_SIZE,
265        );
266}
267
268impl<S: Scheme, D: Digest> Read for Vote<S, D> {
269    type Cfg = ();
270
271    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
272        let tag = <u8>::read(reader)?;
273        match tag {
274            0 => {
275                let v = Notarize::read(reader)?;
276                Ok(Self::Notarize(v))
277            }
278            1 => {
279                let v = Nullify::read(reader)?;
280                Ok(Self::Nullify(v))
281            }
282            2 => {
283                let v = Finalize::read(reader)?;
284                Ok(Self::Finalize(v))
285            }
286            _ => Err(Error::Invalid("consensus::simplex::Vote", "Invalid type")),
287        }
288    }
289}
290
291impl<S: Scheme, D: Digest> Epochable for Vote<S, D> {
292    fn epoch(&self) -> Epoch {
293        match self {
294            Self::Notarize(v) => v.epoch(),
295            Self::Nullify(v) => v.epoch(),
296            Self::Finalize(v) => v.epoch(),
297        }
298    }
299}
300
301impl<S: Scheme, D: Digest> Viewable for Vote<S, D> {
302    fn view(&self) -> View {
303        match self {
304            Self::Notarize(v) => v.view(),
305            Self::Nullify(v) => v.view(),
306            Self::Finalize(v) => v.view(),
307        }
308    }
309}
310
311#[cfg(feature = "arbitrary")]
312impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Vote<S, D>
313where
314    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
315    D: for<'a> arbitrary::Arbitrary<'a>,
316{
317    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
318        let tag = u.int_in_range(0..=2)?;
319        match tag {
320            0 => {
321                let v = Notarize::arbitrary(u)?;
322                Ok(Self::Notarize(v))
323            }
324            1 => {
325                let v = Nullify::arbitrary(u)?;
326                Ok(Self::Nullify(v))
327            }
328            2 => {
329                let v = Finalize::arbitrary(u)?;
330                Ok(Self::Finalize(v))
331            }
332            _ => unreachable!(),
333        }
334    }
335}
336
337/// Certificate represents aggregated votes ([Notarization], [Nullification], [Finalization]).
338#[derive(Clone, Debug, PartialEq)]
339pub enum Certificate<S: Scheme, D: Digest> {
340    /// A recovered certificate for a notarization.
341    Notarization(Notarization<S, D>),
342    /// A recovered certificate for a nullification.
343    Nullification(Nullification<S>),
344    /// A recovered certificate for a finalization.
345    Finalization(Finalization<S, D>),
346}
347
348/// The discriminant of a [Certificate], naming its kind without its contents.
349#[cfg(not(target_arch = "wasm32"))]
350#[derive(Clone, Copy, Debug, PartialEq, Eq)]
351pub(crate) enum Kind {
352    Notarization,
353    Nullification,
354    Finalization,
355}
356
357#[cfg(not(target_arch = "wasm32"))]
358impl std::fmt::Display for Kind {
359    /// Writes the stable trace field value for this certificate type.
360    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361        f.write_str(match self {
362            Self::Notarization => "notarization",
363            Self::Nullification => "nullification",
364            Self::Finalization => "finalization",
365        })
366    }
367}
368
369impl<S: Scheme, D: Digest> Certificate<S, D> {
370    /// Returns an upper bound on the encoded size of any certificate for a committee of at most
371    /// `participants`.
372    pub fn max_encode_size(participants: u32) -> usize {
373        let notarization = Notarization::<S, D>::max_encode_size(participants);
374        let nullification = Nullification::<S>::max_encode_size(participants);
375        let finalization = Finalization::<S, D>::max_encode_size(participants);
376        notarization.max(nullification).max(finalization) + u8::SIZE
377    }
378
379    /// Returns this certificate's type.
380    #[cfg(not(target_arch = "wasm32"))]
381    pub(crate) const fn kind(&self) -> Kind {
382        match self {
383            Self::Notarization(_) => Kind::Notarization,
384            Self::Nullification(_) => Kind::Nullification,
385            Self::Finalization(_) => Kind::Finalization,
386        }
387    }
388
389    /// Verifies this certificate against the provided signing scheme.
390    pub fn verify<R: CryptoRng>(&self, rng: &mut R, scheme: &S, strategy: &impl Strategy) -> bool
391    where
392        S: scheme::Scheme<D>,
393    {
394        match self {
395            Self::Notarization(notarization) => notarization.verify(rng, scheme, strategy),
396            Self::Nullification(nullification) => {
397                nullification.verify::<_, D>(rng, scheme, strategy)
398            }
399            Self::Finalization(finalization) => finalization.verify(rng, scheme, strategy),
400        }
401    }
402}
403
404impl<S: Scheme, D: Digest> Write for Certificate<S, D> {
405    fn write(&self, writer: &mut impl BufMut) {
406        match self {
407            Self::Notarization(v) => {
408                0u8.write(writer);
409                v.write(writer);
410            }
411            Self::Nullification(v) => {
412                1u8.write(writer);
413                v.write(writer);
414            }
415            Self::Finalization(v) => {
416                2u8.write(writer);
417                v.write(writer);
418            }
419        }
420    }
421}
422
423impl<S: Scheme, D: Digest> EncodeSize for Certificate<S, D> {
424    fn encode_size(&self) -> usize {
425        1 + match self {
426            Self::Notarization(v) => v.encode_size(),
427            Self::Nullification(v) => v.encode_size(),
428            Self::Finalization(v) => v.encode_size(),
429        }
430    }
431}
432
433impl<S: Scheme, D: Digest> Read for Certificate<S, D> {
434    type Cfg = <S::Certificate as Read>::Cfg;
435
436    fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
437        let tag = <u8>::read(reader)?;
438        match tag {
439            0 => {
440                let v = Notarization::read_cfg(reader, cfg)?;
441                Ok(Self::Notarization(v))
442            }
443            1 => {
444                let v = Nullification::read_cfg(reader, cfg)?;
445                Ok(Self::Nullification(v))
446            }
447            2 => {
448                let v = Finalization::read_cfg(reader, cfg)?;
449                Ok(Self::Finalization(v))
450            }
451            _ => Err(Error::Invalid(
452                "consensus::simplex::Certificate",
453                "Invalid type",
454            )),
455        }
456    }
457}
458
459impl<S: Scheme, D: Digest> Epochable for Certificate<S, D> {
460    fn epoch(&self) -> Epoch {
461        match self {
462            Self::Notarization(v) => v.epoch(),
463            Self::Nullification(v) => v.epoch(),
464            Self::Finalization(v) => v.epoch(),
465        }
466    }
467}
468
469impl<S: Scheme, D: Digest> Viewable for Certificate<S, D> {
470    fn view(&self) -> View {
471        match self {
472            Self::Notarization(v) => v.view(),
473            Self::Nullification(v) => v.view(),
474            Self::Finalization(v) => v.view(),
475        }
476    }
477}
478
479#[cfg(feature = "arbitrary")]
480impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Certificate<S, D>
481where
482    S::Certificate: for<'a> arbitrary::Arbitrary<'a>,
483    D: for<'a> arbitrary::Arbitrary<'a>,
484{
485    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
486        let tag = u.int_in_range(0..=2)?;
487        match tag {
488            0 => {
489                let v = Notarization::arbitrary(u)?;
490                Ok(Self::Notarization(v))
491            }
492            1 => {
493                let v = Nullification::arbitrary(u)?;
494                Ok(Self::Nullification(v))
495            }
496            2 => {
497                let v = Finalization::arbitrary(u)?;
498                Ok(Self::Finalization(v))
499            }
500            _ => unreachable!(),
501        }
502    }
503}
504
505/// Artifact represents all consensus artifacts (votes and certificates) for storage.
506#[derive(Clone, Debug, PartialEq)]
507pub enum Artifact<S: Scheme, D: Digest> {
508    /// A validator's notarize vote over a proposal.
509    Notarize(Notarize<S, D>),
510    /// A recovered certificate for a notarization.
511    Notarization(Notarization<S, D>),
512    /// A notarization was locally certified.
513    Certification(Round, bool),
514    /// A validator's nullify vote used to skip the current view.
515    Nullify(Nullify<S>),
516    /// A recovered certificate for a nullification.
517    Nullification(Nullification<S>),
518    /// A validator's finalize vote over a proposal.
519    Finalize(Finalize<S, D>),
520    /// A recovered certificate for a finalization.
521    Finalization(Finalization<S, D>),
522}
523
524impl<S: Scheme, D: Digest> Write for Artifact<S, D> {
525    fn write(&self, writer: &mut impl BufMut) {
526        match self {
527            Self::Notarize(v) => {
528                0u8.write(writer);
529                v.write(writer);
530            }
531            Self::Notarization(v) => {
532                1u8.write(writer);
533                v.write(writer);
534            }
535            Self::Certification(r, b) => {
536                2u8.write(writer);
537                r.write(writer);
538                b.write(writer);
539            }
540            Self::Nullify(v) => {
541                3u8.write(writer);
542                v.write(writer);
543            }
544            Self::Nullification(v) => {
545                4u8.write(writer);
546                v.write(writer);
547            }
548            Self::Finalize(v) => {
549                5u8.write(writer);
550                v.write(writer);
551            }
552            Self::Finalization(v) => {
553                6u8.write(writer);
554                v.write(writer);
555            }
556        }
557    }
558}
559
560impl<S: Scheme, D: Digest> EncodeSize for Artifact<S, D> {
561    fn encode_size(&self) -> usize {
562        1 + match self {
563            Self::Notarize(v) => v.encode_size(),
564            Self::Notarization(v) => v.encode_size(),
565            Self::Certification(r, b) => r.encode_size() + b.encode_size(),
566            Self::Nullify(v) => v.encode_size(),
567            Self::Nullification(v) => v.encode_size(),
568            Self::Finalize(v) => v.encode_size(),
569            Self::Finalization(v) => v.encode_size(),
570        }
571    }
572}
573
574impl<S: Scheme, D: Digest> Read for Artifact<S, D> {
575    type Cfg = <S::Certificate as Read>::Cfg;
576
577    fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
578        let tag = <u8>::read(reader)?;
579        match tag {
580            0 => {
581                let v = Notarize::read(reader)?;
582                Ok(Self::Notarize(v))
583            }
584            1 => {
585                let v = Notarization::read_cfg(reader, cfg)?;
586                Ok(Self::Notarization(v))
587            }
588            2 => {
589                let r = Round::read(reader)?;
590                let b = bool::read(reader)?;
591                Ok(Self::Certification(r, b))
592            }
593            3 => {
594                let v = Nullify::read(reader)?;
595                Ok(Self::Nullify(v))
596            }
597            4 => {
598                let v = Nullification::read_cfg(reader, cfg)?;
599                Ok(Self::Nullification(v))
600            }
601            5 => {
602                let v = Finalize::read(reader)?;
603                Ok(Self::Finalize(v))
604            }
605            6 => {
606                let v = Finalization::read_cfg(reader, cfg)?;
607                Ok(Self::Finalization(v))
608            }
609            _ => Err(Error::Invalid(
610                "consensus::simplex::Artifact",
611                "Invalid type",
612            )),
613        }
614    }
615}
616
617impl<S: Scheme, D: Digest> Epochable for Artifact<S, D> {
618    fn epoch(&self) -> Epoch {
619        match self {
620            Self::Notarize(v) => v.epoch(),
621            Self::Notarization(v) => v.epoch(),
622            Self::Certification(r, _) => r.epoch(),
623            Self::Nullify(v) => v.epoch(),
624            Self::Nullification(v) => v.epoch(),
625            Self::Finalize(v) => v.epoch(),
626            Self::Finalization(v) => v.epoch(),
627        }
628    }
629}
630
631impl<S: Scheme, D: Digest> Viewable for Artifact<S, D> {
632    fn view(&self) -> View {
633        match self {
634            Self::Notarize(v) => v.view(),
635            Self::Notarization(v) => v.view(),
636            Self::Certification(r, _) => r.view(),
637            Self::Nullify(v) => v.view(),
638            Self::Nullification(v) => v.view(),
639            Self::Finalize(v) => v.view(),
640            Self::Finalization(v) => v.view(),
641        }
642    }
643}
644
645impl<S: Scheme, D: Digest> From<Vote<S, D>> for Artifact<S, D> {
646    fn from(vote: Vote<S, D>) -> Self {
647        match vote {
648            Vote::Notarize(v) => Self::Notarize(v),
649            Vote::Nullify(v) => Self::Nullify(v),
650            Vote::Finalize(v) => Self::Finalize(v),
651        }
652    }
653}
654
655impl<S: Scheme, D: Digest> From<Certificate<S, D>> for Artifact<S, D> {
656    fn from(cert: Certificate<S, D>) -> Self {
657        match cert {
658            Certificate::Notarization(v) => Self::Notarization(v),
659            Certificate::Nullification(v) => Self::Nullification(v),
660            Certificate::Finalization(v) => Self::Finalization(v),
661        }
662    }
663}
664
665#[cfg(feature = "arbitrary")]
666impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Artifact<S, D>
667where
668    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
669    S::Certificate: for<'a> arbitrary::Arbitrary<'a>,
670    D: for<'a> arbitrary::Arbitrary<'a>,
671{
672    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
673        let tag = u.int_in_range(0..=6)?;
674        match tag {
675            0 => {
676                let v = Notarize::arbitrary(u)?;
677                Ok(Self::Notarize(v))
678            }
679            1 => {
680                let v = Notarization::arbitrary(u)?;
681                Ok(Self::Notarization(v))
682            }
683            2 => {
684                let r = Round::arbitrary(u)?;
685                let b = bool::arbitrary(u)?;
686                Ok(Self::Certification(r, b))
687            }
688            3 => {
689                let v = Nullify::arbitrary(u)?;
690                Ok(Self::Nullify(v))
691            }
692            4 => {
693                let v = Nullification::arbitrary(u)?;
694                Ok(Self::Nullification(v))
695            }
696            5 => {
697                let v = Finalize::arbitrary(u)?;
698                Ok(Self::Finalize(v))
699            }
700            6 => {
701                let v = Finalization::arbitrary(u)?;
702                Ok(Self::Finalization(v))
703            }
704            _ => unreachable!(),
705        }
706    }
707}
708
709/// Proposal represents a proposed block in the protocol.
710/// It includes the view number, the parent view, and the actual payload (typically a digest of block data).
711#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
712pub struct Proposal<D: Digest> {
713    /// The round in which this proposal is made
714    pub round: Round,
715    /// The view of the parent proposal that this one builds upon
716    pub parent: View,
717    /// The actual payload/content of the proposal (typically a digest of the block data)
718    pub payload: D,
719}
720
721impl<D: Digest> Proposal<D> {
722    /// Creates a new proposal with the specified view, parent view, and payload.
723    pub const fn new(round: Round, parent: View, payload: D) -> Self {
724        Self {
725            round,
726            parent,
727            payload,
728        }
729    }
730}
731
732impl<D: Digest> Write for Proposal<D> {
733    fn write(&self, writer: &mut impl BufMut) {
734        self.round.write(writer);
735        self.parent.write(writer);
736        self.payload.write(writer)
737    }
738}
739
740impl<D: Digest> Read for Proposal<D> {
741    type Cfg = ();
742
743    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
744        let round = Round::read(reader)?;
745        let parent = View::read(reader)?;
746        let payload = D::read(reader)?;
747        Ok(Self {
748            round,
749            parent,
750            payload,
751        })
752    }
753}
754
755impl<D: Digest> EncodeSize for Proposal<D> {
756    fn encode_size(&self) -> usize {
757        self.round.encode_size() + self.parent.encode_size() + self.payload.encode_size()
758    }
759}
760
761impl<D: Digest> MaxEncodeSize for Proposal<D> {
762    const MAX_ENCODE_SIZE: usize = Round::MAX_ENCODE_SIZE + View::MAX_ENCODE_SIZE + D::SIZE;
763}
764
765impl<D: Digest> Epochable for Proposal<D> {
766    fn epoch(&self) -> Epoch {
767        self.round.epoch()
768    }
769}
770
771impl<D: Digest> Viewable for Proposal<D> {
772    fn view(&self) -> View {
773        self.round.view()
774    }
775}
776
777#[cfg(feature = "arbitrary")]
778impl<D: Digest> arbitrary::Arbitrary<'_> for Proposal<D>
779where
780    D: for<'a> arbitrary::Arbitrary<'a>,
781{
782    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
783        let round = Round::arbitrary(u)?;
784        let parent = View::arbitrary(u)?;
785        let payload = D::arbitrary(u)?;
786        Ok(Self {
787            round,
788            parent,
789            payload,
790        })
791    }
792}
793
794/// Validator vote that endorses a proposal for notarization.
795#[derive(Clone, Debug)]
796pub struct Notarize<S: Scheme, D: Digest> {
797    /// Proposal being notarized.
798    pub proposal: Proposal<D>,
799    /// Scheme-specific attestation material.
800    pub attestation: Attestation<S>,
801}
802
803impl<S: Scheme, D: Digest> Notarize<S, D> {
804    /// Signs a notarize vote for the provided proposal.
805    pub fn sign(scheme: &S, proposal: Proposal<D>) -> Option<Self>
806    where
807        S: scheme::Scheme<D>,
808    {
809        let attestation = scheme.sign::<D>(Subject::Notarize {
810            proposal: &proposal,
811        })?;
812
813        Some(Self {
814            proposal,
815            attestation,
816        })
817    }
818
819    /// Verifies the notarize vote against the provided signing scheme.
820    ///
821    /// This ensures that the notarize signature is valid for the claimed proposal.
822    pub fn verify<R>(&self, rng: &mut R, scheme: &S, strategy: &impl Strategy) -> bool
823    where
824        R: CryptoRng,
825        S: scheme::Scheme<D>,
826    {
827        scheme.verify_attestation::<_, D>(
828            rng,
829            Subject::Notarize {
830                proposal: &self.proposal,
831            },
832            &self.attestation,
833            strategy,
834        )
835    }
836
837    /// Returns the round associated with this notarize vote.
838    pub const fn round(&self) -> Round {
839        self.proposal.round
840    }
841}
842
843impl<S: Scheme, D: Digest> PartialEq for Notarize<S, D> {
844    fn eq(&self, other: &Self) -> bool {
845        self.proposal == other.proposal && self.attestation == other.attestation
846    }
847}
848
849impl<S: Scheme, D: Digest> Eq for Notarize<S, D> {}
850
851impl<S: Scheme, D: Digest> Hash for Notarize<S, D> {
852    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
853        self.proposal.hash(state);
854        self.attestation.hash(state);
855    }
856}
857
858impl<S: Scheme, D: Digest> Write for Notarize<S, D> {
859    fn write(&self, writer: &mut impl BufMut) {
860        self.proposal.write(writer);
861        self.attestation.write(writer);
862    }
863}
864
865impl<S: Scheme, D: Digest> EncodeSize for Notarize<S, D> {
866    fn encode_size(&self) -> usize {
867        self.proposal.encode_size() + self.attestation.encode_size()
868    }
869}
870
871impl<S: Scheme, D: Digest> MaxEncodeSize for Notarize<S, D> {
872    const MAX_ENCODE_SIZE: usize =
873        Proposal::<D>::MAX_ENCODE_SIZE + Attestation::<S>::MAX_ENCODE_SIZE;
874}
875
876impl<S: Scheme, D: Digest> Read for Notarize<S, D> {
877    type Cfg = ();
878
879    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
880        let proposal = Proposal::read(reader)?;
881        let attestation = Attestation::read(reader)?;
882
883        Ok(Self {
884            proposal,
885            attestation,
886        })
887    }
888}
889
890impl<S: Scheme, D: Digest> Attributable for Notarize<S, D> {
891    fn signer(&self) -> Participant {
892        self.attestation.signer
893    }
894}
895
896impl<S: Scheme, D: Digest> Epochable for Notarize<S, D> {
897    fn epoch(&self) -> Epoch {
898        self.proposal.epoch()
899    }
900}
901
902impl<S: Scheme, D: Digest> Viewable for Notarize<S, D> {
903    fn view(&self) -> View {
904        self.proposal.view()
905    }
906}
907
908#[cfg(feature = "arbitrary")]
909impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Notarize<S, D>
910where
911    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
912    D: for<'a> arbitrary::Arbitrary<'a>,
913{
914    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
915        let proposal = Proposal::arbitrary(u)?;
916        let attestation = Attestation::arbitrary(u)?;
917        Ok(Self {
918            proposal,
919            attestation,
920        })
921    }
922}
923
924/// Batch-verifies certificates and returns a per-item result.
925///
926/// Uses bisection to efficiently identify invalid certificates when batch
927/// verification fails.
928pub fn verify_certificates<'a, R, S, D>(
929    rng: &mut R,
930    scheme: &S,
931    certificates: &[(Subject<'a, D>, &'a S::Certificate)],
932    strategy: &impl Strategy,
933) -> Vec<bool>
934where
935    R: CryptoRng,
936    S: CertificateVerifier<D>,
937    D: Digest,
938{
939    scheme.verify_certificates_bisect::<_, D>(rng, certificates, strategy)
940}
941
942/// Aggregated notarization certificate recovered from notarize votes.
943/// When a proposal is notarized, it means at least 2f+1 validators have voted for it.
944///
945/// Some signing schemes (like [`super::scheme::bls12381_threshold::vrf`]) embed an additional
946/// randomness seed in the certificate. For threshold signatures, the seed can be accessed
947/// via [`super::scheme::bls12381_threshold::vrf::Seedable::seed`].
948#[derive(Clone, Debug)]
949pub struct Notarization<S: Scheme, D: Digest> {
950    /// The proposal that has been notarized.
951    pub proposal: Proposal<D>,
952    /// The recovered certificate for the proposal.
953    pub certificate: S::Certificate,
954}
955
956impl<S: Scheme, D: Digest> Notarization<S, D> {
957    /// Returns an upper bound on the encoded size of a notarization for a committee of at most
958    /// `participants`.
959    pub fn max_encode_size(participants: u32) -> usize {
960        S::max_certificate_size(participants) + Proposal::<D>::MAX_ENCODE_SIZE
961    }
962
963    /// Builds a notarization certificate from non-empty owned notarize votes for the same
964    /// proposal, consuming the votes to avoid cloning each attestation.
965    pub fn from_owned_notarizes<I>(
966        scheme: &S,
967        notarizes: NonEmpty<I>,
968        strategy: &impl Strategy,
969    ) -> Result<Self, AssemblyError>
970    where
971        I: Iterator<Item = Notarize<S, D>> + Send,
972    {
973        let (first, notarizes) = notarizes.into_parts();
974        let Notarize {
975            proposal,
976            attestation,
977        } = first;
978        let attestations =
979            NonEmpty::new(attestation, notarizes.map(|notarize| notarize.attestation));
980        let certificate = scheme.assemble(attestations, strategy)?;
981
982        Ok(Self {
983            proposal,
984            certificate,
985        })
986    }
987
988    /// Builds a notarization certificate from non-empty notarize votes for the same proposal.
989    pub fn from_notarizes<'a, I>(
990        scheme: &S,
991        notarizes: NonEmpty<I>,
992        strategy: &impl Strategy,
993    ) -> Result<Self, AssemblyError>
994    where
995        I: Iterator<Item = &'a Notarize<S, D>> + Send,
996    {
997        Self::from_owned_notarizes(
998            scheme,
999            non_empty![@notarizes.into_iter().cloned()],
1000            strategy,
1001        )
1002    }
1003
1004    /// Verifies the notarization certificate against the provided signing scheme.
1005    ///
1006    /// This ensures that the certificate is valid for the claimed proposal.
1007    pub fn verify<R: CryptoRng>(
1008        &self,
1009        rng: &mut R,
1010        scheme: &impl CertificateVerifier<D, Certificate = S::Certificate>,
1011        strategy: &impl Strategy,
1012    ) -> bool {
1013        scheme.verify_certificate::<_, D>(
1014            rng,
1015            Subject::Notarize {
1016                proposal: &self.proposal,
1017            },
1018            &self.certificate,
1019            strategy,
1020        )
1021    }
1022
1023    /// Returns the round associated with the notarized proposal.
1024    pub const fn round(&self) -> Round {
1025        self.proposal.round
1026    }
1027}
1028
1029impl<S: Scheme, D: Digest> PartialEq for Notarization<S, D> {
1030    fn eq(&self, other: &Self) -> bool {
1031        self.proposal == other.proposal && self.certificate == other.certificate
1032    }
1033}
1034
1035impl<S: Scheme, D: Digest> Eq for Notarization<S, D> {}
1036
1037impl<S: Scheme, D: Digest> Hash for Notarization<S, D> {
1038    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1039        self.proposal.hash(state);
1040        self.certificate.hash(state);
1041    }
1042}
1043
1044impl<S: Scheme, D: Digest> Write for Notarization<S, D> {
1045    fn write(&self, writer: &mut impl BufMut) {
1046        self.proposal.write(writer);
1047        self.certificate.write(writer);
1048    }
1049}
1050
1051impl<S: Scheme, D: Digest> EncodeSize for Notarization<S, D> {
1052    fn encode_size(&self) -> usize {
1053        self.proposal.encode_size() + self.certificate.encode_size()
1054    }
1055}
1056
1057impl<S: Scheme, D: Digest> Read for Notarization<S, D> {
1058    type Cfg = <S::Certificate as Read>::Cfg;
1059
1060    fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
1061        let proposal = Proposal::read(reader)?;
1062        let certificate = S::Certificate::read_cfg(reader, cfg)?;
1063
1064        Ok(Self {
1065            proposal,
1066            certificate,
1067        })
1068    }
1069}
1070
1071impl<S: Scheme, D: Digest> Epochable for Notarization<S, D> {
1072    fn epoch(&self) -> Epoch {
1073        self.proposal.epoch()
1074    }
1075}
1076
1077impl<S: Scheme, D: Digest> Viewable for Notarization<S, D> {
1078    fn view(&self) -> View {
1079        self.proposal.view()
1080    }
1081}
1082
1083#[cfg(feature = "arbitrary")]
1084impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Notarization<S, D>
1085where
1086    S::Certificate: for<'a> arbitrary::Arbitrary<'a>,
1087    D: for<'a> arbitrary::Arbitrary<'a>,
1088{
1089    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1090        let proposal = Proposal::arbitrary(u)?;
1091        let certificate = S::Certificate::arbitrary(u)?;
1092        Ok(Self {
1093            proposal,
1094            certificate,
1095        })
1096    }
1097}
1098
1099/// Validator vote for nullifying the current round, i.e. skip the current round.
1100/// This is typically used when the leader is unresponsive or fails to propose a valid block.
1101#[derive(Clone, Debug)]
1102pub struct Nullify<S: Scheme> {
1103    /// The round to be nullified (skipped).
1104    pub round: Round,
1105    /// Scheme-specific attestation material.
1106    pub attestation: Attestation<S>,
1107}
1108
1109impl<S: Scheme> PartialEq for Nullify<S> {
1110    fn eq(&self, other: &Self) -> bool {
1111        self.round == other.round && self.attestation == other.attestation
1112    }
1113}
1114
1115impl<S: Scheme> Eq for Nullify<S> {}
1116
1117impl<S: Scheme> Hash for Nullify<S> {
1118    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1119        self.round.hash(state);
1120        self.attestation.hash(state);
1121    }
1122}
1123
1124impl<S: Scheme> Nullify<S> {
1125    /// Signs a nullify vote for the given round.
1126    pub fn sign<D: Digest>(scheme: &S, round: Round) -> Option<Self>
1127    where
1128        S: scheme::Scheme<D>,
1129    {
1130        let attestation = scheme.sign::<D>(Subject::Nullify { round })?;
1131
1132        Some(Self { round, attestation })
1133    }
1134
1135    /// Verifies the nullify vote against the provided signing scheme.
1136    ///
1137    /// This ensures that the nullify signature is valid for the given round.
1138    pub fn verify<R, D: Digest>(&self, rng: &mut R, scheme: &S, strategy: &impl Strategy) -> bool
1139    where
1140        R: CryptoRng,
1141        S: scheme::Scheme<D>,
1142    {
1143        scheme.verify_attestation::<_, D>(
1144            rng,
1145            Subject::Nullify { round: self.round },
1146            &self.attestation,
1147            strategy,
1148        )
1149    }
1150
1151    /// Returns the round associated with this nullify vote.
1152    pub const fn round(&self) -> Round {
1153        self.round
1154    }
1155}
1156
1157impl<S: Scheme> Write for Nullify<S> {
1158    fn write(&self, writer: &mut impl BufMut) {
1159        self.round.write(writer);
1160        self.attestation.write(writer);
1161    }
1162}
1163
1164impl<S: Scheme> EncodeSize for Nullify<S> {
1165    fn encode_size(&self) -> usize {
1166        self.round.encode_size() + self.attestation.encode_size()
1167    }
1168}
1169
1170impl<S: Scheme> MaxEncodeSize for Nullify<S> {
1171    const MAX_ENCODE_SIZE: usize = Round::MAX_ENCODE_SIZE + Attestation::<S>::MAX_ENCODE_SIZE;
1172}
1173
1174impl<S: Scheme> Read for Nullify<S> {
1175    type Cfg = ();
1176
1177    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
1178        let round = Round::read(reader)?;
1179        let attestation = Attestation::read(reader)?;
1180
1181        Ok(Self { round, attestation })
1182    }
1183}
1184
1185impl<S: Scheme> Attributable for Nullify<S> {
1186    fn signer(&self) -> Participant {
1187        self.attestation.signer
1188    }
1189}
1190
1191impl<S: Scheme> Epochable for Nullify<S> {
1192    fn epoch(&self) -> Epoch {
1193        self.round.epoch()
1194    }
1195}
1196
1197impl<S: Scheme> Viewable for Nullify<S> {
1198    fn view(&self) -> View {
1199        self.round.view()
1200    }
1201}
1202
1203#[cfg(feature = "arbitrary")]
1204impl<S: Scheme> arbitrary::Arbitrary<'_> for Nullify<S>
1205where
1206    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
1207{
1208    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1209        let round = Round::arbitrary(u)?;
1210        let attestation = Attestation::arbitrary(u)?;
1211        Ok(Self { round, attestation })
1212    }
1213}
1214
1215/// Aggregated nullification certificate recovered from nullify votes.
1216/// When a view is nullified, consensus moves to the first view of the next
1217/// term without finalizing a block (the next view when `term_length` is 1);
1218/// a nullification covers the nullified view and the rest of its term.
1219#[derive(Clone, Debug)]
1220pub struct Nullification<S: Scheme> {
1221    /// The round in which this nullification is made.
1222    pub round: Round,
1223    /// The recovered certificate for the nullification.
1224    pub certificate: S::Certificate,
1225}
1226
1227impl<S: Scheme> Nullification<S> {
1228    /// Returns an upper bound on the encoded size of a nullification for a committee of at most
1229    /// `participants`.
1230    pub fn max_encode_size(participants: u32) -> usize {
1231        S::max_certificate_size(participants) + Round::MAX_ENCODE_SIZE
1232    }
1233
1234    /// Builds a nullification certificate from non-empty owned nullify votes from the same round,
1235    /// consuming the votes to avoid cloning each attestation.
1236    pub fn from_owned_nullifies<I>(
1237        scheme: &S,
1238        nullifies: NonEmpty<I>,
1239        strategy: &impl Strategy,
1240    ) -> Result<Self, AssemblyError>
1241    where
1242        I: Iterator<Item = Nullify<S>> + Send,
1243    {
1244        let (first, nullifies) = nullifies.into_parts();
1245        let round = first.round;
1246        let attestations = NonEmpty::new(
1247            first.attestation,
1248            nullifies.map(|nullify| nullify.attestation),
1249        );
1250        let certificate = scheme.assemble(attestations, strategy)?;
1251
1252        Ok(Self { round, certificate })
1253    }
1254
1255    /// Builds a nullification certificate from non-empty nullify votes from the same round.
1256    pub fn from_nullifies<'a, I>(
1257        scheme: &S,
1258        nullifies: NonEmpty<I>,
1259        strategy: &impl Strategy,
1260    ) -> Result<Self, AssemblyError>
1261    where
1262        I: Iterator<Item = &'a Nullify<S>> + Send,
1263    {
1264        Self::from_owned_nullifies(
1265            scheme,
1266            non_empty![@nullifies.into_iter().cloned()],
1267            strategy,
1268        )
1269    }
1270
1271    /// Verifies the nullification certificate against the provided signing scheme.
1272    ///
1273    /// This ensures that the certificate is valid for the claimed round.
1274    pub fn verify<R: CryptoRng, D: Digest>(
1275        &self,
1276        rng: &mut R,
1277        scheme: &impl CertificateVerifier<D, Certificate = S::Certificate>,
1278        strategy: &impl Strategy,
1279    ) -> bool {
1280        scheme.verify_certificate::<_, D>(
1281            rng,
1282            Subject::Nullify { round: self.round },
1283            &self.certificate,
1284            strategy,
1285        )
1286    }
1287
1288    /// Returns the round associated with this nullification.
1289    pub const fn round(&self) -> Round {
1290        self.round
1291    }
1292}
1293
1294impl<S: Scheme> PartialEq for Nullification<S> {
1295    fn eq(&self, other: &Self) -> bool {
1296        self.round == other.round && self.certificate == other.certificate
1297    }
1298}
1299
1300impl<S: Scheme> Eq for Nullification<S> {}
1301
1302impl<S: Scheme> Hash for Nullification<S> {
1303    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1304        self.round.hash(state);
1305        self.certificate.hash(state);
1306    }
1307}
1308
1309impl<S: Scheme> Write for Nullification<S> {
1310    fn write(&self, writer: &mut impl BufMut) {
1311        self.round.write(writer);
1312        self.certificate.write(writer);
1313    }
1314}
1315
1316impl<S: Scheme> EncodeSize for Nullification<S> {
1317    fn encode_size(&self) -> usize {
1318        self.round.encode_size() + self.certificate.encode_size()
1319    }
1320}
1321
1322impl<S: Scheme> Read for Nullification<S> {
1323    type Cfg = <S::Certificate as Read>::Cfg;
1324
1325    fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
1326        let round = Round::read(reader)?;
1327        let certificate = S::Certificate::read_cfg(reader, cfg)?;
1328
1329        Ok(Self { round, certificate })
1330    }
1331}
1332
1333impl<S: Scheme> Epochable for Nullification<S> {
1334    fn epoch(&self) -> Epoch {
1335        self.round.epoch()
1336    }
1337}
1338
1339impl<S: Scheme> Viewable for Nullification<S> {
1340    fn view(&self) -> View {
1341        self.round.view()
1342    }
1343}
1344
1345#[cfg(feature = "arbitrary")]
1346impl<S: Scheme> arbitrary::Arbitrary<'_> for Nullification<S>
1347where
1348    S::Certificate: for<'a> arbitrary::Arbitrary<'a>,
1349{
1350    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1351        let round = Round::arbitrary(u)?;
1352        let certificate = S::Certificate::arbitrary(u)?;
1353        Ok(Self { round, certificate })
1354    }
1355}
1356
1357/// Validator vote to finalize a proposal.
1358/// This happens after a proposal has been notarized, confirming it as the canonical block
1359/// for this round.
1360#[derive(Clone, Debug)]
1361pub struct Finalize<S: Scheme, D: Digest> {
1362    /// Proposal being finalized.
1363    pub proposal: Proposal<D>,
1364    /// Scheme-specific attestation material.
1365    pub attestation: Attestation<S>,
1366}
1367
1368impl<S: Scheme, D: Digest> Finalize<S, D> {
1369    /// Signs a finalize vote for the provided proposal.
1370    pub fn sign(scheme: &S, proposal: Proposal<D>) -> Option<Self>
1371    where
1372        S: scheme::Scheme<D>,
1373    {
1374        let attestation = scheme.sign::<D>(Subject::Finalize {
1375            proposal: &proposal,
1376        })?;
1377
1378        Some(Self {
1379            proposal,
1380            attestation,
1381        })
1382    }
1383
1384    /// Verifies the finalize vote against the provided signing scheme.
1385    ///
1386    /// This ensures that the finalize signature is valid for the claimed proposal.
1387    pub fn verify<R>(&self, rng: &mut R, scheme: &S, strategy: &impl Strategy) -> bool
1388    where
1389        R: CryptoRng,
1390        S: scheme::Scheme<D>,
1391    {
1392        scheme.verify_attestation::<_, D>(
1393            rng,
1394            Subject::Finalize {
1395                proposal: &self.proposal,
1396            },
1397            &self.attestation,
1398            strategy,
1399        )
1400    }
1401
1402    /// Returns the round associated with this finalize vote.
1403    pub const fn round(&self) -> Round {
1404        self.proposal.round
1405    }
1406}
1407
1408impl<S: Scheme, D: Digest> PartialEq for Finalize<S, D> {
1409    fn eq(&self, other: &Self) -> bool {
1410        self.proposal == other.proposal && self.attestation == other.attestation
1411    }
1412}
1413
1414impl<S: Scheme, D: Digest> Eq for Finalize<S, D> {}
1415
1416impl<S: Scheme, D: Digest> Hash for Finalize<S, D> {
1417    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1418        self.proposal.hash(state);
1419        self.attestation.hash(state);
1420    }
1421}
1422
1423impl<S: Scheme, D: Digest> Write for Finalize<S, D> {
1424    fn write(&self, writer: &mut impl BufMut) {
1425        self.proposal.write(writer);
1426        self.attestation.write(writer);
1427    }
1428}
1429
1430impl<S: Scheme, D: Digest> EncodeSize for Finalize<S, D> {
1431    fn encode_size(&self) -> usize {
1432        self.proposal.encode_size() + self.attestation.encode_size()
1433    }
1434}
1435
1436impl<S: Scheme, D: Digest> MaxEncodeSize for Finalize<S, D> {
1437    const MAX_ENCODE_SIZE: usize =
1438        Proposal::<D>::MAX_ENCODE_SIZE + Attestation::<S>::MAX_ENCODE_SIZE;
1439}
1440
1441impl<S: Scheme, D: Digest> Read for Finalize<S, D> {
1442    type Cfg = ();
1443
1444    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
1445        let proposal = Proposal::read(reader)?;
1446        let attestation = Attestation::read(reader)?;
1447
1448        Ok(Self {
1449            proposal,
1450            attestation,
1451        })
1452    }
1453}
1454
1455impl<S: Scheme, D: Digest> Attributable for Finalize<S, D> {
1456    fn signer(&self) -> Participant {
1457        self.attestation.signer
1458    }
1459}
1460
1461impl<S: Scheme, D: Digest> Epochable for Finalize<S, D> {
1462    fn epoch(&self) -> Epoch {
1463        self.proposal.epoch()
1464    }
1465}
1466
1467impl<S: Scheme, D: Digest> Viewable for Finalize<S, D> {
1468    fn view(&self) -> View {
1469        self.proposal.view()
1470    }
1471}
1472
1473#[cfg(feature = "arbitrary")]
1474impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Finalize<S, D>
1475where
1476    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
1477    D: for<'a> arbitrary::Arbitrary<'a>,
1478{
1479    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1480        let proposal = Proposal::arbitrary(u)?;
1481        let attestation = Attestation::arbitrary(u)?;
1482        Ok(Self {
1483            proposal,
1484            attestation,
1485        })
1486    }
1487}
1488
1489/// Aggregated finalization certificate recovered from finalize votes.
1490/// When a proposal is finalized, it becomes the canonical block for its view.
1491///
1492/// Some signing schemes (like [`super::scheme::bls12381_threshold::vrf`]) embed an additional
1493/// randomness seed in the certificate. For threshold signatures, the seed can be accessed
1494/// via [`super::scheme::bls12381_threshold::vrf::Seedable::seed`].
1495#[derive(Clone, Debug)]
1496pub struct Finalization<S: Scheme, D: Digest> {
1497    /// The proposal that has been finalized.
1498    pub proposal: Proposal<D>,
1499    /// The recovered certificate for the proposal.
1500    pub certificate: S::Certificate,
1501}
1502
1503impl<S: Scheme, D: Digest> Finalization<S, D> {
1504    /// Returns an upper bound on the encoded size of a finalization for a committee of at most
1505    /// `participants`.
1506    pub fn max_encode_size(participants: u32) -> usize {
1507        S::max_certificate_size(participants) + Proposal::<D>::MAX_ENCODE_SIZE
1508    }
1509
1510    /// Builds a finalization certificate from non-empty owned finalize votes for the same proposal,
1511    /// consuming the votes to avoid cloning each attestation.
1512    pub fn from_owned_finalizes<I>(
1513        scheme: &S,
1514        finalizes: NonEmpty<I>,
1515        strategy: &impl Strategy,
1516    ) -> Result<Self, AssemblyError>
1517    where
1518        I: Iterator<Item = Finalize<S, D>> + Send,
1519    {
1520        let (first, finalizes) = finalizes.into_parts();
1521        let Finalize {
1522            proposal,
1523            attestation,
1524        } = first;
1525        let attestations =
1526            NonEmpty::new(attestation, finalizes.map(|finalize| finalize.attestation));
1527        let certificate = scheme.assemble(attestations, strategy)?;
1528
1529        Ok(Self {
1530            proposal,
1531            certificate,
1532        })
1533    }
1534
1535    /// Builds a finalization certificate from non-empty finalize votes for the same proposal.
1536    pub fn from_finalizes<'a, I>(
1537        scheme: &S,
1538        finalizes: NonEmpty<I>,
1539        strategy: &impl Strategy,
1540    ) -> Result<Self, AssemblyError>
1541    where
1542        I: Iterator<Item = &'a Finalize<S, D>> + Send,
1543    {
1544        Self::from_owned_finalizes(
1545            scheme,
1546            non_empty![@finalizes.into_iter().cloned()],
1547            strategy,
1548        )
1549    }
1550
1551    /// Verifies the finalization certificate against the provided signing scheme.
1552    ///
1553    /// This ensures that the certificate is valid for the claimed proposal.
1554    pub fn verify<R: CryptoRng>(
1555        &self,
1556        rng: &mut R,
1557        scheme: &impl CertificateVerifier<D, Certificate = S::Certificate>,
1558        strategy: &impl Strategy,
1559    ) -> bool {
1560        scheme.verify_certificate::<_, D>(
1561            rng,
1562            Subject::Finalize {
1563                proposal: &self.proposal,
1564            },
1565            &self.certificate,
1566            strategy,
1567        )
1568    }
1569
1570    /// Returns the round associated with the finalized proposal.
1571    pub const fn round(&self) -> Round {
1572        self.proposal.round
1573    }
1574}
1575
1576impl<S: Scheme, D: Digest> PartialEq for Finalization<S, D> {
1577    fn eq(&self, other: &Self) -> bool {
1578        self.proposal == other.proposal && self.certificate == other.certificate
1579    }
1580}
1581
1582impl<S: Scheme, D: Digest> Eq for Finalization<S, D> {}
1583
1584impl<S: Scheme, D: Digest> Hash for Finalization<S, D> {
1585    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1586        self.proposal.hash(state);
1587        self.certificate.hash(state);
1588    }
1589}
1590
1591impl<S: Scheme, D: Digest> Write for Finalization<S, D> {
1592    fn write(&self, writer: &mut impl BufMut) {
1593        self.proposal.write(writer);
1594        self.certificate.write(writer);
1595    }
1596}
1597
1598impl<S: Scheme, D: Digest> EncodeSize for Finalization<S, D> {
1599    fn encode_size(&self) -> usize {
1600        self.proposal.encode_size() + self.certificate.encode_size()
1601    }
1602}
1603
1604impl<S: Scheme, D: Digest> Read for Finalization<S, D> {
1605    type Cfg = <S::Certificate as Read>::Cfg;
1606
1607    fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
1608        let proposal = Proposal::read(reader)?;
1609        let certificate = S::Certificate::read_cfg(reader, cfg)?;
1610
1611        Ok(Self {
1612            proposal,
1613            certificate,
1614        })
1615    }
1616}
1617
1618impl<S: Scheme, D: Digest> Epochable for Finalization<S, D> {
1619    fn epoch(&self) -> Epoch {
1620        self.proposal.epoch()
1621    }
1622}
1623
1624impl<S: Scheme, D: Digest> Viewable for Finalization<S, D> {
1625    fn view(&self) -> View {
1626        self.proposal.view()
1627    }
1628}
1629
1630#[cfg(feature = "arbitrary")]
1631impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Finalization<S, D>
1632where
1633    S::Certificate: for<'a> arbitrary::Arbitrary<'a>,
1634    D: for<'a> arbitrary::Arbitrary<'a>,
1635{
1636    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1637        let proposal = Proposal::arbitrary(u)?;
1638        let certificate = S::Certificate::arbitrary(u)?;
1639        Ok(Self {
1640            proposal,
1641            certificate,
1642        })
1643    }
1644}
1645
1646/// Activity represents all possible activities that can occur in the consensus protocol.
1647/// This includes both regular consensus messages and fault evidence.
1648///
1649/// # Verification
1650///
1651/// Some activities issued by consensus are not guaranteed to be cryptographically verified. Use
1652/// [`Activity::verified`] to check whether an activity may be unverified, and [`Activity::verify`] to
1653/// perform verification.
1654///
1655/// # Activity Filtering
1656///
1657/// For **non-attributable** schemes like [`crate::simplex::scheme::bls12381_threshold`], exposing
1658/// per-validator activity as fault evidence is not safe: with threshold cryptography, any `t` valid partial signatures can
1659/// be used to forge a partial signature for any player.
1660///
1661/// Use [`crate::simplex::scheme::reporter::AttributableReporter`] to automatically filter and
1662/// verify activities based on [`Scheme::is_attributable`].
1663#[derive(Clone, Debug)]
1664pub enum Activity<S: Scheme, D: Digest> {
1665    /// A validator's notarize vote over a proposal.
1666    Notarize(Notarize<S, D>),
1667    /// A recovered certificate for a notarization (scheme-specific).
1668    Notarization(Notarization<S, D>),
1669    /// A notarization was locally certified.
1670    Certification(Notarization<S, D>),
1671    /// A validator's nullify vote used to skip the current view.
1672    Nullify(Nullify<S>),
1673    /// A recovered certificate for a nullification (scheme-specific).
1674    Nullification(Nullification<S>),
1675    /// A validator's finalize vote over a proposal.
1676    Finalize(Finalize<S, D>),
1677    /// A recovered certificate for a finalization (scheme-specific).
1678    Finalization(Finalization<S, D>),
1679    /// Evidence of a validator sending conflicting notarizes (Byzantine behavior).
1680    ConflictingNotarize(ConflictingNotarize<S, D>),
1681    /// Evidence of a validator sending conflicting finalizes (Byzantine behavior).
1682    ConflictingFinalize(ConflictingFinalize<S, D>),
1683    /// Evidence of a validator sending both nullify and finalize for the same view (Byzantine behavior).
1684    NullifyFinalize(NullifyFinalize<S, D>),
1685}
1686
1687impl<S: Scheme, D: Digest> PartialEq for Activity<S, D> {
1688    fn eq(&self, other: &Self) -> bool {
1689        match (self, other) {
1690            (Self::Notarize(a), Self::Notarize(b)) => a == b,
1691            (Self::Notarization(a), Self::Notarization(b)) => a == b,
1692            (Self::Certification(a), Self::Certification(b)) => a == b,
1693            (Self::Nullify(a), Self::Nullify(b)) => a == b,
1694            (Self::Nullification(a), Self::Nullification(b)) => a == b,
1695            (Self::Finalize(a), Self::Finalize(b)) => a == b,
1696            (Self::Finalization(a), Self::Finalization(b)) => a == b,
1697            (Self::ConflictingNotarize(a), Self::ConflictingNotarize(b)) => a == b,
1698            (Self::ConflictingFinalize(a), Self::ConflictingFinalize(b)) => a == b,
1699            (Self::NullifyFinalize(a), Self::NullifyFinalize(b)) => a == b,
1700            _ => false,
1701        }
1702    }
1703}
1704
1705impl<S: Scheme, D: Digest> Eq for Activity<S, D> {}
1706
1707impl<S: Scheme, D: Digest> Hash for Activity<S, D> {
1708    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1709        match self {
1710            Self::Notarize(v) => {
1711                0u8.hash(state);
1712                v.hash(state);
1713            }
1714            Self::Notarization(v) => {
1715                1u8.hash(state);
1716                v.hash(state);
1717            }
1718            Self::Certification(v) => {
1719                2u8.hash(state);
1720                v.hash(state);
1721            }
1722            Self::Nullify(v) => {
1723                3u8.hash(state);
1724                v.hash(state);
1725            }
1726            Self::Nullification(v) => {
1727                4u8.hash(state);
1728                v.hash(state);
1729            }
1730            Self::Finalize(v) => {
1731                5u8.hash(state);
1732                v.hash(state);
1733            }
1734            Self::Finalization(v) => {
1735                6u8.hash(state);
1736                v.hash(state);
1737            }
1738            Self::ConflictingNotarize(v) => {
1739                7u8.hash(state);
1740                v.hash(state);
1741            }
1742            Self::ConflictingFinalize(v) => {
1743                8u8.hash(state);
1744                v.hash(state);
1745            }
1746            Self::NullifyFinalize(v) => {
1747                9u8.hash(state);
1748                v.hash(state);
1749            }
1750        }
1751    }
1752}
1753
1754impl<S: Scheme, D: Digest> Activity<S, D> {
1755    /// Indicates whether the activity is guaranteed to have been verified by consensus.
1756    pub const fn verified(&self) -> bool {
1757        match self {
1758            Self::Notarize(_) => false,
1759            Self::Notarization(_) => true,
1760            Self::Certification(_) => true,
1761            Self::Nullify(_) => false,
1762            Self::Nullification(_) => true,
1763            Self::Finalize(_) => false,
1764            Self::Finalization(_) => true,
1765            Self::ConflictingNotarize(_) => false,
1766            Self::ConflictingFinalize(_) => false,
1767            Self::NullifyFinalize(_) => false,
1768        }
1769    }
1770
1771    /// Verifies the validity of this activity against the signing scheme.
1772    ///
1773    /// This method **always** performs verification regardless of whether the activity has been
1774    /// previously verified. Callers can use [`Activity::verified`] to check if verification is
1775    /// necessary before calling this method.
1776    pub fn verify<R: CryptoRng>(&self, rng: &mut R, scheme: &S, strategy: &impl Strategy) -> bool
1777    where
1778        S: scheme::Scheme<D>,
1779    {
1780        match self {
1781            Self::Notarize(n) => n.verify(rng, scheme, strategy),
1782            Self::Notarization(n) => n.verify(rng, scheme, strategy),
1783            Self::Certification(n) => n.verify(rng, scheme, strategy),
1784            Self::Nullify(n) => n.verify(rng, scheme, strategy),
1785            Self::Nullification(n) => n.verify(rng, scheme, strategy),
1786            Self::Finalize(f) => f.verify(rng, scheme, strategy),
1787            Self::Finalization(f) => f.verify(rng, scheme, strategy),
1788            Self::ConflictingNotarize(c) => c.verify(rng, scheme, strategy),
1789            Self::ConflictingFinalize(c) => c.verify(rng, scheme, strategy),
1790            Self::NullifyFinalize(c) => c.verify(rng, scheme, strategy),
1791        }
1792    }
1793}
1794
1795impl<S: Scheme, D: Digest> Write for Activity<S, D> {
1796    fn write(&self, writer: &mut impl BufMut) {
1797        match self {
1798            Self::Notarize(v) => {
1799                0u8.write(writer);
1800                v.write(writer);
1801            }
1802            Self::Notarization(v) => {
1803                1u8.write(writer);
1804                v.write(writer);
1805            }
1806            Self::Certification(v) => {
1807                2u8.write(writer);
1808                v.write(writer);
1809            }
1810            Self::Nullify(v) => {
1811                3u8.write(writer);
1812                v.write(writer);
1813            }
1814            Self::Nullification(v) => {
1815                4u8.write(writer);
1816                v.write(writer);
1817            }
1818            Self::Finalize(v) => {
1819                5u8.write(writer);
1820                v.write(writer);
1821            }
1822            Self::Finalization(v) => {
1823                6u8.write(writer);
1824                v.write(writer);
1825            }
1826            Self::ConflictingNotarize(v) => {
1827                7u8.write(writer);
1828                v.write(writer);
1829            }
1830            Self::ConflictingFinalize(v) => {
1831                8u8.write(writer);
1832                v.write(writer);
1833            }
1834            Self::NullifyFinalize(v) => {
1835                9u8.write(writer);
1836                v.write(writer);
1837            }
1838        }
1839    }
1840}
1841
1842impl<S: Scheme, D: Digest> EncodeSize for Activity<S, D> {
1843    fn encode_size(&self) -> usize {
1844        1 + match self {
1845            Self::Notarize(v) => v.encode_size(),
1846            Self::Notarization(v) => v.encode_size(),
1847            Self::Certification(v) => v.encode_size(),
1848            Self::Nullify(v) => v.encode_size(),
1849            Self::Nullification(v) => v.encode_size(),
1850            Self::Finalize(v) => v.encode_size(),
1851            Self::Finalization(v) => v.encode_size(),
1852            Self::ConflictingNotarize(v) => v.encode_size(),
1853            Self::ConflictingFinalize(v) => v.encode_size(),
1854            Self::NullifyFinalize(v) => v.encode_size(),
1855        }
1856    }
1857}
1858
1859impl<S: Scheme, D: Digest> Read for Activity<S, D> {
1860    type Cfg = <S::Certificate as Read>::Cfg;
1861
1862    fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
1863        let tag = <u8>::read(reader)?;
1864        match tag {
1865            0 => {
1866                let v = Notarize::<S, D>::read(reader)?;
1867                Ok(Self::Notarize(v))
1868            }
1869            1 => {
1870                let v = Notarization::<S, D>::read_cfg(reader, cfg)?;
1871                Ok(Self::Notarization(v))
1872            }
1873            2 => {
1874                let v = Notarization::<S, D>::read_cfg(reader, cfg)?;
1875                Ok(Self::Certification(v))
1876            }
1877            3 => {
1878                let v = Nullify::<S>::read(reader)?;
1879                Ok(Self::Nullify(v))
1880            }
1881            4 => {
1882                let v = Nullification::<S>::read_cfg(reader, cfg)?;
1883                Ok(Self::Nullification(v))
1884            }
1885            5 => {
1886                let v = Finalize::<S, D>::read(reader)?;
1887                Ok(Self::Finalize(v))
1888            }
1889            6 => {
1890                let v = Finalization::<S, D>::read_cfg(reader, cfg)?;
1891                Ok(Self::Finalization(v))
1892            }
1893            7 => {
1894                let v = ConflictingNotarize::<S, D>::read(reader)?;
1895                Ok(Self::ConflictingNotarize(v))
1896            }
1897            8 => {
1898                let v = ConflictingFinalize::<S, D>::read(reader)?;
1899                Ok(Self::ConflictingFinalize(v))
1900            }
1901            9 => {
1902                let v = NullifyFinalize::<S, D>::read(reader)?;
1903                Ok(Self::NullifyFinalize(v))
1904            }
1905            _ => Err(Error::Invalid(
1906                "consensus::simplex::Activity",
1907                "Invalid type",
1908            )),
1909        }
1910    }
1911}
1912
1913impl<S: Scheme, D: Digest> Epochable for Activity<S, D> {
1914    fn epoch(&self) -> Epoch {
1915        match self {
1916            Self::Notarize(v) => v.epoch(),
1917            Self::Notarization(v) => v.epoch(),
1918            Self::Certification(v) => v.epoch(),
1919            Self::Nullify(v) => v.epoch(),
1920            Self::Nullification(v) => v.epoch(),
1921            Self::Finalize(v) => v.epoch(),
1922            Self::Finalization(v) => v.epoch(),
1923            Self::ConflictingNotarize(v) => v.epoch(),
1924            Self::ConflictingFinalize(v) => v.epoch(),
1925            Self::NullifyFinalize(v) => v.epoch(),
1926        }
1927    }
1928}
1929
1930impl<S: Scheme, D: Digest> Viewable for Activity<S, D> {
1931    fn view(&self) -> View {
1932        match self {
1933            Self::Notarize(v) => v.view(),
1934            Self::Notarization(v) => v.view(),
1935            Self::Certification(v) => v.view(),
1936            Self::Nullify(v) => v.view(),
1937            Self::Nullification(v) => v.view(),
1938            Self::Finalize(v) => v.view(),
1939            Self::Finalization(v) => v.view(),
1940            Self::ConflictingNotarize(v) => v.view(),
1941            Self::ConflictingFinalize(v) => v.view(),
1942            Self::NullifyFinalize(v) => v.view(),
1943        }
1944    }
1945}
1946
1947#[cfg(feature = "arbitrary")]
1948impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Activity<S, D>
1949where
1950    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
1951    S::Certificate: for<'a> arbitrary::Arbitrary<'a>,
1952    D: for<'a> arbitrary::Arbitrary<'a>,
1953{
1954    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1955        let tag = u.int_in_range(0..=9)?;
1956        match tag {
1957            0 => {
1958                let v = Notarize::<S, D>::arbitrary(u)?;
1959                Ok(Self::Notarize(v))
1960            }
1961            1 => {
1962                let v = Notarization::<S, D>::arbitrary(u)?;
1963                Ok(Self::Notarization(v))
1964            }
1965            2 => {
1966                let v = Notarization::<S, D>::arbitrary(u)?;
1967                Ok(Self::Certification(v))
1968            }
1969            3 => {
1970                let v = Nullify::<S>::arbitrary(u)?;
1971                Ok(Self::Nullify(v))
1972            }
1973            4 => {
1974                let v = Nullification::<S>::arbitrary(u)?;
1975                Ok(Self::Nullification(v))
1976            }
1977            5 => {
1978                let v = Finalize::<S, D>::arbitrary(u)?;
1979                Ok(Self::Finalize(v))
1980            }
1981            6 => {
1982                let v = Finalization::<S, D>::arbitrary(u)?;
1983                Ok(Self::Finalization(v))
1984            }
1985            7 => {
1986                let v = ConflictingNotarize::<S, D>::arbitrary(u)?;
1987                Ok(Self::ConflictingNotarize(v))
1988            }
1989            8 => {
1990                let v = ConflictingFinalize::<S, D>::arbitrary(u)?;
1991                Ok(Self::ConflictingFinalize(v))
1992            }
1993            9 => {
1994                let v = NullifyFinalize::<S, D>::arbitrary(u)?;
1995                Ok(Self::NullifyFinalize(v))
1996            }
1997            _ => unreachable!(),
1998        }
1999    }
2000}
2001
2002/// ConflictingNotarize represents evidence of a Byzantine validator sending conflicting notarizes.
2003/// This is used to prove that a validator has equivocated (voted for different proposals in the same view).
2004#[derive(Clone, Debug)]
2005pub struct ConflictingNotarize<S: Scheme, D: Digest> {
2006    /// The first conflicting notarize
2007    notarize_1: Notarize<S, D>,
2008    /// The second conflicting notarize
2009    notarize_2: Notarize<S, D>,
2010}
2011
2012impl<S: Scheme, D: Digest> PartialEq for ConflictingNotarize<S, D> {
2013    fn eq(&self, other: &Self) -> bool {
2014        self.notarize_1 == other.notarize_1 && self.notarize_2 == other.notarize_2
2015    }
2016}
2017
2018impl<S: Scheme, D: Digest> Eq for ConflictingNotarize<S, D> {}
2019
2020impl<S: Scheme, D: Digest> Hash for ConflictingNotarize<S, D> {
2021    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2022        self.notarize_1.hash(state);
2023        self.notarize_2.hash(state);
2024    }
2025}
2026
2027impl<S: Scheme, D: Digest> ConflictingNotarize<S, D> {
2028    /// Creates a new conflicting notarize evidence from two conflicting notarizes.
2029    ///
2030    /// # Panics
2031    ///
2032    /// Panics if the two notarizes do not have the same round and signer, or if they
2033    /// have identical proposals (which would not constitute conflicting evidence).
2034    pub fn new(notarize_1: Notarize<S, D>, notarize_2: Notarize<S, D>) -> Self {
2035        assert_eq!(notarize_1.round(), notarize_2.round());
2036        assert_eq!(notarize_1.signer(), notarize_2.signer());
2037        assert_ne!(
2038            notarize_1.proposal, notarize_2.proposal,
2039            "proposals must differ to constitute conflicting evidence"
2040        );
2041
2042        Self {
2043            notarize_1,
2044            notarize_2,
2045        }
2046    }
2047
2048    /// Verifies that both conflicting signatures are valid, proving Byzantine behavior.
2049    pub fn verify<R>(&self, rng: &mut R, scheme: &S, strategy: &impl Strategy) -> bool
2050    where
2051        R: CryptoRng,
2052        S: scheme::Scheme<D>,
2053    {
2054        self.notarize_1.verify(rng, scheme, strategy)
2055            && self.notarize_2.verify(rng, scheme, strategy)
2056    }
2057}
2058
2059impl<S: Scheme, D: Digest> Attributable for ConflictingNotarize<S, D> {
2060    fn signer(&self) -> Participant {
2061        self.notarize_1.signer()
2062    }
2063}
2064
2065impl<S: Scheme, D: Digest> Epochable for ConflictingNotarize<S, D> {
2066    fn epoch(&self) -> Epoch {
2067        self.notarize_1.epoch()
2068    }
2069}
2070
2071impl<S: Scheme, D: Digest> Viewable for ConflictingNotarize<S, D> {
2072    fn view(&self) -> View {
2073        self.notarize_1.view()
2074    }
2075}
2076
2077impl<S: Scheme, D: Digest> Write for ConflictingNotarize<S, D> {
2078    fn write(&self, writer: &mut impl BufMut) {
2079        self.notarize_1.write(writer);
2080        self.notarize_2.write(writer);
2081    }
2082}
2083
2084impl<S: Scheme, D: Digest> Read for ConflictingNotarize<S, D> {
2085    type Cfg = ();
2086
2087    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
2088        let notarize_1 = Notarize::read(reader)?;
2089        let notarize_2 = Notarize::read(reader)?;
2090
2091        if notarize_1.signer() != notarize_2.signer()
2092            || notarize_1.round() != notarize_2.round()
2093            || notarize_1.proposal == notarize_2.proposal
2094        {
2095            return Err(Error::Invalid(
2096                "consensus::simplex::ConflictingNotarize",
2097                "invalid conflicting notarize",
2098            ));
2099        }
2100
2101        Ok(Self {
2102            notarize_1,
2103            notarize_2,
2104        })
2105    }
2106}
2107
2108impl<S: Scheme, D: Digest> EncodeSize for ConflictingNotarize<S, D> {
2109    fn encode_size(&self) -> usize {
2110        self.notarize_1.encode_size() + self.notarize_2.encode_size()
2111    }
2112}
2113
2114#[cfg(feature = "arbitrary")]
2115impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for ConflictingNotarize<S, D>
2116where
2117    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
2118    D: for<'a> arbitrary::Arbitrary<'a>,
2119{
2120    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
2121        let notarize_1 = Notarize::arbitrary(u)?;
2122        let notarize_2 = Notarize::arbitrary(u)?;
2123        Ok(Self {
2124            notarize_1,
2125            notarize_2,
2126        })
2127    }
2128}
2129
2130/// ConflictingFinalize represents evidence of a Byzantine validator sending conflicting finalizes.
2131/// Similar to ConflictingNotarize, but for finalizes.
2132#[derive(Clone, Debug)]
2133pub struct ConflictingFinalize<S: Scheme, D: Digest> {
2134    /// The second conflicting finalize
2135    finalize_1: Finalize<S, D>,
2136    /// The second conflicting finalize
2137    finalize_2: Finalize<S, D>,
2138}
2139
2140impl<S: Scheme, D: Digest> PartialEq for ConflictingFinalize<S, D> {
2141    fn eq(&self, other: &Self) -> bool {
2142        self.finalize_1 == other.finalize_1 && self.finalize_2 == other.finalize_2
2143    }
2144}
2145
2146impl<S: Scheme, D: Digest> Eq for ConflictingFinalize<S, D> {}
2147
2148impl<S: Scheme, D: Digest> Hash for ConflictingFinalize<S, D> {
2149    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2150        self.finalize_1.hash(state);
2151        self.finalize_2.hash(state);
2152    }
2153}
2154
2155impl<S: Scheme, D: Digest> ConflictingFinalize<S, D> {
2156    /// Creates a new conflicting finalize evidence from two conflicting finalizes.
2157    ///
2158    /// # Panics
2159    ///
2160    /// Panics if the two finalizes do not have the same round and signer, or if they
2161    /// have identical proposals (which would not constitute conflicting evidence).
2162    pub fn new(finalize_1: Finalize<S, D>, finalize_2: Finalize<S, D>) -> Self {
2163        assert_eq!(finalize_1.round(), finalize_2.round());
2164        assert_eq!(finalize_1.signer(), finalize_2.signer());
2165        assert_ne!(
2166            finalize_1.proposal, finalize_2.proposal,
2167            "proposals must differ to constitute conflicting evidence"
2168        );
2169
2170        Self {
2171            finalize_1,
2172            finalize_2,
2173        }
2174    }
2175
2176    /// Verifies that both conflicting signatures are valid, proving Byzantine behavior.
2177    pub fn verify<R>(&self, rng: &mut R, scheme: &S, strategy: &impl Strategy) -> bool
2178    where
2179        R: CryptoRng,
2180        S: scheme::Scheme<D>,
2181    {
2182        self.finalize_1.verify(rng, scheme, strategy)
2183            && self.finalize_2.verify(rng, scheme, strategy)
2184    }
2185}
2186
2187impl<S: Scheme, D: Digest> Attributable for ConflictingFinalize<S, D> {
2188    fn signer(&self) -> Participant {
2189        self.finalize_1.signer()
2190    }
2191}
2192
2193impl<S: Scheme, D: Digest> Epochable for ConflictingFinalize<S, D> {
2194    fn epoch(&self) -> Epoch {
2195        self.finalize_1.epoch()
2196    }
2197}
2198
2199impl<S: Scheme, D: Digest> Viewable for ConflictingFinalize<S, D> {
2200    fn view(&self) -> View {
2201        self.finalize_1.view()
2202    }
2203}
2204
2205impl<S: Scheme, D: Digest> Write for ConflictingFinalize<S, D> {
2206    fn write(&self, writer: &mut impl BufMut) {
2207        self.finalize_1.write(writer);
2208        self.finalize_2.write(writer);
2209    }
2210}
2211
2212impl<S: Scheme, D: Digest> Read for ConflictingFinalize<S, D> {
2213    type Cfg = ();
2214
2215    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
2216        let finalize_1 = Finalize::read(reader)?;
2217        let finalize_2 = Finalize::read(reader)?;
2218
2219        if finalize_1.signer() != finalize_2.signer()
2220            || finalize_1.round() != finalize_2.round()
2221            || finalize_1.proposal == finalize_2.proposal
2222        {
2223            return Err(Error::Invalid(
2224                "consensus::simplex::ConflictingFinalize",
2225                "invalid conflicting finalize",
2226            ));
2227        }
2228
2229        Ok(Self {
2230            finalize_1,
2231            finalize_2,
2232        })
2233    }
2234}
2235
2236impl<S: Scheme, D: Digest> EncodeSize for ConflictingFinalize<S, D> {
2237    fn encode_size(&self) -> usize {
2238        self.finalize_1.encode_size() + self.finalize_2.encode_size()
2239    }
2240}
2241
2242#[cfg(feature = "arbitrary")]
2243impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for ConflictingFinalize<S, D>
2244where
2245    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
2246    D: for<'a> arbitrary::Arbitrary<'a>,
2247{
2248    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
2249        let finalize_1 = Finalize::arbitrary(u)?;
2250        let finalize_2 = Finalize::arbitrary(u)?;
2251        Ok(Self {
2252            finalize_1,
2253            finalize_2,
2254        })
2255    }
2256}
2257
2258/// NullifyFinalize represents evidence of a Byzantine validator sending both a nullify and finalize
2259/// for the same view, which is contradictory behavior (a validator should either try to skip a view OR
2260/// finalize a proposal, not both).
2261#[derive(Clone, Debug)]
2262pub struct NullifyFinalize<S: Scheme, D: Digest> {
2263    /// The conflicting nullify
2264    nullify: Nullify<S>,
2265    /// The conflicting finalize
2266    finalize: Finalize<S, D>,
2267}
2268
2269impl<S: Scheme, D: Digest> PartialEq for NullifyFinalize<S, D> {
2270    fn eq(&self, other: &Self) -> bool {
2271        self.nullify == other.nullify && self.finalize == other.finalize
2272    }
2273}
2274
2275impl<S: Scheme, D: Digest> Eq for NullifyFinalize<S, D> {}
2276
2277impl<S: Scheme, D: Digest> Hash for NullifyFinalize<S, D> {
2278    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2279        self.nullify.hash(state);
2280        self.finalize.hash(state);
2281    }
2282}
2283
2284impl<S: Scheme, D: Digest> NullifyFinalize<S, D> {
2285    /// Creates a new nullify-finalize evidence from a nullify and a finalize.
2286    pub fn new(nullify: Nullify<S>, finalize: Finalize<S, D>) -> Self {
2287        assert_eq!(nullify.round, finalize.round());
2288        assert_eq!(nullify.signer(), finalize.signer());
2289
2290        Self { nullify, finalize }
2291    }
2292
2293    /// Verifies that both the nullify and finalize signatures are valid, proving Byzantine behavior.
2294    pub fn verify<R>(&self, rng: &mut R, scheme: &S, strategy: &impl Strategy) -> bool
2295    where
2296        R: CryptoRng,
2297        S: scheme::Scheme<D>,
2298    {
2299        self.nullify.verify(rng, scheme, strategy) && self.finalize.verify(rng, scheme, strategy)
2300    }
2301}
2302
2303impl<S: Scheme, D: Digest> Attributable for NullifyFinalize<S, D> {
2304    fn signer(&self) -> Participant {
2305        self.nullify.signer()
2306    }
2307}
2308
2309impl<S: Scheme, D: Digest> Epochable for NullifyFinalize<S, D> {
2310    fn epoch(&self) -> Epoch {
2311        self.nullify.epoch()
2312    }
2313}
2314
2315impl<S: Scheme, D: Digest> Viewable for NullifyFinalize<S, D> {
2316    fn view(&self) -> View {
2317        self.nullify.view()
2318    }
2319}
2320
2321impl<S: Scheme, D: Digest> Write for NullifyFinalize<S, D> {
2322    fn write(&self, writer: &mut impl BufMut) {
2323        self.nullify.write(writer);
2324        self.finalize.write(writer);
2325    }
2326}
2327
2328impl<S: Scheme, D: Digest> Read for NullifyFinalize<S, D> {
2329    type Cfg = ();
2330
2331    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
2332        let nullify = Nullify::read(reader)?;
2333        let finalize = Finalize::read(reader)?;
2334
2335        if nullify.signer() != finalize.signer() || nullify.round != finalize.round() {
2336            return Err(Error::Invalid(
2337                "consensus::simplex::NullifyFinalize",
2338                "mismatched signatures",
2339            ));
2340        }
2341
2342        Ok(Self { nullify, finalize })
2343    }
2344}
2345
2346impl<S: Scheme, D: Digest> EncodeSize for NullifyFinalize<S, D> {
2347    fn encode_size(&self) -> usize {
2348        self.nullify.encode_size() + self.finalize.encode_size()
2349    }
2350}
2351
2352#[cfg(feature = "arbitrary")]
2353impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for NullifyFinalize<S, D>
2354where
2355    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
2356    D: for<'a> arbitrary::Arbitrary<'a>,
2357{
2358    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
2359        let nullify = Nullify::arbitrary(u)?;
2360        let finalize = Finalize::arbitrary(u)?;
2361        Ok(Self { nullify, finalize })
2362    }
2363}
2364
2365#[cfg(test)]
2366mod tests {
2367    use super::*;
2368    use crate::simplex::{
2369        quorum,
2370        scheme::{
2371            Scheme, bls12381_multisig,
2372            bls12381_threshold::{
2373                standard as bls12381_threshold_std, vrf as bls12381_threshold_vrf,
2374            },
2375            ed25519, secp256r1,
2376        },
2377    };
2378    use commonware_codec::{Decode, DecodeExt, Encode};
2379    use commonware_cryptography::{
2380        bls12381::primitives::variant::{MinPk, MinSig},
2381        certificate::mocks::Fixture,
2382        sha256::Digest as Sha256,
2383    };
2384    use commonware_parallel::Sequential;
2385    use commonware_utils::{Faults, N3f1, TestRng, test_rng};
2386
2387    const NAMESPACE: &[u8] = b"test";
2388
2389    // Helper function to create a sample digest
2390    fn sample_digest(v: u8) -> Sha256 {
2391        Sha256::from([v; 32]) // Simple fixed digest for testing
2392    }
2393
2394    /// Generate a fixture using the provided generator function with a specific seed.
2395    fn setup_seeded<S, F>(n: u32, seed: u64, fixture: F) -> Fixture<S>
2396    where
2397        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
2398    {
2399        setup_seeded_ns(n, seed, NAMESPACE, fixture)
2400    }
2401
2402    /// Generate a fixture using the provided generator function with a specific seed and namespace.
2403    fn setup_seeded_ns<S, F>(n: u32, seed: u64, namespace: &[u8], fixture: F) -> Fixture<S>
2404    where
2405        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
2406    {
2407        let mut rng = TestRng::new(seed);
2408        fixture(&mut rng, namespace, n)
2409    }
2410
2411    #[test]
2412    fn test_proposal_encode_decode() {
2413        let proposal = Proposal::new(
2414            Round::new(Epoch::new(0), View::new(10)),
2415            View::new(5),
2416            sample_digest(1),
2417        );
2418        let encoded = proposal.encode();
2419        let decoded = Proposal::<Sha256>::decode(encoded).unwrap();
2420        assert_eq!(proposal, decoded);
2421    }
2422
2423    fn notarize_encode_decode<S, F>(fixture: F)
2424    where
2425        S: Scheme<Sha256>,
2426        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
2427    {
2428        let mut rng = test_rng();
2429        let fixture = fixture(&mut rng, NAMESPACE, 5);
2430        let round = Round::new(Epoch::new(0), View::new(10));
2431        let proposal = Proposal::new(round, View::new(5), sample_digest(1));
2432        let notarize = Notarize::sign(&fixture.schemes[0], proposal).unwrap();
2433
2434        let encoded = notarize.encode();
2435        let decoded = Notarize::decode(encoded).unwrap();
2436
2437        assert_eq!(notarize, decoded);
2438        assert!(decoded.verify(&mut rng, &fixture.schemes[0], &Sequential));
2439    }
2440
2441    #[test]
2442    fn test_notarize_encode_decode() {
2443        notarize_encode_decode(ed25519::fixture);
2444        notarize_encode_decode(secp256r1::fixture);
2445        notarize_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
2446        notarize_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
2447        notarize_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
2448        notarize_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
2449        notarize_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
2450        notarize_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
2451    }
2452
2453    fn notarization_encode_decode<S, F>(fixture: F)
2454    where
2455        S: Scheme<Sha256>,
2456        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
2457    {
2458        let mut rng = test_rng();
2459        let fixture = fixture(&mut rng, NAMESPACE, 5);
2460        let proposal = Proposal::new(
2461            Round::new(Epoch::new(0), View::new(10)),
2462            View::new(5),
2463            sample_digest(1),
2464        );
2465        let notarizes: Vec<_> = fixture
2466            .schemes
2467            .iter()
2468            .map(|scheme| Notarize::sign(scheme, proposal.clone()).unwrap())
2469            .collect();
2470        let notarization =
2471            Notarization::from_notarizes(&fixture.schemes[0], non_empty![@&notarizes], &Sequential)
2472                .expect("quorum notarization");
2473        let encoded = notarization.encode();
2474        let cfg = fixture.schemes[0].certificate_codec_config();
2475        let decoded = Notarization::decode_cfg(encoded, &cfg).unwrap();
2476        assert_eq!(notarization, decoded);
2477        assert!(decoded.verify(&mut rng, &fixture.schemes[0], &Sequential));
2478    }
2479
2480    #[test]
2481    fn test_notarization_encode_decode() {
2482        notarization_encode_decode(ed25519::fixture);
2483        notarization_encode_decode(secp256r1::fixture);
2484        notarization_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
2485        notarization_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
2486        notarization_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
2487        notarization_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
2488        notarization_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
2489        notarization_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
2490    }
2491
2492    fn nullify_encode_decode<S, F>(fixture: F)
2493    where
2494        S: Scheme<Sha256>,
2495        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
2496    {
2497        let mut rng = test_rng();
2498        let fixture = fixture(&mut rng, NAMESPACE, 5);
2499        let round = Round::new(Epoch::new(0), View::new(10));
2500        let nullify = Nullify::sign::<Sha256>(&fixture.schemes[0], round).unwrap();
2501        let encoded = nullify.encode();
2502        let decoded = Nullify::decode(encoded).unwrap();
2503        assert_eq!(nullify, decoded);
2504        assert!(decoded.verify::<_, Sha256>(&mut rng, &fixture.schemes[0], &Sequential));
2505    }
2506
2507    #[test]
2508    fn test_nullify_encode_decode() {
2509        nullify_encode_decode(ed25519::fixture);
2510        nullify_encode_decode(secp256r1::fixture);
2511        nullify_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
2512        nullify_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
2513        nullify_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
2514        nullify_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
2515        nullify_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
2516        nullify_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
2517    }
2518
2519    fn nullification_encode_decode<S, F>(fixture: F)
2520    where
2521        S: Scheme<Sha256>,
2522        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
2523    {
2524        let mut rng = test_rng();
2525        let fixture = fixture(&mut rng, NAMESPACE, 5);
2526        let round = Round::new(Epoch::new(333), View::new(10));
2527        let nullifies: Vec<_> = fixture
2528            .schemes
2529            .iter()
2530            .map(|scheme| Nullify::sign::<Sha256>(scheme, round).unwrap())
2531            .collect();
2532        let nullification = Nullification::from_nullifies(
2533            &fixture.schemes[0],
2534            non_empty![@&nullifies],
2535            &Sequential,
2536        )
2537        .unwrap();
2538        let encoded = nullification.encode();
2539        let cfg = fixture.schemes[0].certificate_codec_config();
2540        let decoded = Nullification::decode_cfg(encoded, &cfg).unwrap();
2541        assert_eq!(nullification, decoded);
2542        assert!(decoded.verify::<_, Sha256>(&mut rng, &fixture.schemes[0], &Sequential));
2543    }
2544
2545    #[test]
2546    fn test_nullification_encode_decode() {
2547        nullification_encode_decode(ed25519::fixture);
2548        nullification_encode_decode(secp256r1::fixture);
2549        nullification_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
2550        nullification_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
2551        nullification_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
2552        nullification_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
2553        nullification_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
2554        nullification_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
2555    }
2556
2557    fn finalize_encode_decode<S, F>(fixture: F)
2558    where
2559        S: Scheme<Sha256>,
2560        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
2561    {
2562        let mut rng = test_rng();
2563        let fixture = fixture(&mut rng, NAMESPACE, 5);
2564        let round = Round::new(Epoch::new(0), View::new(10));
2565        let proposal = Proposal::new(round, View::new(5), sample_digest(1));
2566        let finalize = Finalize::sign(&fixture.schemes[0], proposal).unwrap();
2567        let encoded = finalize.encode();
2568        let decoded = Finalize::decode(encoded).unwrap();
2569        assert_eq!(finalize, decoded);
2570        assert!(decoded.verify(&mut rng, &fixture.schemes[0], &Sequential));
2571    }
2572
2573    #[test]
2574    fn test_finalize_encode_decode() {
2575        finalize_encode_decode(ed25519::fixture);
2576        finalize_encode_decode(secp256r1::fixture);
2577        finalize_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
2578        finalize_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
2579        finalize_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
2580        finalize_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
2581        finalize_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
2582        finalize_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
2583    }
2584
2585    fn finalization_encode_decode<S, F>(fixture: F)
2586    where
2587        S: Scheme<Sha256>,
2588        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
2589    {
2590        let mut rng = test_rng();
2591        let fixture = fixture(&mut rng, NAMESPACE, 5);
2592        let round = Round::new(Epoch::new(0), View::new(10));
2593        let proposal = Proposal::new(round, View::new(5), sample_digest(1));
2594        let finalizes: Vec<_> = fixture
2595            .schemes
2596            .iter()
2597            .map(|scheme| Finalize::sign(scheme, proposal.clone()).unwrap())
2598            .collect();
2599        let finalization =
2600            Finalization::from_finalizes(&fixture.schemes[0], non_empty![@&finalizes], &Sequential)
2601                .unwrap();
2602        let encoded = finalization.encode();
2603        let cfg = fixture.schemes[0].certificate_codec_config();
2604        let decoded = Finalization::decode_cfg(encoded, &cfg).unwrap();
2605        assert_eq!(finalization, decoded);
2606        assert!(decoded.verify(&mut rng, &fixture.schemes[0], &Sequential));
2607    }
2608
2609    #[test]
2610    fn test_finalization_encode_decode() {
2611        finalization_encode_decode(ed25519::fixture);
2612        finalization_encode_decode(secp256r1::fixture);
2613        finalization_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
2614        finalization_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
2615        finalization_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
2616        finalization_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
2617        finalization_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
2618        finalization_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
2619    }
2620
2621    fn conflicting_notarize_encode_decode<S, F>(fixture: F)
2622    where
2623        S: Scheme<Sha256>,
2624        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
2625    {
2626        let mut rng = test_rng();
2627        let fixture = fixture(&mut rng, NAMESPACE, 5);
2628        let proposal1 = Proposal::new(
2629            Round::new(Epoch::new(0), View::new(10)),
2630            View::new(5),
2631            sample_digest(1),
2632        );
2633        let proposal2 = Proposal::new(
2634            Round::new(Epoch::new(0), View::new(10)),
2635            View::new(5),
2636            sample_digest(2),
2637        );
2638        let notarize1 = Notarize::sign(&fixture.schemes[0], proposal1).unwrap();
2639        let notarize2 = Notarize::sign(&fixture.schemes[0], proposal2).unwrap();
2640        let conflicting = ConflictingNotarize::new(notarize1, notarize2);
2641
2642        let encoded = conflicting.encode();
2643        let decoded = ConflictingNotarize::<S, Sha256>::decode(encoded).unwrap();
2644
2645        assert_eq!(conflicting, decoded);
2646        assert!(decoded.verify(&mut rng, &fixture.schemes[0], &Sequential));
2647    }
2648
2649    #[test]
2650    fn test_conflicting_notarize_encode_decode() {
2651        conflicting_notarize_encode_decode(ed25519::fixture);
2652        conflicting_notarize_encode_decode(secp256r1::fixture);
2653        conflicting_notarize_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
2654        conflicting_notarize_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
2655        conflicting_notarize_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
2656        conflicting_notarize_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
2657        conflicting_notarize_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
2658        conflicting_notarize_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
2659    }
2660
2661    fn conflicting_finalize_encode_decode<S, F>(fixture: F)
2662    where
2663        S: Scheme<Sha256>,
2664        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
2665    {
2666        let mut rng = test_rng();
2667        let fixture = fixture(&mut rng, NAMESPACE, 5);
2668        let proposal1 = Proposal::new(
2669            Round::new(Epoch::new(0), View::new(10)),
2670            View::new(5),
2671            sample_digest(1),
2672        );
2673        let proposal2 = Proposal::new(
2674            Round::new(Epoch::new(0), View::new(10)),
2675            View::new(5),
2676            sample_digest(2),
2677        );
2678        let finalize1 = Finalize::sign(&fixture.schemes[0], proposal1).unwrap();
2679        let finalize2 = Finalize::sign(&fixture.schemes[0], proposal2).unwrap();
2680        let conflicting = ConflictingFinalize::new(finalize1, finalize2);
2681
2682        let encoded = conflicting.encode();
2683        let decoded = ConflictingFinalize::<S, Sha256>::decode(encoded).unwrap();
2684
2685        assert_eq!(conflicting, decoded);
2686        assert!(decoded.verify(&mut rng, &fixture.schemes[0], &Sequential));
2687    }
2688
2689    #[test]
2690    fn test_conflicting_finalize_encode_decode() {
2691        conflicting_finalize_encode_decode(ed25519::fixture);
2692        conflicting_finalize_encode_decode(secp256r1::fixture);
2693        conflicting_finalize_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
2694        conflicting_finalize_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
2695        conflicting_finalize_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
2696        conflicting_finalize_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
2697        conflicting_finalize_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
2698        conflicting_finalize_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
2699    }
2700
2701    fn nullify_finalize_encode_decode<S, F>(fixture: F)
2702    where
2703        S: Scheme<Sha256>,
2704        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
2705    {
2706        let mut rng = test_rng();
2707        let fixture = fixture(&mut rng, NAMESPACE, 5);
2708        let round = Round::new(Epoch::new(0), View::new(10));
2709        let proposal = Proposal::new(round, View::new(5), sample_digest(1));
2710        let nullify = Nullify::sign::<Sha256>(&fixture.schemes[0], round).unwrap();
2711        let finalize = Finalize::sign(&fixture.schemes[0], proposal).unwrap();
2712        let conflict = NullifyFinalize::new(nullify, finalize);
2713
2714        let encoded = conflict.encode();
2715        let decoded = NullifyFinalize::<S, Sha256>::decode(encoded).unwrap();
2716
2717        assert_eq!(conflict, decoded);
2718        assert!(decoded.verify(&mut rng, &fixture.schemes[0], &Sequential));
2719    }
2720
2721    #[test]
2722    fn test_nullify_finalize_encode_decode() {
2723        nullify_finalize_encode_decode(ed25519::fixture);
2724        nullify_finalize_encode_decode(secp256r1::fixture);
2725        nullify_finalize_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
2726        nullify_finalize_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
2727        nullify_finalize_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
2728        nullify_finalize_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
2729        nullify_finalize_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
2730        nullify_finalize_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
2731    }
2732
2733    fn notarize_verify_wrong_namespace<S, F>(f: F)
2734    where
2735        S: Scheme<Sha256>,
2736        F: Fn(&mut TestRng, &[u8], u32) -> Fixture<S>,
2737    {
2738        // Create two fixtures with different namespaces
2739        let mut rng = test_rng();
2740        let fixture = setup_seeded_ns(5, 0, NAMESPACE, &f);
2741        let wrong_fixture = setup_seeded_ns(5, 0, b"wrong_namespace", &f);
2742        let round = Round::new(Epoch::new(0), View::new(10));
2743        let proposal = Proposal::new(round, View::new(5), sample_digest(1));
2744        let notarize = Notarize::sign(&fixture.schemes[0], proposal).unwrap();
2745
2746        assert!(notarize.verify(&mut rng, &fixture.schemes[0], &Sequential));
2747        assert!(!notarize.verify(&mut rng, &wrong_fixture.schemes[0], &Sequential));
2748    }
2749
2750    #[test]
2751    fn test_notarize_verify_wrong_namespace() {
2752        notarize_verify_wrong_namespace(ed25519::fixture);
2753        notarize_verify_wrong_namespace(secp256r1::fixture);
2754        notarize_verify_wrong_namespace(bls12381_multisig::fixture::<MinPk, _>);
2755        notarize_verify_wrong_namespace(bls12381_multisig::fixture::<MinSig, _>);
2756        notarize_verify_wrong_namespace(bls12381_threshold_vrf::fixture::<MinPk, _>);
2757        notarize_verify_wrong_namespace(bls12381_threshold_vrf::fixture::<MinSig, _>);
2758        notarize_verify_wrong_namespace(bls12381_threshold_std::fixture::<MinPk, _>);
2759        notarize_verify_wrong_namespace(bls12381_threshold_std::fixture::<MinSig, _>);
2760    }
2761
2762    fn notarize_verify_wrong_scheme<S, F>(f: F)
2763    where
2764        S: Scheme<Sha256>,
2765        F: Fn(&mut TestRng, &[u8], u32) -> Fixture<S>,
2766    {
2767        let mut rng = test_rng();
2768        let fixture = setup_seeded(5, 0, &f);
2769        let wrong_fixture = setup_seeded(5, 1, &f);
2770        let round = Round::new(Epoch::new(0), View::new(10));
2771        let proposal = Proposal::new(round, View::new(5), sample_digest(2));
2772        let notarize = Notarize::sign(&fixture.schemes[0], proposal).unwrap();
2773
2774        assert!(notarize.verify(&mut rng, &fixture.schemes[0], &Sequential));
2775        assert!(!notarize.verify(&mut rng, &wrong_fixture.verifier, &Sequential));
2776    }
2777
2778    #[test]
2779    fn test_notarize_verify_wrong_scheme() {
2780        notarize_verify_wrong_scheme(ed25519::fixture);
2781        notarize_verify_wrong_scheme(secp256r1::fixture);
2782        notarize_verify_wrong_scheme(bls12381_multisig::fixture::<MinPk, _>);
2783        notarize_verify_wrong_scheme(bls12381_multisig::fixture::<MinSig, _>);
2784        notarize_verify_wrong_scheme(bls12381_threshold_vrf::fixture::<MinPk, _>);
2785        notarize_verify_wrong_scheme(bls12381_threshold_vrf::fixture::<MinSig, _>);
2786        notarize_verify_wrong_scheme(bls12381_threshold_std::fixture::<MinPk, _>);
2787        notarize_verify_wrong_scheme(bls12381_threshold_std::fixture::<MinSig, _>);
2788    }
2789
2790    fn notarization_verify_wrong_scheme<S, F>(f: F)
2791    where
2792        S: Scheme<Sha256>,
2793        F: Fn(&mut TestRng, &[u8], u32) -> Fixture<S>,
2794    {
2795        let mut rng = test_rng();
2796        let fixture = setup_seeded(5, 0, &f);
2797        let wrong_fixture = setup_seeded(5, 1, &f);
2798        let round = Round::new(Epoch::new(0), View::new(10));
2799        let proposal = Proposal::new(round, View::new(5), sample_digest(3));
2800        let quorum = N3f1::quorum(fixture.schemes.len()) as usize;
2801        let notarizes: Vec<_> = fixture
2802            .schemes
2803            .iter()
2804            .take(quorum)
2805            .map(|scheme| Notarize::sign(scheme, proposal.clone()).unwrap())
2806            .collect();
2807
2808        let notarization =
2809            Notarization::from_notarizes(&fixture.schemes[0], non_empty![@&notarizes], &Sequential)
2810                .unwrap();
2811        assert!(notarization.verify(&mut rng, &fixture.schemes[0], &Sequential));
2812        assert!(!notarization.verify(&mut rng, &wrong_fixture.verifier, &Sequential));
2813    }
2814
2815    #[test]
2816    fn test_notarization_verify_wrong_scheme() {
2817        notarization_verify_wrong_scheme(ed25519::fixture);
2818        notarization_verify_wrong_scheme(secp256r1::fixture);
2819        notarization_verify_wrong_scheme(bls12381_multisig::fixture::<MinPk, _>);
2820        notarization_verify_wrong_scheme(bls12381_multisig::fixture::<MinSig, _>);
2821        notarization_verify_wrong_scheme(bls12381_threshold_vrf::fixture::<MinPk, _>);
2822        notarization_verify_wrong_scheme(bls12381_threshold_vrf::fixture::<MinSig, _>);
2823        notarization_verify_wrong_scheme(bls12381_threshold_std::fixture::<MinPk, _>);
2824        notarization_verify_wrong_scheme(bls12381_threshold_std::fixture::<MinSig, _>);
2825    }
2826
2827    fn notarization_verify_wrong_namespace<S, F>(f: F)
2828    where
2829        S: Scheme<Sha256>,
2830        F: Fn(&mut TestRng, &[u8], u32) -> Fixture<S>,
2831    {
2832        // Create two fixtures with different namespaces
2833        let fixture = setup_seeded_ns(5, 0, NAMESPACE, &f);
2834        let wrong_fixture = setup_seeded_ns(5, 0, b"wrong_namespace", &f);
2835        let mut rng = test_rng();
2836        let round = Round::new(Epoch::new(0), View::new(10));
2837        let proposal = Proposal::new(round, View::new(5), sample_digest(4));
2838        let quorum = N3f1::quorum(fixture.schemes.len()) as usize;
2839        let notarizes: Vec<_> = fixture
2840            .schemes
2841            .iter()
2842            .take(quorum)
2843            .map(|scheme| Notarize::sign(scheme, proposal.clone()).unwrap())
2844            .collect();
2845
2846        let notarization =
2847            Notarization::from_notarizes(&fixture.schemes[0], non_empty![@&notarizes], &Sequential)
2848                .unwrap();
2849        assert!(notarization.verify(&mut rng, &fixture.schemes[0], &Sequential));
2850
2851        assert!(!notarization.verify(&mut rng, &wrong_fixture.schemes[0], &Sequential));
2852    }
2853
2854    #[test]
2855    fn test_notarization_verify_wrong_namespace() {
2856        notarization_verify_wrong_namespace(ed25519::fixture);
2857        notarization_verify_wrong_namespace(secp256r1::fixture);
2858        notarization_verify_wrong_namespace(bls12381_multisig::fixture::<MinPk, _>);
2859        notarization_verify_wrong_namespace(bls12381_multisig::fixture::<MinSig, _>);
2860        notarization_verify_wrong_namespace(bls12381_threshold_vrf::fixture::<MinPk, _>);
2861        notarization_verify_wrong_namespace(bls12381_threshold_vrf::fixture::<MinSig, _>);
2862        notarization_verify_wrong_namespace(bls12381_threshold_std::fixture::<MinPk, _>);
2863        notarization_verify_wrong_namespace(bls12381_threshold_std::fixture::<MinSig, _>);
2864    }
2865
2866    fn notarization_recover_insufficient_signatures<S, F>(fixture: F)
2867    where
2868        S: Scheme<Sha256>,
2869        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
2870    {
2871        let mut rng = test_rng();
2872        let fixture = fixture(&mut rng, NAMESPACE, 5);
2873        let participant_count =
2874            u32::try_from(fixture.schemes.len()).expect("participant count exceeds u32::MAX");
2875        let quorum_size = quorum(participant_count);
2876        let subquorum = usize::try_from(quorum_size - 1).expect("quorum exceeds usize::MAX");
2877        assert!(quorum_size > 1, "test requires quorum larger than one");
2878        let round = Round::new(Epoch::new(0), View::new(10));
2879        let proposal = Proposal::new(round, View::new(5), sample_digest(5));
2880        let notarizes: Vec<_> = fixture
2881            .schemes
2882            .iter()
2883            .take(subquorum)
2884            .map(|scheme| Notarize::sign(scheme, proposal.clone()).unwrap())
2885            .collect();
2886
2887        assert_eq!(
2888            Notarization::from_notarizes(&fixture.schemes[0], non_empty![@&notarizes], &Sequential),
2889            Err(AssemblyError::InsufficientAttestations(
2890                quorum_size,
2891                quorum_size - 1
2892            )),
2893            "insufficient votes should not form a notarization"
2894        );
2895    }
2896
2897    #[test]
2898    fn test_notarization_recover_insufficient_signatures() {
2899        notarization_recover_insufficient_signatures(ed25519::fixture);
2900        notarization_recover_insufficient_signatures(secp256r1::fixture);
2901        notarization_recover_insufficient_signatures(bls12381_multisig::fixture::<MinPk, _>);
2902        notarization_recover_insufficient_signatures(bls12381_multisig::fixture::<MinSig, _>);
2903        notarization_recover_insufficient_signatures(bls12381_threshold_vrf::fixture::<MinPk, _>);
2904        notarization_recover_insufficient_signatures(bls12381_threshold_vrf::fixture::<MinSig, _>);
2905        notarization_recover_insufficient_signatures(bls12381_threshold_std::fixture::<MinPk, _>);
2906        notarization_recover_insufficient_signatures(bls12381_threshold_std::fixture::<MinSig, _>);
2907    }
2908
2909    fn conflicting_notarize_detection<S, F>(f: F)
2910    where
2911        S: Scheme<Sha256>,
2912        F: Fn(&mut TestRng, &[u8], u32) -> Fixture<S>,
2913    {
2914        let mut rng = test_rng();
2915        let fixture = setup_seeded(5, 0, &f);
2916        let wrong_ns_fixture = setup_seeded_ns(5, 0, b"wrong_namespace", &f);
2917        let wrong_scheme_fixture = setup_seeded(5, 1, &f);
2918
2919        let round = Round::new(Epoch::new(0), View::new(10));
2920        let proposal1 = Proposal::new(round, View::new(5), sample_digest(6));
2921        let proposal2 = Proposal::new(round, View::new(5), sample_digest(7));
2922
2923        let notarize1 = Notarize::sign(&fixture.schemes[0], proposal1).unwrap();
2924        let notarize2 = Notarize::sign(&fixture.schemes[0], proposal2).unwrap();
2925        let conflict = ConflictingNotarize::new(notarize1, notarize2);
2926
2927        assert!(conflict.verify(&mut rng, &fixture.schemes[0], &Sequential));
2928        assert!(!conflict.verify(&mut rng, &wrong_ns_fixture.schemes[0], &Sequential));
2929        assert!(!conflict.verify(&mut rng, &wrong_scheme_fixture.verifier, &Sequential));
2930    }
2931
2932    #[test]
2933    fn test_conflicting_notarize_detection() {
2934        conflicting_notarize_detection(ed25519::fixture);
2935        conflicting_notarize_detection(secp256r1::fixture);
2936        conflicting_notarize_detection(bls12381_multisig::fixture::<MinPk, _>);
2937        conflicting_notarize_detection(bls12381_multisig::fixture::<MinSig, _>);
2938        conflicting_notarize_detection(bls12381_threshold_vrf::fixture::<MinPk, _>);
2939        conflicting_notarize_detection(bls12381_threshold_vrf::fixture::<MinSig, _>);
2940        conflicting_notarize_detection(bls12381_threshold_std::fixture::<MinPk, _>);
2941        conflicting_notarize_detection(bls12381_threshold_std::fixture::<MinSig, _>);
2942    }
2943
2944    fn nullify_finalize_detection<S, F>(f: F)
2945    where
2946        S: Scheme<Sha256>,
2947        F: Fn(&mut TestRng, &[u8], u32) -> Fixture<S>,
2948    {
2949        let mut rng = test_rng();
2950        let fixture = setup_seeded(5, 0, &f);
2951        let wrong_ns_fixture = setup_seeded_ns(5, 0, b"wrong_namespace", &f);
2952        let wrong_scheme_fixture = setup_seeded(5, 1, &f);
2953
2954        let round = Round::new(Epoch::new(0), View::new(10));
2955        let proposal = Proposal::new(round, View::new(5), sample_digest(8));
2956
2957        let nullify = Nullify::sign::<Sha256>(&fixture.schemes[0], round).unwrap();
2958        let finalize = Finalize::sign(&fixture.schemes[0], proposal).unwrap();
2959        let conflict = NullifyFinalize::new(nullify, finalize);
2960
2961        assert!(conflict.verify(&mut rng, &fixture.schemes[0], &Sequential));
2962        assert!(!conflict.verify(&mut rng, &wrong_ns_fixture.schemes[0], &Sequential));
2963        assert!(!conflict.verify(&mut rng, &wrong_scheme_fixture.verifier, &Sequential));
2964    }
2965
2966    #[test]
2967    fn test_nullify_finalize_detection() {
2968        nullify_finalize_detection(ed25519::fixture);
2969        nullify_finalize_detection(secp256r1::fixture);
2970        nullify_finalize_detection(bls12381_multisig::fixture::<MinPk, _>);
2971        nullify_finalize_detection(bls12381_multisig::fixture::<MinSig, _>);
2972        nullify_finalize_detection(bls12381_threshold_vrf::fixture::<MinPk, _>);
2973        nullify_finalize_detection(bls12381_threshold_vrf::fixture::<MinSig, _>);
2974        nullify_finalize_detection(bls12381_threshold_std::fixture::<MinPk, _>);
2975        nullify_finalize_detection(bls12381_threshold_std::fixture::<MinSig, _>);
2976    }
2977
2978    fn finalization_verify_wrong_scheme<S, F>(f: F)
2979    where
2980        S: Scheme<Sha256>,
2981        F: Fn(&mut TestRng, &[u8], u32) -> Fixture<S>,
2982    {
2983        let mut rng = test_rng();
2984        let fixture = setup_seeded(5, 0, &f);
2985        let wrong_fixture = setup_seeded(5, 1, &f);
2986        let round = Round::new(Epoch::new(0), View::new(10));
2987        let proposal = Proposal::new(round, View::new(5), sample_digest(9));
2988        let quorum = N3f1::quorum(fixture.schemes.len()) as usize;
2989        let finalizes: Vec<_> = fixture
2990            .schemes
2991            .iter()
2992            .take(quorum)
2993            .map(|scheme| Finalize::sign(scheme, proposal.clone()).unwrap())
2994            .collect();
2995
2996        let finalization =
2997            Finalization::from_finalizes(&fixture.schemes[0], non_empty![@&finalizes], &Sequential)
2998                .expect("quorum finalization");
2999        assert!(finalization.verify(&mut rng, &fixture.schemes[0], &Sequential));
3000        assert!(!finalization.verify(&mut rng, &wrong_fixture.verifier, &Sequential));
3001    }
3002
3003    #[test]
3004    fn test_finalization_wrong_scheme() {
3005        finalization_verify_wrong_scheme(ed25519::fixture);
3006        finalization_verify_wrong_scheme(secp256r1::fixture);
3007        finalization_verify_wrong_scheme(bls12381_multisig::fixture::<MinPk, _>);
3008        finalization_verify_wrong_scheme(bls12381_multisig::fixture::<MinSig, _>);
3009        finalization_verify_wrong_scheme(bls12381_threshold_vrf::fixture::<MinPk, _>);
3010        finalization_verify_wrong_scheme(bls12381_threshold_vrf::fixture::<MinSig, _>);
3011        finalization_verify_wrong_scheme(bls12381_threshold_std::fixture::<MinPk, _>);
3012        finalization_verify_wrong_scheme(bls12381_threshold_std::fixture::<MinSig, _>);
3013    }
3014
3015    struct MockAttributable(Participant);
3016
3017    impl Attributable for MockAttributable {
3018        fn signer(&self) -> Participant {
3019            self.0
3020        }
3021    }
3022
3023    #[test]
3024    fn test_attributable_map() {
3025        let mut map = AttributableMap::new(5);
3026        assert_eq!(map.len(), 0);
3027        assert!(map.is_empty());
3028        assert_eq!(map.data.capacity(), 0, "empty maps should allocate lazily");
3029
3030        // Test get on empty map
3031        for i in 0..5 {
3032            assert!(map.get(Participant::new(i)).is_none());
3033        }
3034
3035        assert!(map.insert(MockAttributable(Participant::new(3))));
3036        assert!(map.data.capacity() >= 5);
3037        assert_eq!(map.len(), 1);
3038        assert!(!map.is_empty());
3039        let mut iter = map.iter();
3040        assert!(matches!(iter.next(), Some(a) if a.signer() == Participant::new(3)));
3041        assert!(iter.next().is_none());
3042        drop(iter);
3043
3044        // Test get on existing item
3045        assert!(
3046            matches!(map.get(Participant::new(3)), Some(a) if a.signer() == Participant::new(3))
3047        );
3048
3049        assert!(map.insert(MockAttributable(Participant::new(1))));
3050        assert_eq!(map.len(), 2);
3051        assert!(!map.is_empty());
3052        let mut iter = map.iter();
3053        assert!(matches!(iter.next(), Some(a) if a.signer() == Participant::new(1)));
3054        assert!(matches!(iter.next(), Some(a) if a.signer() == Participant::new(3)));
3055        assert!(iter.next().is_none());
3056        drop(iter);
3057
3058        // Test get on both items
3059        assert!(
3060            matches!(map.get(Participant::new(1)), Some(a) if a.signer() == Participant::new(1))
3061        );
3062        assert!(
3063            matches!(map.get(Participant::new(3)), Some(a) if a.signer() == Participant::new(3))
3064        );
3065
3066        // Test get on non-existing items
3067        assert!(map.get(Participant::new(0)).is_none());
3068        assert!(map.get(Participant::new(2)).is_none());
3069        assert!(map.get(Participant::new(4)).is_none());
3070
3071        assert!(!map.insert(MockAttributable(Participant::new(3))));
3072        assert_eq!(map.len(), 2);
3073        assert!(!map.is_empty());
3074        let mut iter = map.iter();
3075        assert!(matches!(iter.next(), Some(a) if a.signer() == Participant::new(1)));
3076        assert!(matches!(iter.next(), Some(a) if a.signer() == Participant::new(3)));
3077        assert!(iter.next().is_none());
3078        drop(iter);
3079
3080        // Test out-of-bounds signer indices
3081        assert!(!map.insert(MockAttributable(Participant::new(5))));
3082        assert!(!map.insert(MockAttributable(Participant::new(100))));
3083        assert_eq!(map.len(), 2);
3084
3085        // Test clear
3086        map.clear();
3087        assert_eq!(map.len(), 0);
3088        assert!(map.is_empty());
3089        assert!(map.iter().next().is_none());
3090        assert_eq!(map.data.capacity(), 0, "clear should release vote storage");
3091
3092        // Verify can insert after clear
3093        assert!(map.insert(MockAttributable(Participant::new(2))));
3094        assert_eq!(map.len(), 1);
3095        let mut iter = map.iter();
3096        assert!(matches!(iter.next(), Some(a) if a.signer() == Participant::new(2)));
3097        assert!(iter.next().is_none());
3098    }
3099
3100    #[test]
3101    #[should_panic(expected = "proposals must differ")]
3102    fn issue_2944_regression_conflicting_notarize_new() {
3103        let mut rng = test_rng();
3104        let fixture = ed25519::fixture(&mut rng, NAMESPACE, 1);
3105        let proposal = Proposal::new(
3106            Round::new(Epoch::new(0), View::new(10)),
3107            View::new(5),
3108            sample_digest(1),
3109        );
3110        let notarize = Notarize::sign(&fixture.schemes[0], proposal).unwrap();
3111        let _ = ConflictingNotarize::new(notarize.clone(), notarize);
3112    }
3113
3114    #[test]
3115    fn issue_2944_regression_conflicting_notarize_decode() {
3116        let mut rng = test_rng();
3117        let fixture = ed25519::fixture(&mut rng, NAMESPACE, 1);
3118        let proposal = Proposal::new(
3119            Round::new(Epoch::new(0), View::new(10)),
3120            View::new(5),
3121            sample_digest(1),
3122        );
3123        let notarize = Notarize::sign(&fixture.schemes[0], proposal).unwrap();
3124
3125        // Manually encode two identical notarizes
3126        let mut buf = Vec::new();
3127        notarize.write(&mut buf);
3128        notarize.write(&mut buf);
3129
3130        // Decoding should fail
3131        let result = ConflictingNotarize::<ed25519::Scheme, Sha256>::decode(buf);
3132        assert!(result.is_err());
3133    }
3134
3135    #[test]
3136    #[should_panic(expected = "proposals must differ")]
3137    fn issue_2944_regression_conflicting_finalize_new() {
3138        let mut rng = test_rng();
3139        let fixture = ed25519::fixture(&mut rng, NAMESPACE, 1);
3140        let proposal = Proposal::new(
3141            Round::new(Epoch::new(0), View::new(10)),
3142            View::new(5),
3143            sample_digest(1),
3144        );
3145        let finalize = Finalize::sign(&fixture.schemes[0], proposal).unwrap();
3146        let _ = ConflictingFinalize::new(finalize.clone(), finalize);
3147    }
3148
3149    #[test]
3150    fn issue_2944_regression_conflicting_finalize_decode() {
3151        let mut rng = test_rng();
3152        let fixture = ed25519::fixture(&mut rng, NAMESPACE, 1);
3153        let proposal = Proposal::new(
3154            Round::new(Epoch::new(0), View::new(10)),
3155            View::new(5),
3156            sample_digest(1),
3157        );
3158        let finalize = Finalize::sign(&fixture.schemes[0], proposal).unwrap();
3159
3160        // Manually encode two identical finalizes
3161        let mut buf = Vec::new();
3162        finalize.write(&mut buf);
3163        finalize.write(&mut buf);
3164
3165        // Decoding should fail
3166        let result = ConflictingFinalize::<ed25519::Scheme, Sha256>::decode(buf);
3167        assert!(result.is_err());
3168    }
3169
3170    #[cfg(feature = "arbitrary")]
3171    mod conformance {
3172        use super::*;
3173        use crate::simplex::scheme::bls12381_threshold::vrf as bls12381_threshold_vrf;
3174        use commonware_codec::conformance::CodecConformance;
3175        use commonware_cryptography::{ed25519::PublicKey, sha256::Digest as Sha256Digest};
3176
3177        type Scheme = bls12381_threshold_vrf::Scheme<PublicKey, MinSig>;
3178
3179        commonware_conformance::conformance_tests! {
3180            CodecConformance<Vote<Scheme, Sha256Digest>>,
3181            CodecConformance<Certificate<Scheme, Sha256Digest>>,
3182            CodecConformance<Artifact<Scheme, Sha256Digest>>,
3183            CodecConformance<Proposal<Sha256Digest>>,
3184            CodecConformance<Notarize<Scheme, Sha256Digest>>,
3185            CodecConformance<Notarization<Scheme, Sha256Digest>>,
3186            CodecConformance<Nullify<Scheme>>,
3187            CodecConformance<Nullification<Scheme>>,
3188            CodecConformance<Finalize<Scheme, Sha256Digest>>,
3189            CodecConformance<Finalization<Scheme, Sha256Digest>>,
3190            CodecConformance<Activity<Scheme, Sha256Digest>>,
3191            CodecConformance<ConflictingNotarize<Scheme, Sha256Digest>>,
3192            CodecConformance<ConflictingFinalize<Scheme, Sha256Digest>>,
3193            CodecConformance<NullifyFinalize<Scheme, Sha256Digest>>,
3194            CodecConformance<Context<Sha256Digest, PublicKey>>
3195        }
3196    }
3197}