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::{Buf, BufMut};
9use commonware_codec::{EncodeSize, Error, Read, ReadExt, ReadRangeExt, Write, varint::UInt};
10use commonware_cryptography::{
11    Digest, PublicKey,
12    certificate::{AssemblyError, Attestation, Scheme},
13};
14use commonware_parallel::Strategy;
15use commonware_utils::{iter::NonEmpty, non_empty};
16use rand_core::CryptoRng;
17use std::{collections::HashSet, 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. The parent remains valid even if a later nullification in its own
35    /// term covers the parent view.
36    pub parent: (View, D),
37}
38
39impl<D: Digest, P: PublicKey> Epochable for Context<D, P> {
40    fn epoch(&self) -> Epoch {
41        self.round.epoch()
42    }
43}
44
45impl<D: Digest, P: PublicKey> Viewable for Context<D, P> {
46    fn view(&self) -> View {
47        self.round.view()
48    }
49}
50
51impl<D: Digest, P: PublicKey> Write for Context<D, P> {
52    fn write(&self, buf: &mut impl BufMut) {
53        self.round.write(buf);
54        self.leader.write(buf);
55        self.parent.write(buf);
56    }
57}
58
59impl<D: Digest, P: PublicKey> EncodeSize for Context<D, P> {
60    fn encode_size(&self) -> usize {
61        self.round.encode_size() + self.leader.encode_size() + self.parent.encode_size()
62    }
63}
64
65impl<D: Digest, P: PublicKey> Read for Context<D, P> {
66    type Cfg = ();
67
68    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
69        let round = Round::read(reader)?;
70        let leader = P::read(reader)?;
71        let parent = <(View, D)>::read_cfg(reader, &((), ()))?;
72
73        Ok(Self {
74            round,
75            leader,
76            parent,
77        })
78    }
79}
80
81#[cfg(feature = "arbitrary")]
82impl<D: Digest, P: PublicKey> arbitrary::Arbitrary<'_> for Context<D, P>
83where
84    D: for<'a> arbitrary::Arbitrary<'a>,
85    P: for<'a> arbitrary::Arbitrary<'a>,
86{
87    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
88        Ok(Self {
89            round: Round::arbitrary(u)?,
90            leader: P::arbitrary(u)?,
91            parent: (View::arbitrary(u)?, D::arbitrary(u)?),
92        })
93    }
94}
95
96/// Attributable is a trait that provides access to the signer index.
97/// This is used to identify which participant signed a given message.
98pub trait Attributable {
99    /// Returns the index of the signer (validator) who produced this message.
100    fn signer(&self) -> Participant;
101}
102
103/// A map of [Attributable] items keyed by their signer index.
104///
105/// The key for each item is automatically inferred from [Attributable::signer()].
106/// Each signer can insert at most one item.
107pub struct AttributableMap<T: Attributable> {
108    participants: usize,
109    data: Vec<Option<T>>,
110    added: usize,
111}
112
113impl<T: Attributable> AttributableMap<T> {
114    /// Creates a new [AttributableMap] with the given number of participants.
115    pub const fn new(participants: usize) -> Self {
116        Self {
117            participants,
118            data: Vec::new(),
119            added: 0,
120        }
121    }
122
123    /// Clears all existing items and releases their storage.
124    pub fn clear(&mut self) {
125        self.data = Vec::new();
126        self.added = 0;
127    }
128
129    /// Inserts an item into the map, using [Attributable::signer()] as the key,
130    /// if it has not been added yet.
131    ///
132    /// Returns `true` if the item was inserted, `false` if an item from this
133    /// signer already exists or if the signer index is out of bounds.
134    pub fn insert(&mut self, item: T) -> bool {
135        let index: usize = item.signer().into();
136        if index >= self.participants {
137            return false;
138        }
139        if self.data.is_empty() {
140            // `resize_with` avoids requiring `T: Clone` while pre-filling with `None`.
141            self.data.reserve_exact(self.participants);
142            self.data.resize_with(self.participants, || None);
143        }
144        if self.data[index].is_some() {
145            return false;
146        }
147        self.data[index] = Some(item);
148        self.added += 1;
149        true
150    }
151
152    /// Returns the number of items in the [AttributableMap].
153    pub const fn len(&self) -> usize {
154        self.added
155    }
156
157    /// Returns `true` if the [AttributableMap] is empty.
158    pub const fn is_empty(&self) -> bool {
159        self.added == 0
160    }
161
162    /// Returns a reference to the item associated with the given signer, if present.
163    pub fn get(&self, signer: Participant) -> Option<&T> {
164        self.data.get(<usize>::from(signer))?.as_ref()
165    }
166
167    /// Returns an iterator over items in the map, ordered by signer index
168    /// ([Attributable::signer()]).
169    pub fn iter(&self) -> impl Iterator<Item = &T> {
170        self.data.iter().filter_map(|o| o.as_ref())
171    }
172}
173
174/// Full vote storage for a phase, or a marker that its certificate was recorded.
175#[cfg(not(target_arch = "wasm32"))]
176enum Phase<T: Attributable> {
177    Full(AttributableMap<T>),
178    Compacted,
179}
180
181#[cfg(not(target_arch = "wasm32"))]
182impl<T: Attributable> Phase<T> {
183    const fn new(participants: usize) -> Self {
184        Self::Full(AttributableMap::new(participants))
185    }
186
187    fn insert(&mut self, vote: T) -> bool {
188        match self {
189            Self::Full(votes) => votes.insert(vote),
190            Self::Compacted => false,
191        }
192    }
193
194    fn get(&self, signer: Participant) -> Option<&T> {
195        match self {
196            Self::Full(votes) => votes.get(signer),
197            Self::Compacted => None,
198        }
199    }
200
201    fn iter(&self) -> impl Iterator<Item = &T> {
202        match self {
203            Self::Full(votes) => Some(votes),
204            Self::Compacted => None,
205        }
206        .into_iter()
207        .flat_map(AttributableMap::iter)
208    }
209
210    const fn len(&self) -> usize {
211        match self {
212            Self::Full(votes) => votes.len(),
213            Self::Compacted => 0,
214        }
215    }
216
217    fn compact(&mut self) -> Option<AttributableMap<T>> {
218        match std::mem::replace(self, Self::Compacted) {
219            Self::Full(votes) => Some(votes),
220            Self::Compacted => None,
221        }
222    }
223
224    fn reset(&mut self, participants: usize) {
225        *self = Self::new(participants);
226    }
227}
228
229/// Tracks notarize/nullify/finalize votes for a view.
230///
231/// Each vote type is stored in its own lazily allocated phase so a validator can
232/// contribute at most one vote per phase. After certification, compact signer facts
233/// can replace full votes while preserving forwarding, duplicate suppression, and
234/// compact conflict detection.
235#[cfg(not(target_arch = "wasm32"))]
236pub struct VoteTracker<S: Scheme, D: Digest> {
237    participants: usize,
238    retain_votes_after_certification: bool,
239    /// Compact state records whether a signer voted and whether the vote carried the
240    /// authoritative proposal. The first fact suppresses duplicates and cross-phase
241    /// conflicts. The second avoids forwarding a block to validators that already have it.
242    compacted: Vec<u8>,
243    notarizes: Phase<Notarize<S, D>>,
244    nullifies: Phase<Nullify<S>>,
245    /// Finalize votes include the proposal digest so the entire certificate can be
246    /// reconstructed once the quorum threshold is hit.
247    finalizes: Phase<Finalize<S, D>>,
248}
249
250/// Outcome of recording a vote in its phase-specific lifecycle state.
251#[cfg(not(target_arch = "wasm32"))]
252pub(crate) enum Outcome {
253    /// Newly recorded, with the full vote retained when `retained` is true.
254    Added { retained: bool },
255    /// Not newly recorded, with full votes still retained when `retained` is true.
256    Duplicate { retained: bool },
257    /// The compact proposal relation proves a same-phase conflict.
258    Conflicting,
259}
260
261/// A recorded vote retained in full or represented by compact signer state.
262#[cfg(not(target_arch = "wasm32"))]
263pub(crate) enum ObservedVote<'a, T> {
264    Retained(&'a T),
265    Compacted,
266}
267
268#[cfg(not(target_arch = "wasm32"))]
269impl<S: Scheme, D: Digest> VoteTracker<S, D> {
270    const NOTARIZE_SEEN: u8 = 1 << 0;
271    const NOTARIZE_HAS_PROPOSAL: u8 = 1 << 1;
272    const NULLIFY_SEEN: u8 = 1 << 2;
273    const FINALIZE_SEEN: u8 = 1 << 3;
274    const FINALIZE_HAS_PROPOSAL: u8 = 1 << 4;
275
276    /// Creates a tracker sized for `participants` validators.
277    ///
278    /// When `retain_votes_after_certification` is false, full votes are released
279    /// once their phase certifies. Otherwise they remain until explicitly cleared
280    /// or the tracker is dropped.
281    pub const fn new(participants: usize, retain_votes_after_certification: bool) -> Self {
282        Self {
283            participants,
284            retain_votes_after_certification,
285            compacted: Vec::new(),
286            notarizes: Phase::new(participants),
287            nullifies: Phase::new(participants),
288            finalizes: Phase::new(participants),
289        }
290    }
291}
292
293#[cfg(not(target_arch = "wasm32"))]
294impl<S: Scheme, D: Digest> VoteTracker<S, D> {
295    /// Records monotonic signer facts after a phase releases its full vote map.
296    ///
297    /// A later matching vote can record that the signer has the authoritative
298    /// proposal even when the signer was already observed.
299    fn remember(
300        participants: usize,
301        compacted: &mut Vec<u8>,
302        signer: Participant,
303        seen: u8,
304        proposal_relation: Option<(u8, bool)>,
305    ) -> Outcome {
306        let index = usize::from(signer);
307        if index >= participants {
308            return Outcome::Duplicate { retained: false };
309        }
310
311        // Certificate-first rounds remain allocation-free until a vote arrives.
312        if compacted.is_empty() {
313            compacted.resize(participants, 0);
314        }
315
316        let flags = &mut compacted[index];
317        let previously_seen = *flags & seen != 0;
318        let proposal_conflict = proposal_relation.is_some_and(|(has_proposal, matches)| {
319            previously_seen && (*flags & has_proposal != 0) != matches
320        });
321        *flags |= seen;
322        if let Some((has_proposal, true)) = proposal_relation {
323            *flags |= has_proposal;
324        }
325        if !previously_seen {
326            Outcome::Added { retained: false }
327        } else if proposal_conflict {
328            Outcome::Conflicting
329        } else {
330            Outcome::Duplicate { retained: false }
331        }
332    }
333
334    /// Records one phase according to its full-to-compact storage lifecycle.
335    ///
336    /// A full phase owns duplicate detection and full-vote storage. A compacted
337    /// phase retains only signer facts until explicitly cleared.
338    fn record_phase<T: Attributable + Clone>(
339        participants: usize,
340        compacted: &mut Vec<u8>,
341        phase: &mut Phase<T>,
342        vote: &T,
343        seen: u8,
344        proposal_relation: Option<(u8, bool)>,
345    ) -> Outcome {
346        match phase {
347            Phase::Full(votes) => {
348                if votes.insert(vote.clone()) {
349                    Outcome::Added { retained: true }
350                } else {
351                    Outcome::Duplicate { retained: true }
352                }
353            }
354            Phase::Compacted => Self::remember(
355                participants,
356                compacted,
357                vote.signer(),
358                seen,
359                proposal_relation,
360            ),
361        }
362    }
363
364    fn remembered(&self, signer: Participant, flag: u8) -> bool {
365        self.compacted
366            .get(usize::from(signer))
367            .is_some_and(|flags| flags & flag != 0)
368    }
369
370    /// Records a vote in full or as compact post-certificate state.
371    ///
372    /// `proposal` identifies the authoritative proposal used for compact match state.
373    pub(crate) fn record(&mut self, vote: &Vote<S, D>, proposal: Option<&Proposal<D>>) -> Outcome {
374        match vote {
375            Vote::Notarize(notarize) => Self::record_phase(
376                self.participants,
377                &mut self.compacted,
378                &mut self.notarizes,
379                notarize,
380                Self::NOTARIZE_SEEN,
381                proposal
382                    .map(|proposal| (Self::NOTARIZE_HAS_PROPOSAL, proposal == &notarize.proposal)),
383            ),
384            Vote::Nullify(nullify) => Self::record_phase(
385                self.participants,
386                &mut self.compacted,
387                &mut self.nullifies,
388                nullify,
389                Self::NULLIFY_SEEN,
390                None,
391            ),
392            Vote::Finalize(finalize) => Self::record_phase(
393                self.participants,
394                &mut self.compacted,
395                &mut self.finalizes,
396                finalize,
397                Self::FINALIZE_SEEN,
398                proposal
399                    .map(|proposal| (Self::FINALIZE_HAS_PROPOSAL, proposal == &finalize.proposal)),
400            ),
401        }
402    }
403
404    /// Moves a phase from full vote storage to compact signer state.
405    ///
406    /// Taking the map makes the transition idempotent. Empty phases remain
407    /// allocation-free, while existing signer and proposal-match facts survive.
408    fn release<T: Attributable>(
409        participants: usize,
410        compacted: &mut Vec<u8>,
411        phase: &mut Phase<T>,
412        seen: u8,
413        has_proposal: u8,
414        carries_proposal: impl Fn(&T) -> bool,
415    ) {
416        let Some(votes) = phase.compact() else {
417            return;
418        };
419
420        // A certificate may arrive before any individual votes, in which case there
421        // are no signer facts worth allocating a table for.
422        if !votes.is_empty() && compacted.is_empty() {
423            compacted.resize(participants, 0);
424        }
425
426        // Proposal-match bits are relative to the certificate-backed proposal.
427        for vote in votes.iter() {
428            let flags = &mut compacted[usize::from(vote.signer())];
429            *flags |= seen;
430            if carries_proposal(vote) {
431                *flags |= has_proposal;
432            }
433        }
434    }
435
436    /// Returns the retained or compact state for a previously observed nullify vote.
437    pub(crate) fn saw_nullify(&self, signer: Participant) -> Option<ObservedVote<'_, Nullify<S>>> {
438        self.nullify(signer)
439            .map(ObservedVote::Retained)
440            .or_else(|| {
441                self.remembered(signer, Self::NULLIFY_SEEN)
442                    .then_some(ObservedVote::Compacted)
443            })
444    }
445
446    /// Returns the retained or compact state for a previously observed finalize vote.
447    pub(crate) fn saw_finalize(
448        &self,
449        signer: Participant,
450    ) -> Option<ObservedVote<'_, Finalize<S, D>>> {
451        self.finalize(signer)
452            .map(ObservedVote::Retained)
453            .or_else(|| {
454                self.remembered(signer, Self::FINALIZE_SEEN)
455                    .then_some(ObservedVote::Compacted)
456            })
457    }
458
459    /// Returns whether `signer` is known to have the authoritative proposal
460    /// from notarizing it.
461    pub(crate) fn has_notarize_for(&self, signer: Participant, proposal: &Proposal<D>) -> bool {
462        self.remembered(signer, Self::NOTARIZE_HAS_PROPOSAL)
463            || self
464                .notarize(signer)
465                .is_some_and(|vote| &vote.proposal == proposal)
466    }
467
468    /// Returns whether `signer` is known to have the authoritative proposal
469    /// from finalizing it.
470    pub(crate) fn has_finalize_for(&self, signer: Participant, proposal: &Proposal<D>) -> bool {
471        self.remembered(signer, Self::FINALIZE_HAS_PROPOSAL)
472            || self
473                .finalize(signer)
474                .is_some_and(|vote| &vote.proposal == proposal)
475    }
476
477    /// Releases notarize votes unless full evidence is configured for retention.
478    pub(crate) fn release_notarizes(&mut self, proposal: &Proposal<D>) {
479        if self.retain_votes_after_certification {
480            return;
481        }
482        Self::release(
483            self.participants,
484            &mut self.compacted,
485            &mut self.notarizes,
486            Self::NOTARIZE_SEEN,
487            Self::NOTARIZE_HAS_PROPOSAL,
488            |vote: &Notarize<S, D>| &vote.proposal == proposal,
489        );
490    }
491
492    /// Releases nullify votes unless full evidence is configured for retention.
493    pub(crate) fn release_nullifies(&mut self) {
494        if self.retain_votes_after_certification {
495            return;
496        }
497        Self::release(
498            self.participants,
499            &mut self.compacted,
500            &mut self.nullifies,
501            Self::NULLIFY_SEEN,
502            0,
503            |_| false,
504        );
505    }
506
507    /// Releases finalize votes unless full evidence is configured for retention.
508    pub(crate) fn release_finalizes(&mut self, proposal: &Proposal<D>) {
509        if self.retain_votes_after_certification {
510            return;
511        }
512        Self::release(
513            self.participants,
514            &mut self.compacted,
515            &mut self.finalizes,
516            Self::FINALIZE_SEEN,
517            Self::FINALIZE_HAS_PROPOSAL,
518            |vote: &Finalize<S, D>| &vote.proposal == proposal,
519        );
520    }
521}
522
523#[cfg(not(target_arch = "wasm32"))]
524impl<S: Scheme, D: Digest> VoteTracker<S, D> {
525    fn clear_compacted(&mut self, cleared: u8) {
526        for flags in &mut self.compacted {
527            *flags &= !cleared;
528        }
529        if self.compacted.iter().all(|&flags| flags == 0) {
530            self.compacted = Vec::new();
531        }
532    }
533
534    /// Inserts a notarize vote if the signer has not already voted.
535    pub fn insert_notarize(&mut self, vote: Notarize<S, D>) -> bool {
536        self.notarizes.insert(vote)
537    }
538
539    /// Inserts a nullify vote if the signer has not already voted.
540    pub fn insert_nullify(&mut self, vote: Nullify<S>) -> bool {
541        self.nullifies.insert(vote)
542    }
543
544    /// Inserts a finalize vote if the signer has not already voted.
545    pub fn insert_finalize(&mut self, vote: Finalize<S, D>) -> bool {
546        self.finalizes.insert(vote)
547    }
548
549    /// Returns the notarize vote for `signer`, if present.
550    pub fn notarize(&self, signer: Participant) -> Option<&Notarize<S, D>> {
551        self.notarizes.get(signer)
552    }
553
554    /// Returns the nullify vote for `signer`, if present.
555    pub fn nullify(&self, signer: Participant) -> Option<&Nullify<S>> {
556        self.nullifies.get(signer)
557    }
558
559    /// Returns the finalize vote for `signer`, if present.
560    pub fn finalize(&self, signer: Participant) -> Option<&Finalize<S, D>> {
561        self.finalizes.get(signer)
562    }
563
564    /// Iterates over notarize votes in signer order.
565    pub fn iter_notarizes(&self) -> impl Iterator<Item = &Notarize<S, D>> {
566        self.notarizes.iter()
567    }
568
569    /// Iterates over nullify votes in signer order.
570    pub fn iter_nullifies(&self) -> impl Iterator<Item = &Nullify<S>> {
571        self.nullifies.iter()
572    }
573
574    /// Iterates over finalize votes in signer order.
575    pub fn iter_finalizes(&self) -> impl Iterator<Item = &Finalize<S, D>> {
576        self.finalizes.iter()
577    }
578
579    /// Returns how many notarize votes have been recorded.
580    pub fn len_notarizes(&self) -> u32 {
581        let len = self.notarizes.len();
582        u32::try_from(len).expect("too many notarize votes")
583    }
584
585    /// Returns how many nullify votes have been recorded.
586    pub fn len_nullifies(&self) -> u32 {
587        let len = self.nullifies.len();
588        u32::try_from(len).expect("too many nullify votes")
589    }
590
591    /// Returns how many finalize votes have been recorded.
592    pub fn len_finalizes(&self) -> u32 {
593        let len = self.finalizes.len();
594        u32::try_from(len).expect("too many finalize votes")
595    }
596
597    /// Returns `true` if the given signer has a notarize vote recorded.
598    pub fn has_notarize(&self, signer: Participant) -> bool {
599        self.notarize(signer).is_some()
600    }
601
602    /// Returns `true` if a nullify vote has been recorded for `signer`.
603    pub fn has_nullify(&self, signer: Participant) -> bool {
604        self.nullify(signer).is_some()
605    }
606
607    /// Returns `true` if a finalize vote has been recorded for `signer`.
608    pub fn has_finalize(&self, signer: Participant) -> bool {
609        self.finalize(signer).is_some()
610    }
611
612    /// Clears all notarize votes and releases their storage.
613    pub fn clear_notarizes(&mut self) {
614        self.notarizes.reset(self.participants);
615        self.clear_compacted(Self::NOTARIZE_SEEN | Self::NOTARIZE_HAS_PROPOSAL);
616    }
617
618    /// Clears all finalize votes and releases their storage.
619    pub fn clear_finalizes(&mut self) {
620        self.finalizes.reset(self.participants);
621        self.clear_compacted(Self::FINALIZE_SEEN | Self::FINALIZE_HAS_PROPOSAL);
622    }
623}
624
625/// Identifies the subject of a vote or certificate.
626///
627/// Implementations use the subject to derive domain-separated message bytes for both
628/// individual votes and recovered certificates.
629#[derive(Copy, Clone, Debug)]
630pub enum Subject<'a, D: Digest> {
631    /// Subject for notarize votes and certificates, carrying the proposal.
632    Notarize { proposal: &'a Proposal<D> },
633    /// Subject for nullify votes and certificates, scoped to a round.
634    Nullify { round: Round },
635    /// Subject for finalize votes and certificates, carrying the proposal.
636    Finalize { proposal: &'a Proposal<D> },
637}
638
639impl<D: Digest> Viewable for Subject<'_, D> {
640    fn view(&self) -> View {
641        match self {
642            Subject::Notarize { proposal } => proposal.view(),
643            Subject::Nullify { round } => round.view(),
644            Subject::Finalize { proposal } => proposal.view(),
645        }
646    }
647}
648
649/// Vote represents individual votes ([Notarize], [Nullify], [Finalize]).
650#[derive(Clone, Debug, PartialEq)]
651pub enum Vote<S: Scheme, D: Digest> {
652    /// A validator's notarize vote over a proposal.
653    Notarize(Notarize<S, D>),
654    /// A validator's nullify vote used to skip the current view.
655    Nullify(Nullify<S>),
656    /// A validator's finalize vote over a proposal.
657    Finalize(Finalize<S, D>),
658}
659
660impl<S: Scheme, D: Digest> Write for Vote<S, D> {
661    fn write(&self, writer: &mut impl BufMut) {
662        match self {
663            Self::Notarize(v) => {
664                0u8.write(writer);
665                v.write(writer);
666            }
667            Self::Nullify(v) => {
668                1u8.write(writer);
669                v.write(writer);
670            }
671            Self::Finalize(v) => {
672                2u8.write(writer);
673                v.write(writer);
674            }
675        }
676    }
677}
678
679impl<S: Scheme, D: Digest> EncodeSize for Vote<S, D> {
680    fn encode_size(&self) -> usize {
681        1 + match self {
682            Self::Notarize(v) => v.encode_size(),
683            Self::Nullify(v) => v.encode_size(),
684            Self::Finalize(v) => v.encode_size(),
685        }
686    }
687}
688
689impl<S: Scheme, D: Digest> Read for Vote<S, D> {
690    type Cfg = ();
691
692    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
693        let tag = <u8>::read(reader)?;
694        match tag {
695            0 => {
696                let v = Notarize::read(reader)?;
697                Ok(Self::Notarize(v))
698            }
699            1 => {
700                let v = Nullify::read(reader)?;
701                Ok(Self::Nullify(v))
702            }
703            2 => {
704                let v = Finalize::read(reader)?;
705                Ok(Self::Finalize(v))
706            }
707            _ => Err(Error::Invalid("consensus::simplex::Vote", "Invalid type")),
708        }
709    }
710}
711
712impl<S: Scheme, D: Digest> Epochable for Vote<S, D> {
713    fn epoch(&self) -> Epoch {
714        match self {
715            Self::Notarize(v) => v.epoch(),
716            Self::Nullify(v) => v.epoch(),
717            Self::Finalize(v) => v.epoch(),
718        }
719    }
720}
721
722impl<S: Scheme, D: Digest> Viewable for Vote<S, D> {
723    fn view(&self) -> View {
724        match self {
725            Self::Notarize(v) => v.view(),
726            Self::Nullify(v) => v.view(),
727            Self::Finalize(v) => v.view(),
728        }
729    }
730}
731
732#[cfg(feature = "arbitrary")]
733impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Vote<S, D>
734where
735    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
736    D: for<'a> arbitrary::Arbitrary<'a>,
737{
738    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
739        let tag = u.int_in_range(0..=2)?;
740        match tag {
741            0 => {
742                let v = Notarize::arbitrary(u)?;
743                Ok(Self::Notarize(v))
744            }
745            1 => {
746                let v = Nullify::arbitrary(u)?;
747                Ok(Self::Nullify(v))
748            }
749            2 => {
750                let v = Finalize::arbitrary(u)?;
751                Ok(Self::Finalize(v))
752            }
753            _ => unreachable!(),
754        }
755    }
756}
757
758/// Certificate represents aggregated votes ([Notarization], [Nullification], [Finalization]).
759#[derive(Clone, Debug, PartialEq)]
760pub enum Certificate<S: Scheme, D: Digest> {
761    /// A recovered certificate for a notarization.
762    Notarization(Notarization<S, D>),
763    /// A recovered certificate for a nullification.
764    Nullification(Nullification<S>),
765    /// A recovered certificate for a finalization.
766    Finalization(Finalization<S, D>),
767}
768
769/// The discriminant of a [Certificate], naming its kind without its contents.
770#[cfg(not(target_arch = "wasm32"))]
771#[derive(Clone, Copy, Debug, PartialEq, Eq)]
772pub(crate) enum Kind {
773    Notarization,
774    Nullification,
775    Finalization,
776}
777
778#[cfg(not(target_arch = "wasm32"))]
779impl std::fmt::Display for Kind {
780    /// Writes the stable trace field value for this certificate type.
781    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
782        f.write_str(match self {
783            Self::Notarization => "notarization",
784            Self::Nullification => "nullification",
785            Self::Finalization => "finalization",
786        })
787    }
788}
789
790#[cfg(not(target_arch = "wasm32"))]
791impl<S: Scheme, D: Digest> Certificate<S, D> {
792    /// Returns this certificate's type.
793    pub(crate) const fn kind(&self) -> Kind {
794        match self {
795            Self::Notarization(_) => Kind::Notarization,
796            Self::Nullification(_) => Kind::Nullification,
797            Self::Finalization(_) => Kind::Finalization,
798        }
799    }
800}
801
802impl<S: Scheme, D: Digest> Write for Certificate<S, D> {
803    fn write(&self, writer: &mut impl BufMut) {
804        match self {
805            Self::Notarization(v) => {
806                0u8.write(writer);
807                v.write(writer);
808            }
809            Self::Nullification(v) => {
810                1u8.write(writer);
811                v.write(writer);
812            }
813            Self::Finalization(v) => {
814                2u8.write(writer);
815                v.write(writer);
816            }
817        }
818    }
819}
820
821impl<S: Scheme, D: Digest> EncodeSize for Certificate<S, D> {
822    fn encode_size(&self) -> usize {
823        1 + match self {
824            Self::Notarization(v) => v.encode_size(),
825            Self::Nullification(v) => v.encode_size(),
826            Self::Finalization(v) => v.encode_size(),
827        }
828    }
829}
830
831impl<S: Scheme, D: Digest> Read for Certificate<S, D> {
832    type Cfg = <S::Certificate as Read>::Cfg;
833
834    fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
835        let tag = <u8>::read(reader)?;
836        match tag {
837            0 => {
838                let v = Notarization::read_cfg(reader, cfg)?;
839                Ok(Self::Notarization(v))
840            }
841            1 => {
842                let v = Nullification::read_cfg(reader, cfg)?;
843                Ok(Self::Nullification(v))
844            }
845            2 => {
846                let v = Finalization::read_cfg(reader, cfg)?;
847                Ok(Self::Finalization(v))
848            }
849            _ => Err(Error::Invalid(
850                "consensus::simplex::Certificate",
851                "Invalid type",
852            )),
853        }
854    }
855}
856
857impl<S: Scheme, D: Digest> Epochable for Certificate<S, D> {
858    fn epoch(&self) -> Epoch {
859        match self {
860            Self::Notarization(v) => v.epoch(),
861            Self::Nullification(v) => v.epoch(),
862            Self::Finalization(v) => v.epoch(),
863        }
864    }
865}
866
867impl<S: Scheme, D: Digest> Viewable for Certificate<S, D> {
868    fn view(&self) -> View {
869        match self {
870            Self::Notarization(v) => v.view(),
871            Self::Nullification(v) => v.view(),
872            Self::Finalization(v) => v.view(),
873        }
874    }
875}
876
877impl<S: Scheme, D: Digest> Certificate<S, D> {
878    /// Verifies this certificate against the provided signing scheme.
879    pub fn verify<R: CryptoRng>(&self, rng: &mut R, scheme: &S, strategy: &impl Strategy) -> bool
880    where
881        S: scheme::Scheme<D>,
882    {
883        match self {
884            Self::Notarization(notarization) => notarization.verify(rng, scheme, strategy),
885            Self::Nullification(nullification) => {
886                nullification.verify::<_, D>(rng, scheme, strategy)
887            }
888            Self::Finalization(finalization) => finalization.verify(rng, scheme, strategy),
889        }
890    }
891}
892
893#[cfg(feature = "arbitrary")]
894impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Certificate<S, D>
895where
896    S::Certificate: for<'a> arbitrary::Arbitrary<'a>,
897    D: for<'a> arbitrary::Arbitrary<'a>,
898{
899    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
900        let tag = u.int_in_range(0..=2)?;
901        match tag {
902            0 => {
903                let v = Notarization::arbitrary(u)?;
904                Ok(Self::Notarization(v))
905            }
906            1 => {
907                let v = Nullification::arbitrary(u)?;
908                Ok(Self::Nullification(v))
909            }
910            2 => {
911                let v = Finalization::arbitrary(u)?;
912                Ok(Self::Finalization(v))
913            }
914            _ => unreachable!(),
915        }
916    }
917}
918
919/// Artifact represents all consensus artifacts (votes and certificates) for storage.
920#[derive(Clone, Debug, PartialEq)]
921pub enum Artifact<S: Scheme, D: Digest> {
922    /// A validator's notarize vote over a proposal.
923    Notarize(Notarize<S, D>),
924    /// A recovered certificate for a notarization.
925    Notarization(Notarization<S, D>),
926    /// A notarization was locally certified.
927    Certification(Round, bool),
928    /// A validator's nullify vote used to skip the current view.
929    Nullify(Nullify<S>),
930    /// A recovered certificate for a nullification.
931    Nullification(Nullification<S>),
932    /// A validator's finalize vote over a proposal.
933    Finalize(Finalize<S, D>),
934    /// A recovered certificate for a finalization.
935    Finalization(Finalization<S, D>),
936}
937
938impl<S: Scheme, D: Digest> Write for Artifact<S, D> {
939    fn write(&self, writer: &mut impl BufMut) {
940        match self {
941            Self::Notarize(v) => {
942                0u8.write(writer);
943                v.write(writer);
944            }
945            Self::Notarization(v) => {
946                1u8.write(writer);
947                v.write(writer);
948            }
949            Self::Certification(r, b) => {
950                2u8.write(writer);
951                r.write(writer);
952                b.write(writer);
953            }
954            Self::Nullify(v) => {
955                3u8.write(writer);
956                v.write(writer);
957            }
958            Self::Nullification(v) => {
959                4u8.write(writer);
960                v.write(writer);
961            }
962            Self::Finalize(v) => {
963                5u8.write(writer);
964                v.write(writer);
965            }
966            Self::Finalization(v) => {
967                6u8.write(writer);
968                v.write(writer);
969            }
970        }
971    }
972}
973
974impl<S: Scheme, D: Digest> EncodeSize for Artifact<S, D> {
975    fn encode_size(&self) -> usize {
976        1 + match self {
977            Self::Notarize(v) => v.encode_size(),
978            Self::Notarization(v) => v.encode_size(),
979            Self::Certification(r, b) => r.encode_size() + b.encode_size(),
980            Self::Nullify(v) => v.encode_size(),
981            Self::Nullification(v) => v.encode_size(),
982            Self::Finalize(v) => v.encode_size(),
983            Self::Finalization(v) => v.encode_size(),
984        }
985    }
986}
987
988impl<S: Scheme, D: Digest> Read for Artifact<S, D> {
989    type Cfg = <S::Certificate as Read>::Cfg;
990
991    fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
992        let tag = <u8>::read(reader)?;
993        match tag {
994            0 => {
995                let v = Notarize::read(reader)?;
996                Ok(Self::Notarize(v))
997            }
998            1 => {
999                let v = Notarization::read_cfg(reader, cfg)?;
1000                Ok(Self::Notarization(v))
1001            }
1002            2 => {
1003                let r = Round::read(reader)?;
1004                let b = bool::read(reader)?;
1005                Ok(Self::Certification(r, b))
1006            }
1007            3 => {
1008                let v = Nullify::read(reader)?;
1009                Ok(Self::Nullify(v))
1010            }
1011            4 => {
1012                let v = Nullification::read_cfg(reader, cfg)?;
1013                Ok(Self::Nullification(v))
1014            }
1015            5 => {
1016                let v = Finalize::read(reader)?;
1017                Ok(Self::Finalize(v))
1018            }
1019            6 => {
1020                let v = Finalization::read_cfg(reader, cfg)?;
1021                Ok(Self::Finalization(v))
1022            }
1023            _ => Err(Error::Invalid(
1024                "consensus::simplex::Artifact",
1025                "Invalid type",
1026            )),
1027        }
1028    }
1029}
1030
1031impl<S: Scheme, D: Digest> Epochable for Artifact<S, D> {
1032    fn epoch(&self) -> Epoch {
1033        match self {
1034            Self::Notarize(v) => v.epoch(),
1035            Self::Notarization(v) => v.epoch(),
1036            Self::Certification(r, _) => r.epoch(),
1037            Self::Nullify(v) => v.epoch(),
1038            Self::Nullification(v) => v.epoch(),
1039            Self::Finalize(v) => v.epoch(),
1040            Self::Finalization(v) => v.epoch(),
1041        }
1042    }
1043}
1044
1045impl<S: Scheme, D: Digest> Viewable for Artifact<S, D> {
1046    fn view(&self) -> View {
1047        match self {
1048            Self::Notarize(v) => v.view(),
1049            Self::Notarization(v) => v.view(),
1050            Self::Certification(r, _) => r.view(),
1051            Self::Nullify(v) => v.view(),
1052            Self::Nullification(v) => v.view(),
1053            Self::Finalize(v) => v.view(),
1054            Self::Finalization(v) => v.view(),
1055        }
1056    }
1057}
1058
1059impl<S: Scheme, D: Digest> From<Vote<S, D>> for Artifact<S, D> {
1060    fn from(vote: Vote<S, D>) -> Self {
1061        match vote {
1062            Vote::Notarize(v) => Self::Notarize(v),
1063            Vote::Nullify(v) => Self::Nullify(v),
1064            Vote::Finalize(v) => Self::Finalize(v),
1065        }
1066    }
1067}
1068
1069impl<S: Scheme, D: Digest> From<Certificate<S, D>> for Artifact<S, D> {
1070    fn from(cert: Certificate<S, D>) -> Self {
1071        match cert {
1072            Certificate::Notarization(v) => Self::Notarization(v),
1073            Certificate::Nullification(v) => Self::Nullification(v),
1074            Certificate::Finalization(v) => Self::Finalization(v),
1075        }
1076    }
1077}
1078
1079#[cfg(feature = "arbitrary")]
1080impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Artifact<S, D>
1081where
1082    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
1083    S::Certificate: for<'a> arbitrary::Arbitrary<'a>,
1084    D: for<'a> arbitrary::Arbitrary<'a>,
1085{
1086    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1087        let tag = u.int_in_range(0..=6)?;
1088        match tag {
1089            0 => {
1090                let v = Notarize::arbitrary(u)?;
1091                Ok(Self::Notarize(v))
1092            }
1093            1 => {
1094                let v = Notarization::arbitrary(u)?;
1095                Ok(Self::Notarization(v))
1096            }
1097            2 => {
1098                let r = Round::arbitrary(u)?;
1099                let b = bool::arbitrary(u)?;
1100                Ok(Self::Certification(r, b))
1101            }
1102            3 => {
1103                let v = Nullify::arbitrary(u)?;
1104                Ok(Self::Nullify(v))
1105            }
1106            4 => {
1107                let v = Nullification::arbitrary(u)?;
1108                Ok(Self::Nullification(v))
1109            }
1110            5 => {
1111                let v = Finalize::arbitrary(u)?;
1112                Ok(Self::Finalize(v))
1113            }
1114            6 => {
1115                let v = Finalization::arbitrary(u)?;
1116                Ok(Self::Finalization(v))
1117            }
1118            _ => unreachable!(),
1119        }
1120    }
1121}
1122
1123/// Proposal represents a proposed block in the protocol.
1124/// It includes the view number, the parent view, and the actual payload (typically a digest of block data).
1125#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1126pub struct Proposal<D: Digest> {
1127    /// The round in which this proposal is made
1128    pub round: Round,
1129    /// The view of the parent proposal that this one builds upon
1130    pub parent: View,
1131    /// The actual payload/content of the proposal (typically a digest of the block data)
1132    pub payload: D,
1133}
1134
1135impl<D: Digest> Proposal<D> {
1136    /// Creates a new proposal with the specified view, parent view, and payload.
1137    pub const fn new(round: Round, parent: View, payload: D) -> Self {
1138        Self {
1139            round,
1140            parent,
1141            payload,
1142        }
1143    }
1144}
1145
1146impl<D: Digest> Write for Proposal<D> {
1147    fn write(&self, writer: &mut impl BufMut) {
1148        self.round.write(writer);
1149        self.parent.write(writer);
1150        self.payload.write(writer)
1151    }
1152}
1153
1154impl<D: Digest> Read for Proposal<D> {
1155    type Cfg = ();
1156
1157    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
1158        let round = Round::read(reader)?;
1159        let parent = View::read(reader)?;
1160        let payload = D::read(reader)?;
1161        Ok(Self {
1162            round,
1163            parent,
1164            payload,
1165        })
1166    }
1167}
1168
1169impl<D: Digest> EncodeSize for Proposal<D> {
1170    fn encode_size(&self) -> usize {
1171        self.round.encode_size() + self.parent.encode_size() + self.payload.encode_size()
1172    }
1173}
1174
1175impl<D: Digest> Epochable for Proposal<D> {
1176    fn epoch(&self) -> Epoch {
1177        self.round.epoch()
1178    }
1179}
1180
1181impl<D: Digest> Viewable for Proposal<D> {
1182    fn view(&self) -> View {
1183        self.round.view()
1184    }
1185}
1186
1187#[cfg(feature = "arbitrary")]
1188impl<D: Digest> arbitrary::Arbitrary<'_> for Proposal<D>
1189where
1190    D: for<'a> arbitrary::Arbitrary<'a>,
1191{
1192    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1193        let round = Round::arbitrary(u)?;
1194        let parent = View::arbitrary(u)?;
1195        let payload = D::arbitrary(u)?;
1196        Ok(Self {
1197            round,
1198            parent,
1199            payload,
1200        })
1201    }
1202}
1203
1204/// Validator vote that endorses a proposal for notarization.
1205#[derive(Clone, Debug)]
1206pub struct Notarize<S: Scheme, D: Digest> {
1207    /// Proposal being notarized.
1208    pub proposal: Proposal<D>,
1209    /// Scheme-specific attestation material.
1210    pub attestation: Attestation<S>,
1211}
1212
1213impl<S: Scheme, D: Digest> Notarize<S, D> {
1214    /// Signs a notarize vote for the provided proposal.
1215    pub fn sign(scheme: &S, proposal: Proposal<D>) -> Option<Self>
1216    where
1217        S: scheme::Scheme<D>,
1218    {
1219        let attestation = scheme.sign::<D>(Subject::Notarize {
1220            proposal: &proposal,
1221        })?;
1222
1223        Some(Self {
1224            proposal,
1225            attestation,
1226        })
1227    }
1228
1229    /// Verifies the notarize vote against the provided signing scheme.
1230    ///
1231    /// This ensures that the notarize signature is valid for the claimed proposal.
1232    pub fn verify<R>(&self, rng: &mut R, scheme: &S, strategy: &impl Strategy) -> bool
1233    where
1234        R: CryptoRng,
1235        S: scheme::Scheme<D>,
1236    {
1237        scheme.verify_attestation::<_, D>(
1238            rng,
1239            Subject::Notarize {
1240                proposal: &self.proposal,
1241            },
1242            &self.attestation,
1243            strategy,
1244        )
1245    }
1246
1247    /// Returns the round associated with this notarize vote.
1248    pub const fn round(&self) -> Round {
1249        self.proposal.round
1250    }
1251}
1252
1253impl<S: Scheme, D: Digest> PartialEq for Notarize<S, D> {
1254    fn eq(&self, other: &Self) -> bool {
1255        self.proposal == other.proposal && self.attestation == other.attestation
1256    }
1257}
1258
1259impl<S: Scheme, D: Digest> Eq for Notarize<S, D> {}
1260
1261impl<S: Scheme, D: Digest> Hash for Notarize<S, D> {
1262    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1263        self.proposal.hash(state);
1264        self.attestation.hash(state);
1265    }
1266}
1267
1268impl<S: Scheme, D: Digest> Write for Notarize<S, D> {
1269    fn write(&self, writer: &mut impl BufMut) {
1270        self.proposal.write(writer);
1271        self.attestation.write(writer);
1272    }
1273}
1274
1275impl<S: Scheme, D: Digest> EncodeSize for Notarize<S, D> {
1276    fn encode_size(&self) -> usize {
1277        self.proposal.encode_size() + self.attestation.encode_size()
1278    }
1279}
1280
1281impl<S: Scheme, D: Digest> Read for Notarize<S, D> {
1282    type Cfg = ();
1283
1284    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
1285        let proposal = Proposal::read(reader)?;
1286        let attestation = Attestation::read(reader)?;
1287
1288        Ok(Self {
1289            proposal,
1290            attestation,
1291        })
1292    }
1293}
1294
1295impl<S: Scheme, D: Digest> Attributable for Notarize<S, D> {
1296    fn signer(&self) -> Participant {
1297        self.attestation.signer
1298    }
1299}
1300
1301impl<S: Scheme, D: Digest> Epochable for Notarize<S, D> {
1302    fn epoch(&self) -> Epoch {
1303        self.proposal.epoch()
1304    }
1305}
1306
1307impl<S: Scheme, D: Digest> Viewable for Notarize<S, D> {
1308    fn view(&self) -> View {
1309        self.proposal.view()
1310    }
1311}
1312
1313#[cfg(feature = "arbitrary")]
1314impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Notarize<S, D>
1315where
1316    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
1317    D: for<'a> arbitrary::Arbitrary<'a>,
1318{
1319    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1320        let proposal = Proposal::arbitrary(u)?;
1321        let attestation = Attestation::arbitrary(u)?;
1322        Ok(Self {
1323            proposal,
1324            attestation,
1325        })
1326    }
1327}
1328
1329/// Batch-verifies certificates and returns a per-item result.
1330///
1331/// Uses bisection to efficiently identify invalid certificates when batch
1332/// verification fails.
1333pub fn verify_certificates<'a, R, S, D>(
1334    rng: &mut R,
1335    scheme: &S,
1336    certificates: &[(Subject<'a, D>, &'a S::Certificate)],
1337    strategy: &impl Strategy,
1338) -> Vec<bool>
1339where
1340    R: CryptoRng,
1341    S: CertificateVerifier<D>,
1342    D: Digest,
1343{
1344    scheme.verify_certificates_bisect::<_, D>(rng, certificates, strategy)
1345}
1346
1347/// Aggregated notarization certificate recovered from notarize votes.
1348/// When a proposal is notarized, it means at least 2f+1 validators have voted for it.
1349///
1350/// Some signing schemes (like [`super::scheme::bls12381_threshold::vrf`]) embed an additional
1351/// randomness seed in the certificate. For threshold signatures, the seed can be accessed
1352/// via [`super::scheme::bls12381_threshold::vrf::Seedable::seed`].
1353#[derive(Clone, Debug)]
1354pub struct Notarization<S: Scheme, D: Digest> {
1355    /// The proposal that has been notarized.
1356    pub proposal: Proposal<D>,
1357    /// The recovered certificate for the proposal.
1358    pub certificate: S::Certificate,
1359}
1360
1361impl<S: Scheme, D: Digest> Notarization<S, D> {
1362    /// Builds a notarization certificate from non-empty owned notarize votes for the same
1363    /// proposal, consuming the votes to avoid cloning each attestation.
1364    pub fn from_owned_notarizes<I>(
1365        scheme: &S,
1366        notarizes: NonEmpty<I>,
1367        strategy: &impl Strategy,
1368    ) -> Result<Self, AssemblyError>
1369    where
1370        I: Iterator<Item = Notarize<S, D>> + Send,
1371    {
1372        let (first, notarizes) = notarizes.into_parts();
1373        let Notarize {
1374            proposal,
1375            attestation,
1376        } = first;
1377        let attestations =
1378            NonEmpty::new(attestation, notarizes.map(|notarize| notarize.attestation));
1379        let certificate = scheme.assemble(attestations, strategy)?;
1380
1381        Ok(Self {
1382            proposal,
1383            certificate,
1384        })
1385    }
1386
1387    /// Builds a notarization certificate from non-empty notarize votes for the same proposal.
1388    pub fn from_notarizes<'a, I>(
1389        scheme: &S,
1390        notarizes: NonEmpty<I>,
1391        strategy: &impl Strategy,
1392    ) -> Result<Self, AssemblyError>
1393    where
1394        I: Iterator<Item = &'a Notarize<S, D>> + Send,
1395    {
1396        Self::from_owned_notarizes(
1397            scheme,
1398            non_empty![@notarizes.into_iter().cloned()],
1399            strategy,
1400        )
1401    }
1402
1403    /// Verifies the notarization certificate against the provided signing scheme.
1404    ///
1405    /// This ensures that the certificate is valid for the claimed proposal.
1406    pub fn verify<R: CryptoRng>(
1407        &self,
1408        rng: &mut R,
1409        scheme: &impl CertificateVerifier<D, Certificate = S::Certificate>,
1410        strategy: &impl Strategy,
1411    ) -> bool {
1412        scheme.verify_certificate::<_, D>(
1413            rng,
1414            Subject::Notarize {
1415                proposal: &self.proposal,
1416            },
1417            &self.certificate,
1418            strategy,
1419        )
1420    }
1421
1422    /// Returns the round associated with the notarized proposal.
1423    pub const fn round(&self) -> Round {
1424        self.proposal.round
1425    }
1426}
1427
1428impl<S: Scheme, D: Digest> PartialEq for Notarization<S, D> {
1429    fn eq(&self, other: &Self) -> bool {
1430        self.proposal == other.proposal && self.certificate == other.certificate
1431    }
1432}
1433
1434impl<S: Scheme, D: Digest> Eq for Notarization<S, D> {}
1435
1436impl<S: Scheme, D: Digest> Hash for Notarization<S, D> {
1437    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1438        self.proposal.hash(state);
1439        self.certificate.hash(state);
1440    }
1441}
1442
1443impl<S: Scheme, D: Digest> Write for Notarization<S, D> {
1444    fn write(&self, writer: &mut impl BufMut) {
1445        self.proposal.write(writer);
1446        self.certificate.write(writer);
1447    }
1448}
1449
1450impl<S: Scheme, D: Digest> EncodeSize for Notarization<S, D> {
1451    fn encode_size(&self) -> usize {
1452        self.proposal.encode_size() + self.certificate.encode_size()
1453    }
1454}
1455
1456impl<S: Scheme, D: Digest> Read for Notarization<S, D> {
1457    type Cfg = <S::Certificate as Read>::Cfg;
1458
1459    fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
1460        let proposal = Proposal::read(reader)?;
1461        let certificate = S::Certificate::read_cfg(reader, cfg)?;
1462
1463        Ok(Self {
1464            proposal,
1465            certificate,
1466        })
1467    }
1468}
1469
1470impl<S: Scheme, D: Digest> Epochable for Notarization<S, D> {
1471    fn epoch(&self) -> Epoch {
1472        self.proposal.epoch()
1473    }
1474}
1475
1476impl<S: Scheme, D: Digest> Viewable for Notarization<S, D> {
1477    fn view(&self) -> View {
1478        self.proposal.view()
1479    }
1480}
1481
1482#[cfg(feature = "arbitrary")]
1483impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Notarization<S, D>
1484where
1485    S::Certificate: for<'a> arbitrary::Arbitrary<'a>,
1486    D: for<'a> arbitrary::Arbitrary<'a>,
1487{
1488    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1489        let proposal = Proposal::arbitrary(u)?;
1490        let certificate = S::Certificate::arbitrary(u)?;
1491        Ok(Self {
1492            proposal,
1493            certificate,
1494        })
1495    }
1496}
1497
1498/// Validator vote for nullifying the current round, i.e. skip the current round.
1499/// This is typically used when the leader is unresponsive or fails to propose a valid block.
1500#[derive(Clone, Debug)]
1501pub struct Nullify<S: Scheme> {
1502    /// The round to be nullified (skipped).
1503    pub round: Round,
1504    /// Scheme-specific attestation material.
1505    pub attestation: Attestation<S>,
1506}
1507
1508impl<S: Scheme> PartialEq for Nullify<S> {
1509    fn eq(&self, other: &Self) -> bool {
1510        self.round == other.round && self.attestation == other.attestation
1511    }
1512}
1513
1514impl<S: Scheme> Eq for Nullify<S> {}
1515
1516impl<S: Scheme> Hash for Nullify<S> {
1517    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1518        self.round.hash(state);
1519        self.attestation.hash(state);
1520    }
1521}
1522
1523impl<S: Scheme> Nullify<S> {
1524    /// Signs a nullify vote for the given round.
1525    pub fn sign<D: Digest>(scheme: &S, round: Round) -> Option<Self>
1526    where
1527        S: scheme::Scheme<D>,
1528    {
1529        let attestation = scheme.sign::<D>(Subject::Nullify { round })?;
1530
1531        Some(Self { round, attestation })
1532    }
1533
1534    /// Verifies the nullify vote against the provided signing scheme.
1535    ///
1536    /// This ensures that the nullify signature is valid for the given round.
1537    pub fn verify<R, D: Digest>(&self, rng: &mut R, scheme: &S, strategy: &impl Strategy) -> bool
1538    where
1539        R: CryptoRng,
1540        S: scheme::Scheme<D>,
1541    {
1542        scheme.verify_attestation::<_, D>(
1543            rng,
1544            Subject::Nullify { round: self.round },
1545            &self.attestation,
1546            strategy,
1547        )
1548    }
1549
1550    /// Returns the round associated with this nullify vote.
1551    pub const fn round(&self) -> Round {
1552        self.round
1553    }
1554}
1555
1556impl<S: Scheme> Write for Nullify<S> {
1557    fn write(&self, writer: &mut impl BufMut) {
1558        self.round.write(writer);
1559        self.attestation.write(writer);
1560    }
1561}
1562
1563impl<S: Scheme> EncodeSize for Nullify<S> {
1564    fn encode_size(&self) -> usize {
1565        self.round.encode_size() + self.attestation.encode_size()
1566    }
1567}
1568
1569impl<S: Scheme> Read for Nullify<S> {
1570    type Cfg = ();
1571
1572    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
1573        let round = Round::read(reader)?;
1574        let attestation = Attestation::read(reader)?;
1575
1576        Ok(Self { round, attestation })
1577    }
1578}
1579
1580impl<S: Scheme> Attributable for Nullify<S> {
1581    fn signer(&self) -> Participant {
1582        self.attestation.signer
1583    }
1584}
1585
1586impl<S: Scheme> Epochable for Nullify<S> {
1587    fn epoch(&self) -> Epoch {
1588        self.round.epoch()
1589    }
1590}
1591
1592impl<S: Scheme> Viewable for Nullify<S> {
1593    fn view(&self) -> View {
1594        self.round.view()
1595    }
1596}
1597
1598#[cfg(feature = "arbitrary")]
1599impl<S: Scheme> arbitrary::Arbitrary<'_> for Nullify<S>
1600where
1601    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
1602{
1603    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1604        let round = Round::arbitrary(u)?;
1605        let attestation = Attestation::arbitrary(u)?;
1606        Ok(Self { round, attestation })
1607    }
1608}
1609
1610/// Aggregated nullification certificate recovered from nullify votes.
1611/// When a view is nullified, consensus moves to the first view of the next
1612/// term without finalizing a block (the next view when `term_length` is 1);
1613/// a nullification covers the nullified view and the rest of its term.
1614#[derive(Clone, Debug)]
1615pub struct Nullification<S: Scheme> {
1616    /// The round in which this nullification is made.
1617    pub round: Round,
1618    /// The recovered certificate for the nullification.
1619    pub certificate: S::Certificate,
1620}
1621
1622impl<S: Scheme> Nullification<S> {
1623    /// Builds a nullification certificate from non-empty owned nullify votes from the same round,
1624    /// consuming the votes to avoid cloning each attestation.
1625    pub fn from_owned_nullifies<I>(
1626        scheme: &S,
1627        nullifies: NonEmpty<I>,
1628        strategy: &impl Strategy,
1629    ) -> Result<Self, AssemblyError>
1630    where
1631        I: Iterator<Item = Nullify<S>> + Send,
1632    {
1633        let (first, nullifies) = nullifies.into_parts();
1634        let round = first.round;
1635        let attestations = NonEmpty::new(
1636            first.attestation,
1637            nullifies.map(|nullify| nullify.attestation),
1638        );
1639        let certificate = scheme.assemble(attestations, strategy)?;
1640
1641        Ok(Self { round, certificate })
1642    }
1643
1644    /// Builds a nullification certificate from non-empty nullify votes from the same round.
1645    pub fn from_nullifies<'a, I>(
1646        scheme: &S,
1647        nullifies: NonEmpty<I>,
1648        strategy: &impl Strategy,
1649    ) -> Result<Self, AssemblyError>
1650    where
1651        I: Iterator<Item = &'a Nullify<S>> + Send,
1652    {
1653        Self::from_owned_nullifies(
1654            scheme,
1655            non_empty![@nullifies.into_iter().cloned()],
1656            strategy,
1657        )
1658    }
1659
1660    /// Verifies the nullification certificate against the provided signing scheme.
1661    ///
1662    /// This ensures that the certificate is valid for the claimed round.
1663    pub fn verify<R: CryptoRng, D: Digest>(
1664        &self,
1665        rng: &mut R,
1666        scheme: &impl CertificateVerifier<D, Certificate = S::Certificate>,
1667        strategy: &impl Strategy,
1668    ) -> bool {
1669        scheme.verify_certificate::<_, D>(
1670            rng,
1671            Subject::Nullify { round: self.round },
1672            &self.certificate,
1673            strategy,
1674        )
1675    }
1676
1677    /// Returns the round associated with this nullification.
1678    pub const fn round(&self) -> Round {
1679        self.round
1680    }
1681}
1682
1683impl<S: Scheme> PartialEq for Nullification<S> {
1684    fn eq(&self, other: &Self) -> bool {
1685        self.round == other.round && self.certificate == other.certificate
1686    }
1687}
1688
1689impl<S: Scheme> Eq for Nullification<S> {}
1690
1691impl<S: Scheme> Hash for Nullification<S> {
1692    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1693        self.round.hash(state);
1694        self.certificate.hash(state);
1695    }
1696}
1697
1698impl<S: Scheme> Write for Nullification<S> {
1699    fn write(&self, writer: &mut impl BufMut) {
1700        self.round.write(writer);
1701        self.certificate.write(writer);
1702    }
1703}
1704
1705impl<S: Scheme> EncodeSize for Nullification<S> {
1706    fn encode_size(&self) -> usize {
1707        self.round.encode_size() + self.certificate.encode_size()
1708    }
1709}
1710
1711impl<S: Scheme> Read for Nullification<S> {
1712    type Cfg = <S::Certificate as Read>::Cfg;
1713
1714    fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
1715        let round = Round::read(reader)?;
1716        let certificate = S::Certificate::read_cfg(reader, cfg)?;
1717
1718        Ok(Self { round, certificate })
1719    }
1720}
1721
1722impl<S: Scheme> Epochable for Nullification<S> {
1723    fn epoch(&self) -> Epoch {
1724        self.round.epoch()
1725    }
1726}
1727
1728impl<S: Scheme> Viewable for Nullification<S> {
1729    fn view(&self) -> View {
1730        self.round.view()
1731    }
1732}
1733
1734#[cfg(feature = "arbitrary")]
1735impl<S: Scheme> arbitrary::Arbitrary<'_> for Nullification<S>
1736where
1737    S::Certificate: for<'a> arbitrary::Arbitrary<'a>,
1738{
1739    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1740        let round = Round::arbitrary(u)?;
1741        let certificate = S::Certificate::arbitrary(u)?;
1742        Ok(Self { round, certificate })
1743    }
1744}
1745
1746/// Validator vote to finalize a proposal.
1747/// This happens after a proposal has been notarized, confirming it as the canonical block
1748/// for this round.
1749#[derive(Clone, Debug)]
1750pub struct Finalize<S: Scheme, D: Digest> {
1751    /// Proposal being finalized.
1752    pub proposal: Proposal<D>,
1753    /// Scheme-specific attestation material.
1754    pub attestation: Attestation<S>,
1755}
1756
1757impl<S: Scheme, D: Digest> Finalize<S, D> {
1758    /// Signs a finalize vote for the provided proposal.
1759    pub fn sign(scheme: &S, proposal: Proposal<D>) -> Option<Self>
1760    where
1761        S: scheme::Scheme<D>,
1762    {
1763        let attestation = scheme.sign::<D>(Subject::Finalize {
1764            proposal: &proposal,
1765        })?;
1766
1767        Some(Self {
1768            proposal,
1769            attestation,
1770        })
1771    }
1772
1773    /// Verifies the finalize vote against the provided signing scheme.
1774    ///
1775    /// This ensures that the finalize signature is valid for the claimed proposal.
1776    pub fn verify<R>(&self, rng: &mut R, scheme: &S, strategy: &impl Strategy) -> bool
1777    where
1778        R: CryptoRng,
1779        S: scheme::Scheme<D>,
1780    {
1781        scheme.verify_attestation::<_, D>(
1782            rng,
1783            Subject::Finalize {
1784                proposal: &self.proposal,
1785            },
1786            &self.attestation,
1787            strategy,
1788        )
1789    }
1790
1791    /// Returns the round associated with this finalize vote.
1792    pub const fn round(&self) -> Round {
1793        self.proposal.round
1794    }
1795}
1796
1797impl<S: Scheme, D: Digest> PartialEq for Finalize<S, D> {
1798    fn eq(&self, other: &Self) -> bool {
1799        self.proposal == other.proposal && self.attestation == other.attestation
1800    }
1801}
1802
1803impl<S: Scheme, D: Digest> Eq for Finalize<S, D> {}
1804
1805impl<S: Scheme, D: Digest> Hash for Finalize<S, D> {
1806    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1807        self.proposal.hash(state);
1808        self.attestation.hash(state);
1809    }
1810}
1811
1812impl<S: Scheme, D: Digest> Write for Finalize<S, D> {
1813    fn write(&self, writer: &mut impl BufMut) {
1814        self.proposal.write(writer);
1815        self.attestation.write(writer);
1816    }
1817}
1818
1819impl<S: Scheme, D: Digest> EncodeSize for Finalize<S, D> {
1820    fn encode_size(&self) -> usize {
1821        self.proposal.encode_size() + self.attestation.encode_size()
1822    }
1823}
1824
1825impl<S: Scheme, D: Digest> Read for Finalize<S, D> {
1826    type Cfg = ();
1827
1828    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
1829        let proposal = Proposal::read(reader)?;
1830        let attestation = Attestation::read(reader)?;
1831
1832        Ok(Self {
1833            proposal,
1834            attestation,
1835        })
1836    }
1837}
1838
1839impl<S: Scheme, D: Digest> Attributable for Finalize<S, D> {
1840    fn signer(&self) -> Participant {
1841        self.attestation.signer
1842    }
1843}
1844
1845impl<S: Scheme, D: Digest> Epochable for Finalize<S, D> {
1846    fn epoch(&self) -> Epoch {
1847        self.proposal.epoch()
1848    }
1849}
1850
1851impl<S: Scheme, D: Digest> Viewable for Finalize<S, D> {
1852    fn view(&self) -> View {
1853        self.proposal.view()
1854    }
1855}
1856
1857#[cfg(feature = "arbitrary")]
1858impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Finalize<S, D>
1859where
1860    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
1861    D: for<'a> arbitrary::Arbitrary<'a>,
1862{
1863    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1864        let proposal = Proposal::arbitrary(u)?;
1865        let attestation = Attestation::arbitrary(u)?;
1866        Ok(Self {
1867            proposal,
1868            attestation,
1869        })
1870    }
1871}
1872
1873/// Aggregated finalization certificate recovered from finalize votes.
1874/// When a proposal is finalized, it becomes the canonical block for its view.
1875///
1876/// Some signing schemes (like [`super::scheme::bls12381_threshold::vrf`]) embed an additional
1877/// randomness seed in the certificate. For threshold signatures, the seed can be accessed
1878/// via [`super::scheme::bls12381_threshold::vrf::Seedable::seed`].
1879#[derive(Clone, Debug)]
1880pub struct Finalization<S: Scheme, D: Digest> {
1881    /// The proposal that has been finalized.
1882    pub proposal: Proposal<D>,
1883    /// The recovered certificate for the proposal.
1884    pub certificate: S::Certificate,
1885}
1886
1887impl<S: Scheme, D: Digest> Finalization<S, D> {
1888    /// Builds a finalization certificate from non-empty owned finalize votes for the same proposal,
1889    /// consuming the votes to avoid cloning each attestation.
1890    pub fn from_owned_finalizes<I>(
1891        scheme: &S,
1892        finalizes: NonEmpty<I>,
1893        strategy: &impl Strategy,
1894    ) -> Result<Self, AssemblyError>
1895    where
1896        I: Iterator<Item = Finalize<S, D>> + Send,
1897    {
1898        let (first, finalizes) = finalizes.into_parts();
1899        let Finalize {
1900            proposal,
1901            attestation,
1902        } = first;
1903        let attestations =
1904            NonEmpty::new(attestation, finalizes.map(|finalize| finalize.attestation));
1905        let certificate = scheme.assemble(attestations, strategy)?;
1906
1907        Ok(Self {
1908            proposal,
1909            certificate,
1910        })
1911    }
1912
1913    /// Builds a finalization certificate from non-empty finalize votes for the same proposal.
1914    pub fn from_finalizes<'a, I>(
1915        scheme: &S,
1916        finalizes: NonEmpty<I>,
1917        strategy: &impl Strategy,
1918    ) -> Result<Self, AssemblyError>
1919    where
1920        I: Iterator<Item = &'a Finalize<S, D>> + Send,
1921    {
1922        Self::from_owned_finalizes(
1923            scheme,
1924            non_empty![@finalizes.into_iter().cloned()],
1925            strategy,
1926        )
1927    }
1928
1929    /// Verifies the finalization certificate against the provided signing scheme.
1930    ///
1931    /// This ensures that the certificate is valid for the claimed proposal.
1932    pub fn verify<R: CryptoRng>(
1933        &self,
1934        rng: &mut R,
1935        scheme: &impl CertificateVerifier<D, Certificate = S::Certificate>,
1936        strategy: &impl Strategy,
1937    ) -> bool {
1938        scheme.verify_certificate::<_, D>(
1939            rng,
1940            Subject::Finalize {
1941                proposal: &self.proposal,
1942            },
1943            &self.certificate,
1944            strategy,
1945        )
1946    }
1947
1948    /// Returns the round associated with the finalized proposal.
1949    pub const fn round(&self) -> Round {
1950        self.proposal.round
1951    }
1952}
1953
1954impl<S: Scheme, D: Digest> PartialEq for Finalization<S, D> {
1955    fn eq(&self, other: &Self) -> bool {
1956        self.proposal == other.proposal && self.certificate == other.certificate
1957    }
1958}
1959
1960impl<S: Scheme, D: Digest> Eq for Finalization<S, D> {}
1961
1962impl<S: Scheme, D: Digest> Hash for Finalization<S, D> {
1963    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1964        self.proposal.hash(state);
1965        self.certificate.hash(state);
1966    }
1967}
1968
1969impl<S: Scheme, D: Digest> Write for Finalization<S, D> {
1970    fn write(&self, writer: &mut impl BufMut) {
1971        self.proposal.write(writer);
1972        self.certificate.write(writer);
1973    }
1974}
1975
1976impl<S: Scheme, D: Digest> EncodeSize for Finalization<S, D> {
1977    fn encode_size(&self) -> usize {
1978        self.proposal.encode_size() + self.certificate.encode_size()
1979    }
1980}
1981
1982impl<S: Scheme, D: Digest> Read for Finalization<S, D> {
1983    type Cfg = <S::Certificate as Read>::Cfg;
1984
1985    fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
1986        let proposal = Proposal::read(reader)?;
1987        let certificate = S::Certificate::read_cfg(reader, cfg)?;
1988
1989        Ok(Self {
1990            proposal,
1991            certificate,
1992        })
1993    }
1994}
1995
1996impl<S: Scheme, D: Digest> Epochable for Finalization<S, D> {
1997    fn epoch(&self) -> Epoch {
1998        self.proposal.epoch()
1999    }
2000}
2001
2002impl<S: Scheme, D: Digest> Viewable for Finalization<S, D> {
2003    fn view(&self) -> View {
2004        self.proposal.view()
2005    }
2006}
2007
2008#[cfg(feature = "arbitrary")]
2009impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Finalization<S, D>
2010where
2011    S::Certificate: for<'a> arbitrary::Arbitrary<'a>,
2012    D: for<'a> arbitrary::Arbitrary<'a>,
2013{
2014    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
2015        let proposal = Proposal::arbitrary(u)?;
2016        let certificate = S::Certificate::arbitrary(u)?;
2017        Ok(Self {
2018            proposal,
2019            certificate,
2020        })
2021    }
2022}
2023
2024/// Backfiller is a message type for requesting and receiving missing consensus artifacts.
2025/// This is used to synchronize validators that have fallen behind or just joined the network.
2026#[derive(Clone, Debug, PartialEq)]
2027pub enum Backfiller<S: Scheme, D: Digest> {
2028    /// Request for missing notarizations and nullifications
2029    Request(Request),
2030    /// Response containing requested notarizations and nullifications
2031    Response(Response<S, D>),
2032}
2033
2034impl<S: Scheme, D: Digest> Write for Backfiller<S, D> {
2035    fn write(&self, writer: &mut impl BufMut) {
2036        match self {
2037            Self::Request(request) => {
2038                0u8.write(writer);
2039                request.write(writer);
2040            }
2041            Self::Response(response) => {
2042                1u8.write(writer);
2043                response.write(writer);
2044            }
2045        }
2046    }
2047}
2048
2049impl<S: Scheme, D: Digest> EncodeSize for Backfiller<S, D> {
2050    fn encode_size(&self) -> usize {
2051        1 + match self {
2052            Self::Request(v) => v.encode_size(),
2053            Self::Response(v) => v.encode_size(),
2054        }
2055    }
2056}
2057
2058impl<S: Scheme, D: Digest> Read for Backfiller<S, D> {
2059    type Cfg = (usize, <S::Certificate as Read>::Cfg);
2060
2061    fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
2062        let tag = <u8>::read(reader)?;
2063        match tag {
2064            0 => {
2065                let (max_len, _) = cfg;
2066                let v = Request::read_cfg(reader, max_len)?;
2067                Ok(Self::Request(v))
2068            }
2069            1 => {
2070                let v = Response::<S, D>::read_cfg(reader, cfg)?;
2071                Ok(Self::Response(v))
2072            }
2073            _ => Err(Error::Invalid(
2074                "consensus::simplex::Backfiller",
2075                "Invalid type",
2076            )),
2077        }
2078    }
2079}
2080
2081#[cfg(feature = "arbitrary")]
2082impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Backfiller<S, D>
2083where
2084    S::Certificate: for<'a> arbitrary::Arbitrary<'a>,
2085    D: for<'a> arbitrary::Arbitrary<'a>,
2086{
2087    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
2088        let tag = u.int_in_range(0..=1)?;
2089        match tag {
2090            0 => {
2091                let v = Request::arbitrary(u)?;
2092                Ok(Self::Request(v))
2093            }
2094            1 => {
2095                let v = Response::<S, D>::arbitrary(u)?;
2096                Ok(Self::Response(v))
2097            }
2098            _ => unreachable!(),
2099        }
2100    }
2101}
2102
2103/// Request is a message to request missing notarizations and nullifications.
2104/// This is used by validators who need to catch up with the consensus state.
2105#[derive(Clone, Debug, PartialEq)]
2106#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2107pub struct Request {
2108    /// Unique identifier for this request (used to match responses)
2109    pub id: u64,
2110    /// Views for which notarizations are requested
2111    pub notarizations: Vec<View>,
2112    /// Views for which nullifications are requested
2113    pub nullifications: Vec<View>,
2114}
2115
2116impl Request {
2117    /// Creates a new request for missing notarizations and nullifications.
2118    pub const fn new(id: u64, notarizations: Vec<View>, nullifications: Vec<View>) -> Self {
2119        Self {
2120            id,
2121            notarizations,
2122            nullifications,
2123        }
2124    }
2125}
2126
2127impl Write for Request {
2128    fn write(&self, writer: &mut impl BufMut) {
2129        UInt(self.id).write(writer);
2130        self.notarizations.write(writer);
2131        self.nullifications.write(writer);
2132    }
2133}
2134
2135impl EncodeSize for Request {
2136    fn encode_size(&self) -> usize {
2137        UInt(self.id).encode_size()
2138            + self.notarizations.encode_size()
2139            + self.nullifications.encode_size()
2140    }
2141}
2142
2143impl Read for Request {
2144    type Cfg = usize;
2145
2146    fn read_cfg(reader: &mut impl Buf, max_len: &usize) -> Result<Self, Error> {
2147        let id = UInt::read(reader)?.into();
2148        let mut views = HashSet::new();
2149        let notarizations = Vec::<View>::read_range(reader, ..=*max_len)?;
2150        for view in notarizations.iter() {
2151            if !views.insert(view) {
2152                return Err(Error::Invalid(
2153                    "consensus::simplex::Request",
2154                    "Duplicate notarization",
2155                ));
2156            }
2157        }
2158        let remaining = max_len - notarizations.len();
2159        views.clear();
2160        let nullifications = Vec::<View>::read_range(reader, ..=remaining)?;
2161        for view in nullifications.iter() {
2162            if !views.insert(view) {
2163                return Err(Error::Invalid(
2164                    "consensus::simplex::Request",
2165                    "Duplicate nullification",
2166                ));
2167            }
2168        }
2169        Ok(Self {
2170            id,
2171            notarizations,
2172            nullifications,
2173        })
2174    }
2175}
2176
2177/// Response is a message containing the requested notarizations and nullifications.
2178/// This is sent in response to a Request message.
2179#[derive(Clone, Debug, PartialEq)]
2180pub struct Response<S: Scheme, D: Digest> {
2181    /// Identifier matching the original request
2182    pub id: u64,
2183    /// Notarizations for the requested views
2184    pub notarizations: Vec<Notarization<S, D>>,
2185    /// Nullifications for the requested views
2186    pub nullifications: Vec<Nullification<S>>,
2187}
2188
2189impl<S: Scheme, D: Digest> Response<S, D> {
2190    /// Creates a new response with the given id, notarizations, and nullifications.
2191    pub const fn new(
2192        id: u64,
2193        notarizations: Vec<Notarization<S, D>>,
2194        nullifications: Vec<Nullification<S>>,
2195    ) -> Self {
2196        Self {
2197            id,
2198            notarizations,
2199            nullifications,
2200        }
2201    }
2202
2203    /// Verifies the certificates contained in this response against the signing scheme.
2204    pub fn verify<R: CryptoRng>(&self, rng: &mut R, scheme: &S, strategy: &impl Strategy) -> bool
2205    where
2206        S: scheme::Scheme<D>,
2207    {
2208        // An honest peer may have no certificate for the requested views. Treating the empty
2209        // response as valid lets the requester immediately try another peer.
2210        if self.notarizations.is_empty() && self.nullifications.is_empty() {
2211            return true;
2212        }
2213
2214        let notarizations = self.notarizations.iter().map(|notarization| {
2215            let context = Subject::Notarize {
2216                proposal: &notarization.proposal,
2217            };
2218
2219            (context, &notarization.certificate)
2220        });
2221
2222        let nullifications = self.nullifications.iter().map(|nullification| {
2223            let context = Subject::Nullify {
2224                round: nullification.round,
2225            };
2226
2227            (context, &nullification.certificate)
2228        });
2229
2230        let certificates = NonEmpty::try_new(notarizations.chain(nullifications))
2231            .expect("non-empty response must contain a certificate");
2232
2233        scheme.verify_certificates::<_, D, _>(rng, certificates, strategy)
2234    }
2235}
2236
2237impl<S: Scheme, D: Digest> Write for Response<S, D> {
2238    fn write(&self, writer: &mut impl BufMut) {
2239        UInt(self.id).write(writer);
2240        self.notarizations.write(writer);
2241        self.nullifications.write(writer);
2242    }
2243}
2244
2245impl<S: Scheme, D: Digest> EncodeSize for Response<S, D> {
2246    fn encode_size(&self) -> usize {
2247        UInt(self.id).encode_size()
2248            + self.notarizations.encode_size()
2249            + self.nullifications.encode_size()
2250    }
2251}
2252
2253impl<S: Scheme, D: Digest> Read for Response<S, D> {
2254    type Cfg = (usize, <S::Certificate as Read>::Cfg);
2255
2256    fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
2257        let (max_len, certificate_cfg) = cfg;
2258        let id = UInt::read(reader)?.into();
2259        let mut views = HashSet::new();
2260        let notarizations = Vec::<Notarization<S, D>>::read_cfg(
2261            reader,
2262            &((..=*max_len).into(), certificate_cfg.clone()),
2263        )?;
2264        for notarization in notarizations.iter() {
2265            if !views.insert(notarization.view()) {
2266                return Err(Error::Invalid(
2267                    "consensus::simplex::Response",
2268                    "Duplicate notarization",
2269                ));
2270            }
2271        }
2272        let remaining = max_len - notarizations.len();
2273        views.clear();
2274        let nullifications = Vec::<Nullification<S>>::read_cfg(
2275            reader,
2276            &((..=remaining).into(), certificate_cfg.clone()),
2277        )?;
2278        for nullification in nullifications.iter() {
2279            if !views.insert(nullification.view()) {
2280                return Err(Error::Invalid(
2281                    "consensus::simplex::Response",
2282                    "Duplicate nullification",
2283                ));
2284            }
2285        }
2286        Ok(Self {
2287            id,
2288            notarizations,
2289            nullifications,
2290        })
2291    }
2292}
2293
2294#[cfg(feature = "arbitrary")]
2295impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Response<S, D>
2296where
2297    S::Certificate: for<'a> arbitrary::Arbitrary<'a>,
2298    D: for<'a> arbitrary::Arbitrary<'a>,
2299{
2300    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
2301        let id = u.arbitrary()?;
2302        let notarizations = u.arbitrary()?;
2303        let nullifications = u.arbitrary()?;
2304        Ok(Self {
2305            id,
2306            notarizations,
2307            nullifications,
2308        })
2309    }
2310}
2311
2312/// Activity represents all possible activities that can occur in the consensus protocol.
2313/// This includes both regular consensus messages and fault evidence.
2314///
2315/// # Verification
2316///
2317/// Some activities issued by consensus are not guaranteed to be cryptographically verified (i.e. if not needed
2318/// to produce a minimum quorum certificate). Use [`Activity::verified`] to check if an activity may not be verified,
2319/// and [`Activity::verify`] to perform verification.
2320///
2321/// # Activity Filtering
2322///
2323/// For **non-attributable** schemes like [`crate::simplex::scheme::bls12381_threshold`], exposing
2324/// per-validator activity as fault evidence is not safe: with threshold cryptography, any `t` valid partial signatures can
2325/// be used to forge a partial signature for any player.
2326///
2327/// Use [`crate::simplex::scheme::reporter::AttributableReporter`] to automatically filter and
2328/// verify activities based on [`Scheme::is_attributable`].
2329#[derive(Clone, Debug)]
2330pub enum Activity<S: Scheme, D: Digest> {
2331    /// A validator's notarize vote over a proposal.
2332    Notarize(Notarize<S, D>),
2333    /// A recovered certificate for a notarization (scheme-specific).
2334    Notarization(Notarization<S, D>),
2335    /// A notarization was locally certified.
2336    Certification(Notarization<S, D>),
2337    /// A validator's nullify vote used to skip the current view.
2338    Nullify(Nullify<S>),
2339    /// A recovered certificate for a nullification (scheme-specific).
2340    Nullification(Nullification<S>),
2341    /// A validator's finalize vote over a proposal.
2342    Finalize(Finalize<S, D>),
2343    /// A recovered certificate for a finalization (scheme-specific).
2344    Finalization(Finalization<S, D>),
2345    /// Evidence of a validator sending conflicting notarizes (Byzantine behavior).
2346    ConflictingNotarize(ConflictingNotarize<S, D>),
2347    /// Evidence of a validator sending conflicting finalizes (Byzantine behavior).
2348    ConflictingFinalize(ConflictingFinalize<S, D>),
2349    /// Evidence of a validator sending both nullify and finalize for the same view (Byzantine behavior).
2350    NullifyFinalize(NullifyFinalize<S, D>),
2351}
2352
2353impl<S: Scheme, D: Digest> PartialEq for Activity<S, D> {
2354    fn eq(&self, other: &Self) -> bool {
2355        match (self, other) {
2356            (Self::Notarize(a), Self::Notarize(b)) => a == b,
2357            (Self::Notarization(a), Self::Notarization(b)) => a == b,
2358            (Self::Certification(a), Self::Certification(b)) => a == b,
2359            (Self::Nullify(a), Self::Nullify(b)) => a == b,
2360            (Self::Nullification(a), Self::Nullification(b)) => a == b,
2361            (Self::Finalize(a), Self::Finalize(b)) => a == b,
2362            (Self::Finalization(a), Self::Finalization(b)) => a == b,
2363            (Self::ConflictingNotarize(a), Self::ConflictingNotarize(b)) => a == b,
2364            (Self::ConflictingFinalize(a), Self::ConflictingFinalize(b)) => a == b,
2365            (Self::NullifyFinalize(a), Self::NullifyFinalize(b)) => a == b,
2366            _ => false,
2367        }
2368    }
2369}
2370
2371impl<S: Scheme, D: Digest> Eq for Activity<S, D> {}
2372
2373impl<S: Scheme, D: Digest> Hash for Activity<S, D> {
2374    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2375        match self {
2376            Self::Notarize(v) => {
2377                0u8.hash(state);
2378                v.hash(state);
2379            }
2380            Self::Notarization(v) => {
2381                1u8.hash(state);
2382                v.hash(state);
2383            }
2384            Self::Certification(v) => {
2385                2u8.hash(state);
2386                v.hash(state);
2387            }
2388            Self::Nullify(v) => {
2389                3u8.hash(state);
2390                v.hash(state);
2391            }
2392            Self::Nullification(v) => {
2393                4u8.hash(state);
2394                v.hash(state);
2395            }
2396            Self::Finalize(v) => {
2397                5u8.hash(state);
2398                v.hash(state);
2399            }
2400            Self::Finalization(v) => {
2401                6u8.hash(state);
2402                v.hash(state);
2403            }
2404            Self::ConflictingNotarize(v) => {
2405                7u8.hash(state);
2406                v.hash(state);
2407            }
2408            Self::ConflictingFinalize(v) => {
2409                8u8.hash(state);
2410                v.hash(state);
2411            }
2412            Self::NullifyFinalize(v) => {
2413                9u8.hash(state);
2414                v.hash(state);
2415            }
2416        }
2417    }
2418}
2419
2420impl<S: Scheme, D: Digest> Activity<S, D> {
2421    /// Indicates whether the activity is guaranteed to have been verified by consensus.
2422    pub const fn verified(&self) -> bool {
2423        match self {
2424            Self::Notarize(_) => false,
2425            Self::Notarization(_) => true,
2426            Self::Certification(_) => false,
2427            Self::Nullify(_) => false,
2428            Self::Nullification(_) => true,
2429            Self::Finalize(_) => false,
2430            Self::Finalization(_) => true,
2431            Self::ConflictingNotarize(_) => false,
2432            Self::ConflictingFinalize(_) => false,
2433            Self::NullifyFinalize(_) => false,
2434        }
2435    }
2436
2437    /// Verifies the validity of this activity against the signing scheme.
2438    ///
2439    /// This method **always** performs verification regardless of whether the activity has been
2440    /// previously verified. Callers can use [`Activity::verified`] to check if verification is
2441    /// necessary before calling this method.
2442    pub fn verify<R: CryptoRng>(&self, rng: &mut R, scheme: &S, strategy: &impl Strategy) -> bool
2443    where
2444        S: scheme::Scheme<D>,
2445    {
2446        match self {
2447            Self::Notarize(n) => n.verify(rng, scheme, strategy),
2448            Self::Notarization(n) => n.verify(rng, scheme, strategy),
2449            Self::Certification(n) => n.verify(rng, scheme, strategy),
2450            Self::Nullify(n) => n.verify(rng, scheme, strategy),
2451            Self::Nullification(n) => n.verify(rng, scheme, strategy),
2452            Self::Finalize(f) => f.verify(rng, scheme, strategy),
2453            Self::Finalization(f) => f.verify(rng, scheme, strategy),
2454            Self::ConflictingNotarize(c) => c.verify(rng, scheme, strategy),
2455            Self::ConflictingFinalize(c) => c.verify(rng, scheme, strategy),
2456            Self::NullifyFinalize(c) => c.verify(rng, scheme, strategy),
2457        }
2458    }
2459}
2460
2461impl<S: Scheme, D: Digest> Write for Activity<S, D> {
2462    fn write(&self, writer: &mut impl BufMut) {
2463        match self {
2464            Self::Notarize(v) => {
2465                0u8.write(writer);
2466                v.write(writer);
2467            }
2468            Self::Notarization(v) => {
2469                1u8.write(writer);
2470                v.write(writer);
2471            }
2472            Self::Certification(v) => {
2473                2u8.write(writer);
2474                v.write(writer);
2475            }
2476            Self::Nullify(v) => {
2477                3u8.write(writer);
2478                v.write(writer);
2479            }
2480            Self::Nullification(v) => {
2481                4u8.write(writer);
2482                v.write(writer);
2483            }
2484            Self::Finalize(v) => {
2485                5u8.write(writer);
2486                v.write(writer);
2487            }
2488            Self::Finalization(v) => {
2489                6u8.write(writer);
2490                v.write(writer);
2491            }
2492            Self::ConflictingNotarize(v) => {
2493                7u8.write(writer);
2494                v.write(writer);
2495            }
2496            Self::ConflictingFinalize(v) => {
2497                8u8.write(writer);
2498                v.write(writer);
2499            }
2500            Self::NullifyFinalize(v) => {
2501                9u8.write(writer);
2502                v.write(writer);
2503            }
2504        }
2505    }
2506}
2507
2508impl<S: Scheme, D: Digest> EncodeSize for Activity<S, D> {
2509    fn encode_size(&self) -> usize {
2510        1 + match self {
2511            Self::Notarize(v) => v.encode_size(),
2512            Self::Notarization(v) => v.encode_size(),
2513            Self::Certification(v) => v.encode_size(),
2514            Self::Nullify(v) => v.encode_size(),
2515            Self::Nullification(v) => v.encode_size(),
2516            Self::Finalize(v) => v.encode_size(),
2517            Self::Finalization(v) => v.encode_size(),
2518            Self::ConflictingNotarize(v) => v.encode_size(),
2519            Self::ConflictingFinalize(v) => v.encode_size(),
2520            Self::NullifyFinalize(v) => v.encode_size(),
2521        }
2522    }
2523}
2524
2525impl<S: Scheme, D: Digest> Read for Activity<S, D> {
2526    type Cfg = <S::Certificate as Read>::Cfg;
2527
2528    fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
2529        let tag = <u8>::read(reader)?;
2530        match tag {
2531            0 => {
2532                let v = Notarize::<S, D>::read(reader)?;
2533                Ok(Self::Notarize(v))
2534            }
2535            1 => {
2536                let v = Notarization::<S, D>::read_cfg(reader, cfg)?;
2537                Ok(Self::Notarization(v))
2538            }
2539            2 => {
2540                let v = Notarization::<S, D>::read_cfg(reader, cfg)?;
2541                Ok(Self::Certification(v))
2542            }
2543            3 => {
2544                let v = Nullify::<S>::read(reader)?;
2545                Ok(Self::Nullify(v))
2546            }
2547            4 => {
2548                let v = Nullification::<S>::read_cfg(reader, cfg)?;
2549                Ok(Self::Nullification(v))
2550            }
2551            5 => {
2552                let v = Finalize::<S, D>::read(reader)?;
2553                Ok(Self::Finalize(v))
2554            }
2555            6 => {
2556                let v = Finalization::<S, D>::read_cfg(reader, cfg)?;
2557                Ok(Self::Finalization(v))
2558            }
2559            7 => {
2560                let v = ConflictingNotarize::<S, D>::read(reader)?;
2561                Ok(Self::ConflictingNotarize(v))
2562            }
2563            8 => {
2564                let v = ConflictingFinalize::<S, D>::read(reader)?;
2565                Ok(Self::ConflictingFinalize(v))
2566            }
2567            9 => {
2568                let v = NullifyFinalize::<S, D>::read(reader)?;
2569                Ok(Self::NullifyFinalize(v))
2570            }
2571            _ => Err(Error::Invalid(
2572                "consensus::simplex::Activity",
2573                "Invalid type",
2574            )),
2575        }
2576    }
2577}
2578
2579impl<S: Scheme, D: Digest> Epochable for Activity<S, D> {
2580    fn epoch(&self) -> Epoch {
2581        match self {
2582            Self::Notarize(v) => v.epoch(),
2583            Self::Notarization(v) => v.epoch(),
2584            Self::Certification(v) => v.epoch(),
2585            Self::Nullify(v) => v.epoch(),
2586            Self::Nullification(v) => v.epoch(),
2587            Self::Finalize(v) => v.epoch(),
2588            Self::Finalization(v) => v.epoch(),
2589            Self::ConflictingNotarize(v) => v.epoch(),
2590            Self::ConflictingFinalize(v) => v.epoch(),
2591            Self::NullifyFinalize(v) => v.epoch(),
2592        }
2593    }
2594}
2595
2596impl<S: Scheme, D: Digest> Viewable for Activity<S, D> {
2597    fn view(&self) -> View {
2598        match self {
2599            Self::Notarize(v) => v.view(),
2600            Self::Notarization(v) => v.view(),
2601            Self::Certification(v) => v.view(),
2602            Self::Nullify(v) => v.view(),
2603            Self::Nullification(v) => v.view(),
2604            Self::Finalize(v) => v.view(),
2605            Self::Finalization(v) => v.view(),
2606            Self::ConflictingNotarize(v) => v.view(),
2607            Self::ConflictingFinalize(v) => v.view(),
2608            Self::NullifyFinalize(v) => v.view(),
2609        }
2610    }
2611}
2612
2613#[cfg(feature = "arbitrary")]
2614impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for Activity<S, D>
2615where
2616    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
2617    S::Certificate: for<'a> arbitrary::Arbitrary<'a>,
2618    D: for<'a> arbitrary::Arbitrary<'a>,
2619{
2620    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
2621        let tag = u.int_in_range(0..=9)?;
2622        match tag {
2623            0 => {
2624                let v = Notarize::<S, D>::arbitrary(u)?;
2625                Ok(Self::Notarize(v))
2626            }
2627            1 => {
2628                let v = Notarization::<S, D>::arbitrary(u)?;
2629                Ok(Self::Notarization(v))
2630            }
2631            2 => {
2632                let v = Notarization::<S, D>::arbitrary(u)?;
2633                Ok(Self::Certification(v))
2634            }
2635            3 => {
2636                let v = Nullify::<S>::arbitrary(u)?;
2637                Ok(Self::Nullify(v))
2638            }
2639            4 => {
2640                let v = Nullification::<S>::arbitrary(u)?;
2641                Ok(Self::Nullification(v))
2642            }
2643            5 => {
2644                let v = Finalize::<S, D>::arbitrary(u)?;
2645                Ok(Self::Finalize(v))
2646            }
2647            6 => {
2648                let v = Finalization::<S, D>::arbitrary(u)?;
2649                Ok(Self::Finalization(v))
2650            }
2651            7 => {
2652                let v = ConflictingNotarize::<S, D>::arbitrary(u)?;
2653                Ok(Self::ConflictingNotarize(v))
2654            }
2655            8 => {
2656                let v = ConflictingFinalize::<S, D>::arbitrary(u)?;
2657                Ok(Self::ConflictingFinalize(v))
2658            }
2659            9 => {
2660                let v = NullifyFinalize::<S, D>::arbitrary(u)?;
2661                Ok(Self::NullifyFinalize(v))
2662            }
2663            _ => unreachable!(),
2664        }
2665    }
2666}
2667
2668/// ConflictingNotarize represents evidence of a Byzantine validator sending conflicting notarizes.
2669/// This is used to prove that a validator has equivocated (voted for different proposals in the same view).
2670#[derive(Clone, Debug)]
2671pub struct ConflictingNotarize<S: Scheme, D: Digest> {
2672    /// The first conflicting notarize
2673    notarize_1: Notarize<S, D>,
2674    /// The second conflicting notarize
2675    notarize_2: Notarize<S, D>,
2676}
2677
2678impl<S: Scheme, D: Digest> PartialEq for ConflictingNotarize<S, D> {
2679    fn eq(&self, other: &Self) -> bool {
2680        self.notarize_1 == other.notarize_1 && self.notarize_2 == other.notarize_2
2681    }
2682}
2683
2684impl<S: Scheme, D: Digest> Eq for ConflictingNotarize<S, D> {}
2685
2686impl<S: Scheme, D: Digest> Hash for ConflictingNotarize<S, D> {
2687    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2688        self.notarize_1.hash(state);
2689        self.notarize_2.hash(state);
2690    }
2691}
2692
2693impl<S: Scheme, D: Digest> ConflictingNotarize<S, D> {
2694    /// Creates a new conflicting notarize evidence from two conflicting notarizes.
2695    ///
2696    /// # Panics
2697    ///
2698    /// Panics if the two notarizes do not have the same round and signer, or if they
2699    /// have identical proposals (which would not constitute conflicting evidence).
2700    pub fn new(notarize_1: Notarize<S, D>, notarize_2: Notarize<S, D>) -> Self {
2701        assert_eq!(notarize_1.round(), notarize_2.round());
2702        assert_eq!(notarize_1.signer(), notarize_2.signer());
2703        assert_ne!(
2704            notarize_1.proposal, notarize_2.proposal,
2705            "proposals must differ to constitute conflicting evidence"
2706        );
2707
2708        Self {
2709            notarize_1,
2710            notarize_2,
2711        }
2712    }
2713
2714    /// Verifies that both conflicting signatures are valid, proving Byzantine behavior.
2715    pub fn verify<R>(&self, rng: &mut R, scheme: &S, strategy: &impl Strategy) -> bool
2716    where
2717        R: CryptoRng,
2718        S: scheme::Scheme<D>,
2719    {
2720        self.notarize_1.verify(rng, scheme, strategy)
2721            && self.notarize_2.verify(rng, scheme, strategy)
2722    }
2723}
2724
2725impl<S: Scheme, D: Digest> Attributable for ConflictingNotarize<S, D> {
2726    fn signer(&self) -> Participant {
2727        self.notarize_1.signer()
2728    }
2729}
2730
2731impl<S: Scheme, D: Digest> Epochable for ConflictingNotarize<S, D> {
2732    fn epoch(&self) -> Epoch {
2733        self.notarize_1.epoch()
2734    }
2735}
2736
2737impl<S: Scheme, D: Digest> Viewable for ConflictingNotarize<S, D> {
2738    fn view(&self) -> View {
2739        self.notarize_1.view()
2740    }
2741}
2742
2743impl<S: Scheme, D: Digest> Write for ConflictingNotarize<S, D> {
2744    fn write(&self, writer: &mut impl BufMut) {
2745        self.notarize_1.write(writer);
2746        self.notarize_2.write(writer);
2747    }
2748}
2749
2750impl<S: Scheme, D: Digest> Read for ConflictingNotarize<S, D> {
2751    type Cfg = ();
2752
2753    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
2754        let notarize_1 = Notarize::read(reader)?;
2755        let notarize_2 = Notarize::read(reader)?;
2756
2757        if notarize_1.signer() != notarize_2.signer()
2758            || notarize_1.round() != notarize_2.round()
2759            || notarize_1.proposal == notarize_2.proposal
2760        {
2761            return Err(Error::Invalid(
2762                "consensus::simplex::ConflictingNotarize",
2763                "invalid conflicting notarize",
2764            ));
2765        }
2766
2767        Ok(Self {
2768            notarize_1,
2769            notarize_2,
2770        })
2771    }
2772}
2773
2774impl<S: Scheme, D: Digest> EncodeSize for ConflictingNotarize<S, D> {
2775    fn encode_size(&self) -> usize {
2776        self.notarize_1.encode_size() + self.notarize_2.encode_size()
2777    }
2778}
2779
2780#[cfg(feature = "arbitrary")]
2781impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for ConflictingNotarize<S, D>
2782where
2783    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
2784    D: for<'a> arbitrary::Arbitrary<'a>,
2785{
2786    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
2787        let notarize_1 = Notarize::arbitrary(u)?;
2788        let notarize_2 = Notarize::arbitrary(u)?;
2789        Ok(Self {
2790            notarize_1,
2791            notarize_2,
2792        })
2793    }
2794}
2795
2796/// ConflictingFinalize represents evidence of a Byzantine validator sending conflicting finalizes.
2797/// Similar to ConflictingNotarize, but for finalizes.
2798#[derive(Clone, Debug)]
2799pub struct ConflictingFinalize<S: Scheme, D: Digest> {
2800    /// The second conflicting finalize
2801    finalize_1: Finalize<S, D>,
2802    /// The second conflicting finalize
2803    finalize_2: Finalize<S, D>,
2804}
2805
2806impl<S: Scheme, D: Digest> PartialEq for ConflictingFinalize<S, D> {
2807    fn eq(&self, other: &Self) -> bool {
2808        self.finalize_1 == other.finalize_1 && self.finalize_2 == other.finalize_2
2809    }
2810}
2811
2812impl<S: Scheme, D: Digest> Eq for ConflictingFinalize<S, D> {}
2813
2814impl<S: Scheme, D: Digest> Hash for ConflictingFinalize<S, D> {
2815    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2816        self.finalize_1.hash(state);
2817        self.finalize_2.hash(state);
2818    }
2819}
2820
2821impl<S: Scheme, D: Digest> ConflictingFinalize<S, D> {
2822    /// Creates a new conflicting finalize evidence from two conflicting finalizes.
2823    ///
2824    /// # Panics
2825    ///
2826    /// Panics if the two finalizes do not have the same round and signer, or if they
2827    /// have identical proposals (which would not constitute conflicting evidence).
2828    pub fn new(finalize_1: Finalize<S, D>, finalize_2: Finalize<S, D>) -> Self {
2829        assert_eq!(finalize_1.round(), finalize_2.round());
2830        assert_eq!(finalize_1.signer(), finalize_2.signer());
2831        assert_ne!(
2832            finalize_1.proposal, finalize_2.proposal,
2833            "proposals must differ to constitute conflicting evidence"
2834        );
2835
2836        Self {
2837            finalize_1,
2838            finalize_2,
2839        }
2840    }
2841
2842    /// Verifies that both conflicting signatures are valid, proving Byzantine behavior.
2843    pub fn verify<R>(&self, rng: &mut R, scheme: &S, strategy: &impl Strategy) -> bool
2844    where
2845        R: CryptoRng,
2846        S: scheme::Scheme<D>,
2847    {
2848        self.finalize_1.verify(rng, scheme, strategy)
2849            && self.finalize_2.verify(rng, scheme, strategy)
2850    }
2851}
2852
2853impl<S: Scheme, D: Digest> Attributable for ConflictingFinalize<S, D> {
2854    fn signer(&self) -> Participant {
2855        self.finalize_1.signer()
2856    }
2857}
2858
2859impl<S: Scheme, D: Digest> Epochable for ConflictingFinalize<S, D> {
2860    fn epoch(&self) -> Epoch {
2861        self.finalize_1.epoch()
2862    }
2863}
2864
2865impl<S: Scheme, D: Digest> Viewable for ConflictingFinalize<S, D> {
2866    fn view(&self) -> View {
2867        self.finalize_1.view()
2868    }
2869}
2870
2871impl<S: Scheme, D: Digest> Write for ConflictingFinalize<S, D> {
2872    fn write(&self, writer: &mut impl BufMut) {
2873        self.finalize_1.write(writer);
2874        self.finalize_2.write(writer);
2875    }
2876}
2877
2878impl<S: Scheme, D: Digest> Read for ConflictingFinalize<S, D> {
2879    type Cfg = ();
2880
2881    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
2882        let finalize_1 = Finalize::read(reader)?;
2883        let finalize_2 = Finalize::read(reader)?;
2884
2885        if finalize_1.signer() != finalize_2.signer()
2886            || finalize_1.round() != finalize_2.round()
2887            || finalize_1.proposal == finalize_2.proposal
2888        {
2889            return Err(Error::Invalid(
2890                "consensus::simplex::ConflictingFinalize",
2891                "invalid conflicting finalize",
2892            ));
2893        }
2894
2895        Ok(Self {
2896            finalize_1,
2897            finalize_2,
2898        })
2899    }
2900}
2901
2902impl<S: Scheme, D: Digest> EncodeSize for ConflictingFinalize<S, D> {
2903    fn encode_size(&self) -> usize {
2904        self.finalize_1.encode_size() + self.finalize_2.encode_size()
2905    }
2906}
2907
2908#[cfg(feature = "arbitrary")]
2909impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for ConflictingFinalize<S, D>
2910where
2911    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
2912    D: for<'a> arbitrary::Arbitrary<'a>,
2913{
2914    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
2915        let finalize_1 = Finalize::arbitrary(u)?;
2916        let finalize_2 = Finalize::arbitrary(u)?;
2917        Ok(Self {
2918            finalize_1,
2919            finalize_2,
2920        })
2921    }
2922}
2923
2924/// NullifyFinalize represents evidence of a Byzantine validator sending both a nullify and finalize
2925/// for the same view, which is contradictory behavior (a validator should either try to skip a view OR
2926/// finalize a proposal, not both).
2927#[derive(Clone, Debug)]
2928pub struct NullifyFinalize<S: Scheme, D: Digest> {
2929    /// The conflicting nullify
2930    nullify: Nullify<S>,
2931    /// The conflicting finalize
2932    finalize: Finalize<S, D>,
2933}
2934
2935impl<S: Scheme, D: Digest> PartialEq for NullifyFinalize<S, D> {
2936    fn eq(&self, other: &Self) -> bool {
2937        self.nullify == other.nullify && self.finalize == other.finalize
2938    }
2939}
2940
2941impl<S: Scheme, D: Digest> Eq for NullifyFinalize<S, D> {}
2942
2943impl<S: Scheme, D: Digest> Hash for NullifyFinalize<S, D> {
2944    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2945        self.nullify.hash(state);
2946        self.finalize.hash(state);
2947    }
2948}
2949
2950impl<S: Scheme, D: Digest> NullifyFinalize<S, D> {
2951    /// Creates a new nullify-finalize evidence from a nullify and a finalize.
2952    pub fn new(nullify: Nullify<S>, finalize: Finalize<S, D>) -> Self {
2953        assert_eq!(nullify.round, finalize.round());
2954        assert_eq!(nullify.signer(), finalize.signer());
2955
2956        Self { nullify, finalize }
2957    }
2958
2959    /// Verifies that both the nullify and finalize signatures are valid, proving Byzantine behavior.
2960    pub fn verify<R>(&self, rng: &mut R, scheme: &S, strategy: &impl Strategy) -> bool
2961    where
2962        R: CryptoRng,
2963        S: scheme::Scheme<D>,
2964    {
2965        self.nullify.verify(rng, scheme, strategy) && self.finalize.verify(rng, scheme, strategy)
2966    }
2967}
2968
2969impl<S: Scheme, D: Digest> Attributable for NullifyFinalize<S, D> {
2970    fn signer(&self) -> Participant {
2971        self.nullify.signer()
2972    }
2973}
2974
2975impl<S: Scheme, D: Digest> Epochable for NullifyFinalize<S, D> {
2976    fn epoch(&self) -> Epoch {
2977        self.nullify.epoch()
2978    }
2979}
2980
2981impl<S: Scheme, D: Digest> Viewable for NullifyFinalize<S, D> {
2982    fn view(&self) -> View {
2983        self.nullify.view()
2984    }
2985}
2986
2987impl<S: Scheme, D: Digest> Write for NullifyFinalize<S, D> {
2988    fn write(&self, writer: &mut impl BufMut) {
2989        self.nullify.write(writer);
2990        self.finalize.write(writer);
2991    }
2992}
2993
2994impl<S: Scheme, D: Digest> Read for NullifyFinalize<S, D> {
2995    type Cfg = ();
2996
2997    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
2998        let nullify = Nullify::read(reader)?;
2999        let finalize = Finalize::read(reader)?;
3000
3001        if nullify.signer() != finalize.signer() || nullify.round != finalize.round() {
3002            return Err(Error::Invalid(
3003                "consensus::simplex::NullifyFinalize",
3004                "mismatched signatures",
3005            ));
3006        }
3007
3008        Ok(Self { nullify, finalize })
3009    }
3010}
3011
3012impl<S: Scheme, D: Digest> EncodeSize for NullifyFinalize<S, D> {
3013    fn encode_size(&self) -> usize {
3014        self.nullify.encode_size() + self.finalize.encode_size()
3015    }
3016}
3017
3018#[cfg(feature = "arbitrary")]
3019impl<S: Scheme, D: Digest> arbitrary::Arbitrary<'_> for NullifyFinalize<S, D>
3020where
3021    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
3022    D: for<'a> arbitrary::Arbitrary<'a>,
3023{
3024    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
3025        let nullify = Nullify::arbitrary(u)?;
3026        let finalize = Finalize::arbitrary(u)?;
3027        Ok(Self { nullify, finalize })
3028    }
3029}
3030
3031#[cfg(test)]
3032mod tests {
3033    use super::*;
3034    use crate::simplex::{
3035        quorum,
3036        scheme::{
3037            Scheme, bls12381_multisig,
3038            bls12381_threshold::{
3039                standard as bls12381_threshold_std, vrf as bls12381_threshold_vrf,
3040            },
3041            ed25519, secp256r1,
3042        },
3043    };
3044    use bytes::Bytes;
3045    use commonware_codec::{Decode, DecodeExt, Encode};
3046    use commonware_cryptography::{
3047        bls12381::primitives::variant::{MinPk, MinSig},
3048        certificate::mocks::Fixture,
3049        sha256::Digest as Sha256,
3050    };
3051    use commonware_parallel::Sequential;
3052    use commonware_utils::{Faults, N3f1, TestRng, test_rng};
3053
3054    const NAMESPACE: &[u8] = b"test";
3055
3056    // Helper function to create a sample digest
3057    fn sample_digest(v: u8) -> Sha256 {
3058        Sha256::from([v; 32]) // Simple fixed digest for testing
3059    }
3060
3061    /// Generate a fixture using the provided generator function with a specific seed.
3062    fn setup_seeded<S, F>(n: u32, seed: u64, fixture: F) -> Fixture<S>
3063    where
3064        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
3065    {
3066        setup_seeded_ns(n, seed, NAMESPACE, fixture)
3067    }
3068
3069    /// Generate a fixture using the provided generator function with a specific seed and namespace.
3070    fn setup_seeded_ns<S, F>(n: u32, seed: u64, namespace: &[u8], fixture: F) -> Fixture<S>
3071    where
3072        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
3073    {
3074        let mut rng = TestRng::new(seed);
3075        fixture(&mut rng, namespace, n)
3076    }
3077
3078    #[test]
3079    fn test_proposal_encode_decode() {
3080        let proposal = Proposal::new(
3081            Round::new(Epoch::new(0), View::new(10)),
3082            View::new(5),
3083            sample_digest(1),
3084        );
3085        let encoded = proposal.encode();
3086        let decoded = Proposal::<Sha256>::decode(encoded).unwrap();
3087        assert_eq!(proposal, decoded);
3088    }
3089
3090    fn notarize_encode_decode<S, F>(fixture: F)
3091    where
3092        S: Scheme<Sha256>,
3093        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
3094    {
3095        let mut rng = test_rng();
3096        let fixture = fixture(&mut rng, NAMESPACE, 5);
3097        let round = Round::new(Epoch::new(0), View::new(10));
3098        let proposal = Proposal::new(round, View::new(5), sample_digest(1));
3099        let notarize = Notarize::sign(&fixture.schemes[0], proposal).unwrap();
3100
3101        let encoded = notarize.encode();
3102        let decoded = Notarize::decode(encoded).unwrap();
3103
3104        assert_eq!(notarize, decoded);
3105        assert!(decoded.verify(&mut rng, &fixture.schemes[0], &Sequential));
3106    }
3107
3108    #[test]
3109    fn test_notarize_encode_decode() {
3110        notarize_encode_decode(ed25519::fixture);
3111        notarize_encode_decode(secp256r1::fixture);
3112        notarize_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
3113        notarize_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
3114        notarize_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
3115        notarize_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
3116        notarize_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
3117        notarize_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
3118    }
3119
3120    fn notarization_encode_decode<S, F>(fixture: F)
3121    where
3122        S: Scheme<Sha256>,
3123        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
3124    {
3125        let mut rng = test_rng();
3126        let fixture = fixture(&mut rng, NAMESPACE, 5);
3127        let proposal = Proposal::new(
3128            Round::new(Epoch::new(0), View::new(10)),
3129            View::new(5),
3130            sample_digest(1),
3131        );
3132        let notarizes: Vec<_> = fixture
3133            .schemes
3134            .iter()
3135            .map(|scheme| Notarize::sign(scheme, proposal.clone()).unwrap())
3136            .collect();
3137        let notarization =
3138            Notarization::from_notarizes(&fixture.schemes[0], non_empty![@&notarizes], &Sequential)
3139                .expect("quorum notarization");
3140        let encoded = notarization.encode();
3141        let cfg = fixture.schemes[0].certificate_codec_config();
3142        let decoded = Notarization::decode_cfg(encoded, &cfg).unwrap();
3143        assert_eq!(notarization, decoded);
3144        assert!(decoded.verify(&mut rng, &fixture.schemes[0], &Sequential));
3145    }
3146
3147    #[test]
3148    fn test_notarization_encode_decode() {
3149        notarization_encode_decode(ed25519::fixture);
3150        notarization_encode_decode(secp256r1::fixture);
3151        notarization_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
3152        notarization_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
3153        notarization_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
3154        notarization_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
3155        notarization_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
3156        notarization_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
3157    }
3158
3159    fn nullify_encode_decode<S, F>(fixture: F)
3160    where
3161        S: Scheme<Sha256>,
3162        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
3163    {
3164        let mut rng = test_rng();
3165        let fixture = fixture(&mut rng, NAMESPACE, 5);
3166        let round = Round::new(Epoch::new(0), View::new(10));
3167        let nullify = Nullify::sign::<Sha256>(&fixture.schemes[0], round).unwrap();
3168        let encoded = nullify.encode();
3169        let decoded = Nullify::decode(encoded).unwrap();
3170        assert_eq!(nullify, decoded);
3171        assert!(decoded.verify::<_, Sha256>(&mut rng, &fixture.schemes[0], &Sequential));
3172    }
3173
3174    #[test]
3175    fn test_nullify_encode_decode() {
3176        nullify_encode_decode(ed25519::fixture);
3177        nullify_encode_decode(secp256r1::fixture);
3178        nullify_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
3179        nullify_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
3180        nullify_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
3181        nullify_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
3182        nullify_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
3183        nullify_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
3184    }
3185
3186    fn nullification_encode_decode<S, F>(fixture: F)
3187    where
3188        S: Scheme<Sha256>,
3189        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
3190    {
3191        let mut rng = test_rng();
3192        let fixture = fixture(&mut rng, NAMESPACE, 5);
3193        let round = Round::new(Epoch::new(333), View::new(10));
3194        let nullifies: Vec<_> = fixture
3195            .schemes
3196            .iter()
3197            .map(|scheme| Nullify::sign::<Sha256>(scheme, round).unwrap())
3198            .collect();
3199        let nullification = Nullification::from_nullifies(
3200            &fixture.schemes[0],
3201            non_empty![@&nullifies],
3202            &Sequential,
3203        )
3204        .unwrap();
3205        let encoded = nullification.encode();
3206        let cfg = fixture.schemes[0].certificate_codec_config();
3207        let decoded = Nullification::decode_cfg(encoded, &cfg).unwrap();
3208        assert_eq!(nullification, decoded);
3209        assert!(decoded.verify::<_, Sha256>(&mut rng, &fixture.schemes[0], &Sequential));
3210    }
3211
3212    #[test]
3213    fn test_nullification_encode_decode() {
3214        nullification_encode_decode(ed25519::fixture);
3215        nullification_encode_decode(secp256r1::fixture);
3216        nullification_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
3217        nullification_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
3218        nullification_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
3219        nullification_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
3220        nullification_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
3221        nullification_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
3222    }
3223
3224    fn finalize_encode_decode<S, F>(fixture: F)
3225    where
3226        S: Scheme<Sha256>,
3227        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
3228    {
3229        let mut rng = test_rng();
3230        let fixture = fixture(&mut rng, NAMESPACE, 5);
3231        let round = Round::new(Epoch::new(0), View::new(10));
3232        let proposal = Proposal::new(round, View::new(5), sample_digest(1));
3233        let finalize = Finalize::sign(&fixture.schemes[0], proposal).unwrap();
3234        let encoded = finalize.encode();
3235        let decoded = Finalize::decode(encoded).unwrap();
3236        assert_eq!(finalize, decoded);
3237        assert!(decoded.verify(&mut rng, &fixture.schemes[0], &Sequential));
3238    }
3239
3240    #[test]
3241    fn test_finalize_encode_decode() {
3242        finalize_encode_decode(ed25519::fixture);
3243        finalize_encode_decode(secp256r1::fixture);
3244        finalize_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
3245        finalize_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
3246        finalize_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
3247        finalize_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
3248        finalize_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
3249        finalize_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
3250    }
3251
3252    fn finalization_encode_decode<S, F>(fixture: F)
3253    where
3254        S: Scheme<Sha256>,
3255        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
3256    {
3257        let mut rng = test_rng();
3258        let fixture = fixture(&mut rng, NAMESPACE, 5);
3259        let round = Round::new(Epoch::new(0), View::new(10));
3260        let proposal = Proposal::new(round, View::new(5), sample_digest(1));
3261        let finalizes: Vec<_> = fixture
3262            .schemes
3263            .iter()
3264            .map(|scheme| Finalize::sign(scheme, proposal.clone()).unwrap())
3265            .collect();
3266        let finalization =
3267            Finalization::from_finalizes(&fixture.schemes[0], non_empty![@&finalizes], &Sequential)
3268                .unwrap();
3269        let encoded = finalization.encode();
3270        let cfg = fixture.schemes[0].certificate_codec_config();
3271        let decoded = Finalization::decode_cfg(encoded, &cfg).unwrap();
3272        assert_eq!(finalization, decoded);
3273        assert!(decoded.verify(&mut rng, &fixture.schemes[0], &Sequential));
3274    }
3275
3276    #[test]
3277    fn test_finalization_encode_decode() {
3278        finalization_encode_decode(ed25519::fixture);
3279        finalization_encode_decode(secp256r1::fixture);
3280        finalization_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
3281        finalization_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
3282        finalization_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
3283        finalization_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
3284        finalization_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
3285        finalization_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
3286    }
3287
3288    fn backfiller_encode_decode<S, F>(fixture: F)
3289    where
3290        S: Scheme<Sha256>,
3291        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
3292    {
3293        let mut rng = test_rng();
3294        let fixture = fixture(&mut rng, NAMESPACE, 5);
3295        let cfg = fixture.schemes[0].certificate_codec_config();
3296        let request = Request::new(
3297            1,
3298            vec![View::new(10), View::new(11)],
3299            vec![View::new(12), View::new(13)],
3300        );
3301        let encoded_request = Backfiller::<S, Sha256>::Request(request.clone()).encode();
3302        let decoded_request =
3303            Backfiller::<S, Sha256>::decode_cfg(encoded_request, &(usize::MAX, cfg.clone()))
3304                .unwrap();
3305        assert!(matches!(decoded_request, Backfiller::Request(r) if r == request));
3306
3307        let round = Round::new(Epoch::new(0), View::new(10));
3308        let proposal = Proposal::new(round, View::new(5), sample_digest(1));
3309        let notarizes: Vec<_> = fixture
3310            .schemes
3311            .iter()
3312            .map(|scheme| Notarize::sign(scheme, proposal.clone()).unwrap())
3313            .collect();
3314        let notarization =
3315            Notarization::from_notarizes(&fixture.schemes[0], non_empty![@&notarizes], &Sequential)
3316                .expect("quorum notarization");
3317
3318        let nullifies: Vec<_> = fixture
3319            .schemes
3320            .iter()
3321            .map(|scheme| Nullify::sign::<Sha256>(scheme, round).unwrap())
3322            .collect();
3323        let nullification = Nullification::from_nullifies(
3324            &fixture.schemes[0],
3325            non_empty![@&nullifies],
3326            &Sequential,
3327        )
3328        .unwrap();
3329
3330        let response = Response::<S, Sha256>::new(1, vec![notarization], vec![nullification]);
3331        let encoded_response = Backfiller::<S, Sha256>::Response(response.clone()).encode();
3332        let decoded_response =
3333            Backfiller::<S, Sha256>::decode_cfg(encoded_response, &(usize::MAX, cfg)).unwrap();
3334        assert!(matches!(decoded_response, Backfiller::Response(r) if r.id == response.id));
3335    }
3336
3337    #[test]
3338    fn test_backfiller_encode_decode() {
3339        backfiller_encode_decode(ed25519::fixture);
3340        backfiller_encode_decode(secp256r1::fixture);
3341        backfiller_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
3342        backfiller_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
3343        backfiller_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
3344        backfiller_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
3345        backfiller_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
3346        backfiller_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
3347    }
3348
3349    #[test]
3350    fn test_request_encode_decode() {
3351        let request = Request::new(
3352            1,
3353            vec![View::new(10), View::new(11)],
3354            vec![View::new(12), View::new(13)],
3355        );
3356        let encoded = request.encode();
3357        let decoded = Request::decode_cfg(encoded, &usize::MAX).unwrap();
3358        assert_eq!(request, decoded);
3359    }
3360
3361    fn response_encode_decode<S, F>(fixture: F)
3362    where
3363        S: Scheme<Sha256>,
3364        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
3365    {
3366        let mut rng = test_rng();
3367        let fixture = fixture(&mut rng, NAMESPACE, 5);
3368        let round = Round::new(Epoch::new(0), View::new(10));
3369        let proposal = Proposal::new(round, View::new(5), sample_digest(1));
3370
3371        let notarizes: Vec<_> = fixture
3372            .schemes
3373            .iter()
3374            .map(|scheme| Notarize::sign(scheme, proposal.clone()).unwrap())
3375            .collect();
3376        let notarization =
3377            Notarization::from_notarizes(&fixture.schemes[0], non_empty![@&notarizes], &Sequential)
3378                .unwrap();
3379
3380        let nullifies: Vec<_> = fixture
3381            .schemes
3382            .iter()
3383            .map(|scheme| Nullify::sign::<Sha256>(scheme, round).unwrap())
3384            .collect();
3385        let nullification = Nullification::from_nullifies(
3386            &fixture.schemes[0],
3387            non_empty![@&nullifies],
3388            &Sequential,
3389        )
3390        .unwrap();
3391
3392        let response = Response::<S, Sha256>::new(1, vec![notarization], vec![nullification]);
3393        let cfg = fixture.schemes[0].certificate_codec_config();
3394        let mut decoded =
3395            Response::<S, Sha256>::decode_cfg(response.encode(), &(usize::MAX, cfg)).unwrap();
3396        assert_eq!(response.id, decoded.id);
3397        assert_eq!(response.notarizations.len(), decoded.notarizations.len());
3398        assert_eq!(response.nullifications.len(), decoded.nullifications.len());
3399
3400        assert!(decoded.verify(&mut rng, &fixture.schemes[0], &Sequential));
3401
3402        decoded.nullifications[0].round = Round::new(
3403            decoded.nullifications[0].round.epoch(),
3404            decoded.nullifications[0].round.view().next(),
3405        );
3406        assert!(!decoded.verify(&mut rng, &fixture.schemes[0], &Sequential));
3407    }
3408
3409    #[test]
3410    fn test_response_encode_decode() {
3411        response_encode_decode(ed25519::fixture);
3412        response_encode_decode(secp256r1::fixture);
3413        response_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
3414        response_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
3415        response_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
3416        response_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
3417        response_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
3418        response_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
3419    }
3420
3421    #[test]
3422    fn empty_response_is_valid() {
3423        let mut rng = test_rng();
3424        let fixture = ed25519::fixture(&mut rng, NAMESPACE, 5);
3425        let response = Response::<ed25519::Scheme, Sha256>::new(1, Vec::new(), Vec::new());
3426
3427        assert!(response.verify(&mut rng, &fixture.schemes[0], &Sequential));
3428    }
3429
3430    fn conflicting_notarize_encode_decode<S, F>(fixture: F)
3431    where
3432        S: Scheme<Sha256>,
3433        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
3434    {
3435        let mut rng = test_rng();
3436        let fixture = fixture(&mut rng, NAMESPACE, 5);
3437        let proposal1 = Proposal::new(
3438            Round::new(Epoch::new(0), View::new(10)),
3439            View::new(5),
3440            sample_digest(1),
3441        );
3442        let proposal2 = Proposal::new(
3443            Round::new(Epoch::new(0), View::new(10)),
3444            View::new(5),
3445            sample_digest(2),
3446        );
3447        let notarize1 = Notarize::sign(&fixture.schemes[0], proposal1).unwrap();
3448        let notarize2 = Notarize::sign(&fixture.schemes[0], proposal2).unwrap();
3449        let conflicting = ConflictingNotarize::new(notarize1, notarize2);
3450
3451        let encoded = conflicting.encode();
3452        let decoded = ConflictingNotarize::<S, Sha256>::decode(encoded).unwrap();
3453
3454        assert_eq!(conflicting, decoded);
3455        assert!(decoded.verify(&mut rng, &fixture.schemes[0], &Sequential));
3456    }
3457
3458    #[test]
3459    fn test_conflicting_notarize_encode_decode() {
3460        conflicting_notarize_encode_decode(ed25519::fixture);
3461        conflicting_notarize_encode_decode(secp256r1::fixture);
3462        conflicting_notarize_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
3463        conflicting_notarize_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
3464        conflicting_notarize_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
3465        conflicting_notarize_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
3466        conflicting_notarize_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
3467        conflicting_notarize_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
3468    }
3469
3470    fn conflicting_finalize_encode_decode<S, F>(fixture: F)
3471    where
3472        S: Scheme<Sha256>,
3473        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
3474    {
3475        let mut rng = test_rng();
3476        let fixture = fixture(&mut rng, NAMESPACE, 5);
3477        let proposal1 = Proposal::new(
3478            Round::new(Epoch::new(0), View::new(10)),
3479            View::new(5),
3480            sample_digest(1),
3481        );
3482        let proposal2 = Proposal::new(
3483            Round::new(Epoch::new(0), View::new(10)),
3484            View::new(5),
3485            sample_digest(2),
3486        );
3487        let finalize1 = Finalize::sign(&fixture.schemes[0], proposal1).unwrap();
3488        let finalize2 = Finalize::sign(&fixture.schemes[0], proposal2).unwrap();
3489        let conflicting = ConflictingFinalize::new(finalize1, finalize2);
3490
3491        let encoded = conflicting.encode();
3492        let decoded = ConflictingFinalize::<S, Sha256>::decode(encoded).unwrap();
3493
3494        assert_eq!(conflicting, decoded);
3495        assert!(decoded.verify(&mut rng, &fixture.schemes[0], &Sequential));
3496    }
3497
3498    #[test]
3499    fn test_conflicting_finalize_encode_decode() {
3500        conflicting_finalize_encode_decode(ed25519::fixture);
3501        conflicting_finalize_encode_decode(secp256r1::fixture);
3502        conflicting_finalize_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
3503        conflicting_finalize_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
3504        conflicting_finalize_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
3505        conflicting_finalize_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
3506        conflicting_finalize_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
3507        conflicting_finalize_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
3508    }
3509
3510    fn nullify_finalize_encode_decode<S, F>(fixture: F)
3511    where
3512        S: Scheme<Sha256>,
3513        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
3514    {
3515        let mut rng = test_rng();
3516        let fixture = fixture(&mut rng, NAMESPACE, 5);
3517        let round = Round::new(Epoch::new(0), View::new(10));
3518        let proposal = Proposal::new(round, View::new(5), sample_digest(1));
3519        let nullify = Nullify::sign::<Sha256>(&fixture.schemes[0], round).unwrap();
3520        let finalize = Finalize::sign(&fixture.schemes[0], proposal).unwrap();
3521        let conflict = NullifyFinalize::new(nullify, finalize);
3522
3523        let encoded = conflict.encode();
3524        let decoded = NullifyFinalize::<S, Sha256>::decode(encoded).unwrap();
3525
3526        assert_eq!(conflict, decoded);
3527        assert!(decoded.verify(&mut rng, &fixture.schemes[0], &Sequential));
3528    }
3529
3530    #[test]
3531    fn test_nullify_finalize_encode_decode() {
3532        nullify_finalize_encode_decode(ed25519::fixture);
3533        nullify_finalize_encode_decode(secp256r1::fixture);
3534        nullify_finalize_encode_decode(bls12381_multisig::fixture::<MinPk, _>);
3535        nullify_finalize_encode_decode(bls12381_multisig::fixture::<MinSig, _>);
3536        nullify_finalize_encode_decode(bls12381_threshold_vrf::fixture::<MinPk, _>);
3537        nullify_finalize_encode_decode(bls12381_threshold_vrf::fixture::<MinSig, _>);
3538        nullify_finalize_encode_decode(bls12381_threshold_std::fixture::<MinPk, _>);
3539        nullify_finalize_encode_decode(bls12381_threshold_std::fixture::<MinSig, _>);
3540    }
3541
3542    fn notarize_verify_wrong_namespace<S, F>(f: F)
3543    where
3544        S: Scheme<Sha256>,
3545        F: Fn(&mut TestRng, &[u8], u32) -> Fixture<S>,
3546    {
3547        // Create two fixtures with different namespaces
3548        let mut rng = test_rng();
3549        let fixture = setup_seeded_ns(5, 0, NAMESPACE, &f);
3550        let wrong_fixture = setup_seeded_ns(5, 0, b"wrong_namespace", &f);
3551        let round = Round::new(Epoch::new(0), View::new(10));
3552        let proposal = Proposal::new(round, View::new(5), sample_digest(1));
3553        let notarize = Notarize::sign(&fixture.schemes[0], proposal).unwrap();
3554
3555        assert!(notarize.verify(&mut rng, &fixture.schemes[0], &Sequential));
3556        assert!(!notarize.verify(&mut rng, &wrong_fixture.schemes[0], &Sequential));
3557    }
3558
3559    #[test]
3560    fn test_notarize_verify_wrong_namespace() {
3561        notarize_verify_wrong_namespace(ed25519::fixture);
3562        notarize_verify_wrong_namespace(secp256r1::fixture);
3563        notarize_verify_wrong_namespace(bls12381_multisig::fixture::<MinPk, _>);
3564        notarize_verify_wrong_namespace(bls12381_multisig::fixture::<MinSig, _>);
3565        notarize_verify_wrong_namespace(bls12381_threshold_vrf::fixture::<MinPk, _>);
3566        notarize_verify_wrong_namespace(bls12381_threshold_vrf::fixture::<MinSig, _>);
3567        notarize_verify_wrong_namespace(bls12381_threshold_std::fixture::<MinPk, _>);
3568        notarize_verify_wrong_namespace(bls12381_threshold_std::fixture::<MinSig, _>);
3569    }
3570
3571    fn notarize_verify_wrong_scheme<S, F>(f: F)
3572    where
3573        S: Scheme<Sha256>,
3574        F: Fn(&mut TestRng, &[u8], u32) -> Fixture<S>,
3575    {
3576        let mut rng = test_rng();
3577        let fixture = setup_seeded(5, 0, &f);
3578        let wrong_fixture = setup_seeded(5, 1, &f);
3579        let round = Round::new(Epoch::new(0), View::new(10));
3580        let proposal = Proposal::new(round, View::new(5), sample_digest(2));
3581        let notarize = Notarize::sign(&fixture.schemes[0], proposal).unwrap();
3582
3583        assert!(notarize.verify(&mut rng, &fixture.schemes[0], &Sequential));
3584        assert!(!notarize.verify(&mut rng, &wrong_fixture.verifier, &Sequential));
3585    }
3586
3587    #[test]
3588    fn test_notarize_verify_wrong_scheme() {
3589        notarize_verify_wrong_scheme(ed25519::fixture);
3590        notarize_verify_wrong_scheme(secp256r1::fixture);
3591        notarize_verify_wrong_scheme(bls12381_multisig::fixture::<MinPk, _>);
3592        notarize_verify_wrong_scheme(bls12381_multisig::fixture::<MinSig, _>);
3593        notarize_verify_wrong_scheme(bls12381_threshold_vrf::fixture::<MinPk, _>);
3594        notarize_verify_wrong_scheme(bls12381_threshold_vrf::fixture::<MinSig, _>);
3595        notarize_verify_wrong_scheme(bls12381_threshold_std::fixture::<MinPk, _>);
3596        notarize_verify_wrong_scheme(bls12381_threshold_std::fixture::<MinSig, _>);
3597    }
3598
3599    fn notarization_verify_wrong_scheme<S, F>(f: F)
3600    where
3601        S: Scheme<Sha256>,
3602        F: Fn(&mut TestRng, &[u8], u32) -> Fixture<S>,
3603    {
3604        let mut rng = test_rng();
3605        let fixture = setup_seeded(5, 0, &f);
3606        let wrong_fixture = setup_seeded(5, 1, &f);
3607        let round = Round::new(Epoch::new(0), View::new(10));
3608        let proposal = Proposal::new(round, View::new(5), sample_digest(3));
3609        let quorum = N3f1::quorum(fixture.schemes.len()) as usize;
3610        let notarizes: Vec<_> = fixture
3611            .schemes
3612            .iter()
3613            .take(quorum)
3614            .map(|scheme| Notarize::sign(scheme, proposal.clone()).unwrap())
3615            .collect();
3616
3617        let notarization =
3618            Notarization::from_notarizes(&fixture.schemes[0], non_empty![@&notarizes], &Sequential)
3619                .unwrap();
3620        assert!(notarization.verify(&mut rng, &fixture.schemes[0], &Sequential));
3621        assert!(!notarization.verify(&mut rng, &wrong_fixture.verifier, &Sequential));
3622    }
3623
3624    #[test]
3625    fn test_notarization_verify_wrong_scheme() {
3626        notarization_verify_wrong_scheme(ed25519::fixture);
3627        notarization_verify_wrong_scheme(secp256r1::fixture);
3628        notarization_verify_wrong_scheme(bls12381_multisig::fixture::<MinPk, _>);
3629        notarization_verify_wrong_scheme(bls12381_multisig::fixture::<MinSig, _>);
3630        notarization_verify_wrong_scheme(bls12381_threshold_vrf::fixture::<MinPk, _>);
3631        notarization_verify_wrong_scheme(bls12381_threshold_vrf::fixture::<MinSig, _>);
3632        notarization_verify_wrong_scheme(bls12381_threshold_std::fixture::<MinPk, _>);
3633        notarization_verify_wrong_scheme(bls12381_threshold_std::fixture::<MinSig, _>);
3634    }
3635
3636    fn notarization_verify_wrong_namespace<S, F>(f: F)
3637    where
3638        S: Scheme<Sha256>,
3639        F: Fn(&mut TestRng, &[u8], u32) -> Fixture<S>,
3640    {
3641        // Create two fixtures with different namespaces
3642        let fixture = setup_seeded_ns(5, 0, NAMESPACE, &f);
3643        let wrong_fixture = setup_seeded_ns(5, 0, b"wrong_namespace", &f);
3644        let mut rng = test_rng();
3645        let round = Round::new(Epoch::new(0), View::new(10));
3646        let proposal = Proposal::new(round, View::new(5), sample_digest(4));
3647        let quorum = N3f1::quorum(fixture.schemes.len()) as usize;
3648        let notarizes: Vec<_> = fixture
3649            .schemes
3650            .iter()
3651            .take(quorum)
3652            .map(|scheme| Notarize::sign(scheme, proposal.clone()).unwrap())
3653            .collect();
3654
3655        let notarization =
3656            Notarization::from_notarizes(&fixture.schemes[0], non_empty![@&notarizes], &Sequential)
3657                .unwrap();
3658        assert!(notarization.verify(&mut rng, &fixture.schemes[0], &Sequential));
3659
3660        assert!(!notarization.verify(&mut rng, &wrong_fixture.schemes[0], &Sequential));
3661    }
3662
3663    #[test]
3664    fn test_notarization_verify_wrong_namespace() {
3665        notarization_verify_wrong_namespace(ed25519::fixture);
3666        notarization_verify_wrong_namespace(secp256r1::fixture);
3667        notarization_verify_wrong_namespace(bls12381_multisig::fixture::<MinPk, _>);
3668        notarization_verify_wrong_namespace(bls12381_multisig::fixture::<MinSig, _>);
3669        notarization_verify_wrong_namespace(bls12381_threshold_vrf::fixture::<MinPk, _>);
3670        notarization_verify_wrong_namespace(bls12381_threshold_vrf::fixture::<MinSig, _>);
3671        notarization_verify_wrong_namespace(bls12381_threshold_std::fixture::<MinPk, _>);
3672        notarization_verify_wrong_namespace(bls12381_threshold_std::fixture::<MinSig, _>);
3673    }
3674
3675    fn notarization_recover_insufficient_signatures<S, F>(fixture: F)
3676    where
3677        S: Scheme<Sha256>,
3678        F: FnOnce(&mut TestRng, &[u8], u32) -> Fixture<S>,
3679    {
3680        let mut rng = test_rng();
3681        let fixture = fixture(&mut rng, NAMESPACE, 5);
3682        let participant_count =
3683            u32::try_from(fixture.schemes.len()).expect("participant count exceeds u32::MAX");
3684        let quorum_size = quorum(participant_count);
3685        let subquorum = usize::try_from(quorum_size - 1).expect("quorum exceeds usize::MAX");
3686        assert!(quorum_size > 1, "test requires quorum larger than one");
3687        let round = Round::new(Epoch::new(0), View::new(10));
3688        let proposal = Proposal::new(round, View::new(5), sample_digest(5));
3689        let notarizes: Vec<_> = fixture
3690            .schemes
3691            .iter()
3692            .take(subquorum)
3693            .map(|scheme| Notarize::sign(scheme, proposal.clone()).unwrap())
3694            .collect();
3695
3696        assert_eq!(
3697            Notarization::from_notarizes(&fixture.schemes[0], non_empty![@&notarizes], &Sequential),
3698            Err(AssemblyError::InsufficientAttestations(
3699                quorum_size,
3700                quorum_size - 1
3701            )),
3702            "insufficient votes should not form a notarization"
3703        );
3704    }
3705
3706    #[test]
3707    fn test_notarization_recover_insufficient_signatures() {
3708        notarization_recover_insufficient_signatures(ed25519::fixture);
3709        notarization_recover_insufficient_signatures(secp256r1::fixture);
3710        notarization_recover_insufficient_signatures(bls12381_multisig::fixture::<MinPk, _>);
3711        notarization_recover_insufficient_signatures(bls12381_multisig::fixture::<MinSig, _>);
3712        notarization_recover_insufficient_signatures(bls12381_threshold_vrf::fixture::<MinPk, _>);
3713        notarization_recover_insufficient_signatures(bls12381_threshold_vrf::fixture::<MinSig, _>);
3714        notarization_recover_insufficient_signatures(bls12381_threshold_std::fixture::<MinPk, _>);
3715        notarization_recover_insufficient_signatures(bls12381_threshold_std::fixture::<MinSig, _>);
3716    }
3717
3718    fn conflicting_notarize_detection<S, F>(f: F)
3719    where
3720        S: Scheme<Sha256>,
3721        F: Fn(&mut TestRng, &[u8], u32) -> Fixture<S>,
3722    {
3723        let mut rng = test_rng();
3724        let fixture = setup_seeded(5, 0, &f);
3725        let wrong_ns_fixture = setup_seeded_ns(5, 0, b"wrong_namespace", &f);
3726        let wrong_scheme_fixture = setup_seeded(5, 1, &f);
3727
3728        let round = Round::new(Epoch::new(0), View::new(10));
3729        let proposal1 = Proposal::new(round, View::new(5), sample_digest(6));
3730        let proposal2 = Proposal::new(round, View::new(5), sample_digest(7));
3731
3732        let notarize1 = Notarize::sign(&fixture.schemes[0], proposal1).unwrap();
3733        let notarize2 = Notarize::sign(&fixture.schemes[0], proposal2).unwrap();
3734        let conflict = ConflictingNotarize::new(notarize1, notarize2);
3735
3736        assert!(conflict.verify(&mut rng, &fixture.schemes[0], &Sequential));
3737        assert!(!conflict.verify(&mut rng, &wrong_ns_fixture.schemes[0], &Sequential));
3738        assert!(!conflict.verify(&mut rng, &wrong_scheme_fixture.verifier, &Sequential));
3739    }
3740
3741    #[test]
3742    fn test_conflicting_notarize_detection() {
3743        conflicting_notarize_detection(ed25519::fixture);
3744        conflicting_notarize_detection(secp256r1::fixture);
3745        conflicting_notarize_detection(bls12381_multisig::fixture::<MinPk, _>);
3746        conflicting_notarize_detection(bls12381_multisig::fixture::<MinSig, _>);
3747        conflicting_notarize_detection(bls12381_threshold_vrf::fixture::<MinPk, _>);
3748        conflicting_notarize_detection(bls12381_threshold_vrf::fixture::<MinSig, _>);
3749        conflicting_notarize_detection(bls12381_threshold_std::fixture::<MinPk, _>);
3750        conflicting_notarize_detection(bls12381_threshold_std::fixture::<MinSig, _>);
3751    }
3752
3753    fn nullify_finalize_detection<S, F>(f: F)
3754    where
3755        S: Scheme<Sha256>,
3756        F: Fn(&mut TestRng, &[u8], u32) -> Fixture<S>,
3757    {
3758        let mut rng = test_rng();
3759        let fixture = setup_seeded(5, 0, &f);
3760        let wrong_ns_fixture = setup_seeded_ns(5, 0, b"wrong_namespace", &f);
3761        let wrong_scheme_fixture = setup_seeded(5, 1, &f);
3762
3763        let round = Round::new(Epoch::new(0), View::new(10));
3764        let proposal = Proposal::new(round, View::new(5), sample_digest(8));
3765
3766        let nullify = Nullify::sign::<Sha256>(&fixture.schemes[0], round).unwrap();
3767        let finalize = Finalize::sign(&fixture.schemes[0], proposal).unwrap();
3768        let conflict = NullifyFinalize::new(nullify, finalize);
3769
3770        assert!(conflict.verify(&mut rng, &fixture.schemes[0], &Sequential));
3771        assert!(!conflict.verify(&mut rng, &wrong_ns_fixture.schemes[0], &Sequential));
3772        assert!(!conflict.verify(&mut rng, &wrong_scheme_fixture.verifier, &Sequential));
3773    }
3774
3775    #[test]
3776    fn test_nullify_finalize_detection() {
3777        nullify_finalize_detection(ed25519::fixture);
3778        nullify_finalize_detection(secp256r1::fixture);
3779        nullify_finalize_detection(bls12381_multisig::fixture::<MinPk, _>);
3780        nullify_finalize_detection(bls12381_multisig::fixture::<MinSig, _>);
3781        nullify_finalize_detection(bls12381_threshold_vrf::fixture::<MinPk, _>);
3782        nullify_finalize_detection(bls12381_threshold_vrf::fixture::<MinSig, _>);
3783        nullify_finalize_detection(bls12381_threshold_std::fixture::<MinPk, _>);
3784        nullify_finalize_detection(bls12381_threshold_std::fixture::<MinSig, _>);
3785    }
3786
3787    fn finalization_verify_wrong_scheme<S, F>(f: F)
3788    where
3789        S: Scheme<Sha256>,
3790        F: Fn(&mut TestRng, &[u8], u32) -> Fixture<S>,
3791    {
3792        let mut rng = test_rng();
3793        let fixture = setup_seeded(5, 0, &f);
3794        let wrong_fixture = setup_seeded(5, 1, &f);
3795        let round = Round::new(Epoch::new(0), View::new(10));
3796        let proposal = Proposal::new(round, View::new(5), sample_digest(9));
3797        let quorum = N3f1::quorum(fixture.schemes.len()) as usize;
3798        let finalizes: Vec<_> = fixture
3799            .schemes
3800            .iter()
3801            .take(quorum)
3802            .map(|scheme| Finalize::sign(scheme, proposal.clone()).unwrap())
3803            .collect();
3804
3805        let finalization =
3806            Finalization::from_finalizes(&fixture.schemes[0], non_empty![@&finalizes], &Sequential)
3807                .expect("quorum finalization");
3808        assert!(finalization.verify(&mut rng, &fixture.schemes[0], &Sequential));
3809        assert!(!finalization.verify(&mut rng, &wrong_fixture.verifier, &Sequential));
3810    }
3811
3812    #[test]
3813    fn test_finalization_wrong_scheme() {
3814        finalization_verify_wrong_scheme(ed25519::fixture);
3815        finalization_verify_wrong_scheme(secp256r1::fixture);
3816        finalization_verify_wrong_scheme(bls12381_multisig::fixture::<MinPk, _>);
3817        finalization_verify_wrong_scheme(bls12381_multisig::fixture::<MinSig, _>);
3818        finalization_verify_wrong_scheme(bls12381_threshold_vrf::fixture::<MinPk, _>);
3819        finalization_verify_wrong_scheme(bls12381_threshold_vrf::fixture::<MinSig, _>);
3820        finalization_verify_wrong_scheme(bls12381_threshold_std::fixture::<MinPk, _>);
3821        finalization_verify_wrong_scheme(bls12381_threshold_std::fixture::<MinSig, _>);
3822    }
3823
3824    struct MockAttributable(Participant);
3825
3826    impl Attributable for MockAttributable {
3827        fn signer(&self) -> Participant {
3828            self.0
3829        }
3830    }
3831
3832    #[test]
3833    fn test_attributable_map() {
3834        let mut map = AttributableMap::new(5);
3835        assert_eq!(map.len(), 0);
3836        assert!(map.is_empty());
3837        assert_eq!(map.data.capacity(), 0, "empty maps should allocate lazily");
3838
3839        // Test get on empty map
3840        for i in 0..5 {
3841            assert!(map.get(Participant::new(i)).is_none());
3842        }
3843
3844        assert!(map.insert(MockAttributable(Participant::new(3))));
3845        assert!(map.data.capacity() >= 5);
3846        assert_eq!(map.len(), 1);
3847        assert!(!map.is_empty());
3848        let mut iter = map.iter();
3849        assert!(matches!(iter.next(), Some(a) if a.signer() == Participant::new(3)));
3850        assert!(iter.next().is_none());
3851        drop(iter);
3852
3853        // Test get on existing item
3854        assert!(
3855            matches!(map.get(Participant::new(3)), Some(a) if a.signer() == Participant::new(3))
3856        );
3857
3858        assert!(map.insert(MockAttributable(Participant::new(1))));
3859        assert_eq!(map.len(), 2);
3860        assert!(!map.is_empty());
3861        let mut iter = map.iter();
3862        assert!(matches!(iter.next(), Some(a) if a.signer() == Participant::new(1)));
3863        assert!(matches!(iter.next(), Some(a) if a.signer() == Participant::new(3)));
3864        assert!(iter.next().is_none());
3865        drop(iter);
3866
3867        // Test get on both items
3868        assert!(
3869            matches!(map.get(Participant::new(1)), Some(a) if a.signer() == Participant::new(1))
3870        );
3871        assert!(
3872            matches!(map.get(Participant::new(3)), Some(a) if a.signer() == Participant::new(3))
3873        );
3874
3875        // Test get on non-existing items
3876        assert!(map.get(Participant::new(0)).is_none());
3877        assert!(map.get(Participant::new(2)).is_none());
3878        assert!(map.get(Participant::new(4)).is_none());
3879
3880        assert!(!map.insert(MockAttributable(Participant::new(3))));
3881        assert_eq!(map.len(), 2);
3882        assert!(!map.is_empty());
3883        let mut iter = map.iter();
3884        assert!(matches!(iter.next(), Some(a) if a.signer() == Participant::new(1)));
3885        assert!(matches!(iter.next(), Some(a) if a.signer() == Participant::new(3)));
3886        assert!(iter.next().is_none());
3887        drop(iter);
3888
3889        // Test out-of-bounds signer indices
3890        assert!(!map.insert(MockAttributable(Participant::new(5))));
3891        assert!(!map.insert(MockAttributable(Participant::new(100))));
3892        assert_eq!(map.len(), 2);
3893
3894        // Test clear
3895        map.clear();
3896        assert_eq!(map.len(), 0);
3897        assert!(map.is_empty());
3898        assert!(map.iter().next().is_none());
3899        assert_eq!(map.data.capacity(), 0, "clear should release vote storage");
3900
3901        // Verify can insert after clear
3902        assert!(map.insert(MockAttributable(Participant::new(2))));
3903        assert_eq!(map.len(), 1);
3904        let mut iter = map.iter();
3905        assert!(matches!(iter.next(), Some(a) if a.signer() == Participant::new(2)));
3906        assert!(iter.next().is_none());
3907    }
3908
3909    #[test]
3910    fn test_vote_tracker_clears_compacted_state() {
3911        let mut rng = test_rng();
3912        let fixture = ed25519::fixture(&mut rng, NAMESPACE, 2);
3913        let round = Round::new(Epoch::new(0), View::new(1));
3914        let mut tracker = VoteTracker::<ed25519::Scheme, Sha256>::new(2, false);
3915        let signer = Participant::new(1);
3916        let scheme = &fixture.schemes[usize::from(signer)];
3917        let proposal = Proposal::new(round, View::zero(), sample_digest(1));
3918        let notarize = Vote::Notarize(Notarize::sign(scheme, proposal.clone()).unwrap());
3919        let finalize = Vote::Finalize(Finalize::sign(scheme, proposal.clone()).unwrap());
3920
3921        tracker.release_notarizes(&proposal);
3922        tracker.release_finalizes(&proposal);
3923        assert!(matches!(
3924            tracker.record(&notarize, Some(&proposal)),
3925            Outcome::Added { retained: false }
3926        ));
3927        assert!(matches!(
3928            tracker.record(&finalize, Some(&proposal)),
3929            Outcome::Added { retained: false }
3930        ));
3931        assert!(tracker.has_notarize_for(signer, &proposal));
3932        assert!(tracker.has_finalize_for(signer, &proposal));
3933
3934        tracker.clear_notarizes();
3935        assert!(!tracker.has_notarize_for(signer, &proposal));
3936        assert!(matches!(
3937            tracker.record(&notarize, Some(&proposal)),
3938            Outcome::Added { retained: true }
3939        ));
3940
3941        tracker.clear_finalizes();
3942        assert!(!tracker.has_finalize_for(signer, &proposal));
3943        assert_eq!(tracker.compacted.capacity(), 0);
3944        assert!(matches!(
3945            tracker.record(&finalize, Some(&proposal)),
3946            Outcome::Added { retained: true }
3947        ));
3948    }
3949
3950    #[test]
3951    fn test_vote_tracker_insert_and_accessors() {
3952        let mut rng = test_rng();
3953        let fixture = ed25519::fixture(&mut rng, NAMESPACE, 2);
3954        let round = Round::new(Epoch::new(0), View::new(1));
3955        let proposal = Proposal::new(round, View::zero(), sample_digest(1));
3956        let scheme = &fixture.schemes[0];
3957        let signer = Participant::new(0);
3958        let notarize = Notarize::sign(scheme, proposal.clone()).unwrap();
3959        let nullify = Nullify::sign::<Sha256>(scheme, round).unwrap();
3960        let finalize = Finalize::sign(scheme, proposal.clone()).unwrap();
3961        let mut tracker = VoteTracker::new(2, false);
3962
3963        assert!(tracker.insert_notarize(notarize.clone()));
3964        assert!(tracker.insert_nullify(nullify.clone()));
3965        assert!(tracker.insert_finalize(finalize.clone()));
3966        assert_eq!(tracker.len_notarizes(), 1);
3967        assert_eq!(tracker.len_nullifies(), 1);
3968        assert_eq!(tracker.len_finalizes(), 1);
3969        assert!(tracker.has_notarize(signer));
3970        assert!(tracker.has_nullify(signer));
3971        assert!(tracker.has_finalize(signer));
3972
3973        tracker.release_notarizes(&proposal);
3974        tracker.release_nullifies();
3975        tracker.release_finalizes(&proposal);
3976        assert_eq!(tracker.len_notarizes(), 0);
3977        assert_eq!(tracker.len_nullifies(), 0);
3978        assert_eq!(tracker.len_finalizes(), 0);
3979        assert!(!tracker.has_notarize(signer));
3980        assert!(!tracker.has_nullify(signer));
3981        assert!(!tracker.has_finalize(signer));
3982        assert!(tracker.iter_notarizes().next().is_none());
3983        assert!(tracker.iter_nullifies().next().is_none());
3984        assert!(tracker.iter_finalizes().next().is_none());
3985        assert!(!tracker.insert_notarize(notarize));
3986        assert!(!tracker.insert_nullify(nullify));
3987        assert!(!tracker.insert_finalize(finalize));
3988
3989        // Releasing a compacted phase is idempotent.
3990        tracker.release_notarizes(&proposal);
3991    }
3992
3993    #[test]
3994    fn test_vote_tracker_retention_policy() {
3995        let mut rng = test_rng();
3996        let fixture = ed25519::fixture(&mut rng, NAMESPACE, 2);
3997        let proposal = Proposal::new(
3998            Round::new(Epoch::new(0), View::new(1)),
3999            View::zero(),
4000            sample_digest(1),
4001        );
4002        let notarize = Notarize::sign(&fixture.schemes[0], proposal.clone()).unwrap();
4003        let signer = notarize.signer();
4004        let vote = Vote::Notarize(notarize);
4005
4006        let mut releasing = VoteTracker::new(2, false);
4007        assert!(matches!(
4008            releasing.record(&vote, Some(&proposal)),
4009            Outcome::Added { retained: true }
4010        ));
4011        let Phase::Full(votes) = &releasing.notarizes else {
4012            panic!("notarize phase compacted before certification");
4013        };
4014        assert!(votes.data.capacity() >= 2);
4015        releasing.release_notarizes(&proposal);
4016        assert!(matches!(&releasing.notarizes, Phase::Compacted));
4017        assert!(matches!(
4018            releasing.record(&vote, Some(&proposal)),
4019            Outcome::Duplicate { retained: false }
4020        ));
4021
4022        // A certificate can arrive before any individual votes. Subsequent votes
4023        // must use compact storage instead of recreating the released full map.
4024        let mut certificate_first = VoteTracker::new(2, false);
4025        certificate_first.release_notarizes(&proposal);
4026        assert!(matches!(
4027            certificate_first.record(&vote, Some(&proposal)),
4028            Outcome::Added { retained: false }
4029        ));
4030        assert!(matches!(&certificate_first.notarizes, Phase::Compacted));
4031        assert!(matches!(
4032            certificate_first.record(&vote, Some(&proposal)),
4033            Outcome::Duplicate { retained: false }
4034        ));
4035
4036        let mut retaining = VoteTracker::new(2, true);
4037        assert!(matches!(
4038            retaining.record(&vote, Some(&proposal)),
4039            Outcome::Added { retained: true }
4040        ));
4041        let Phase::Full(votes) = &retaining.notarizes else {
4042            panic!("retained notarize phase compacted");
4043        };
4044        let retained_capacity = votes.data.capacity();
4045        retaining.release_notarizes(&proposal);
4046        let Phase::Full(votes) = &retaining.notarizes else {
4047            panic!("retained notarize phase compacted");
4048        };
4049        assert_eq!(votes.data.capacity(), retained_capacity);
4050        assert!(retaining.notarize(signer).is_some());
4051    }
4052
4053    #[test]
4054    #[should_panic(expected = "proposals must differ")]
4055    fn issue_2944_regression_conflicting_notarize_new() {
4056        let mut rng = test_rng();
4057        let fixture = ed25519::fixture(&mut rng, NAMESPACE, 1);
4058        let proposal = Proposal::new(
4059            Round::new(Epoch::new(0), View::new(10)),
4060            View::new(5),
4061            sample_digest(1),
4062        );
4063        let notarize = Notarize::sign(&fixture.schemes[0], proposal).unwrap();
4064        let _ = ConflictingNotarize::new(notarize.clone(), notarize);
4065    }
4066
4067    #[test]
4068    fn issue_2944_regression_conflicting_notarize_decode() {
4069        let mut rng = test_rng();
4070        let fixture = ed25519::fixture(&mut rng, NAMESPACE, 1);
4071        let proposal = Proposal::new(
4072            Round::new(Epoch::new(0), View::new(10)),
4073            View::new(5),
4074            sample_digest(1),
4075        );
4076        let notarize = Notarize::sign(&fixture.schemes[0], proposal).unwrap();
4077
4078        // Manually encode two identical notarizes
4079        let mut buf = Vec::new();
4080        notarize.write(&mut buf);
4081        notarize.write(&mut buf);
4082
4083        // Decoding should fail
4084        let result = ConflictingNotarize::<ed25519::Scheme, Sha256>::decode(Bytes::from(buf));
4085        assert!(result.is_err());
4086    }
4087
4088    #[test]
4089    #[should_panic(expected = "proposals must differ")]
4090    fn issue_2944_regression_conflicting_finalize_new() {
4091        let mut rng = test_rng();
4092        let fixture = ed25519::fixture(&mut rng, NAMESPACE, 1);
4093        let proposal = Proposal::new(
4094            Round::new(Epoch::new(0), View::new(10)),
4095            View::new(5),
4096            sample_digest(1),
4097        );
4098        let finalize = Finalize::sign(&fixture.schemes[0], proposal).unwrap();
4099        let _ = ConflictingFinalize::new(finalize.clone(), finalize);
4100    }
4101
4102    #[test]
4103    fn issue_2944_regression_conflicting_finalize_decode() {
4104        let mut rng = test_rng();
4105        let fixture = ed25519::fixture(&mut rng, NAMESPACE, 1);
4106        let proposal = Proposal::new(
4107            Round::new(Epoch::new(0), View::new(10)),
4108            View::new(5),
4109            sample_digest(1),
4110        );
4111        let finalize = Finalize::sign(&fixture.schemes[0], proposal).unwrap();
4112
4113        // Manually encode two identical finalizes
4114        let mut buf = Vec::new();
4115        finalize.write(&mut buf);
4116        finalize.write(&mut buf);
4117
4118        // Decoding should fail
4119        let result = ConflictingFinalize::<ed25519::Scheme, Sha256>::decode(Bytes::from(buf));
4120        assert!(result.is_err());
4121    }
4122
4123    #[cfg(feature = "arbitrary")]
4124    mod conformance {
4125        use super::*;
4126        use crate::simplex::scheme::bls12381_threshold::vrf as bls12381_threshold_vrf;
4127        use commonware_codec::conformance::CodecConformance;
4128        use commonware_cryptography::{ed25519::PublicKey, sha256::Digest as Sha256Digest};
4129
4130        type Scheme = bls12381_threshold_vrf::Scheme<PublicKey, MinSig>;
4131
4132        commonware_conformance::conformance_tests! {
4133            CodecConformance<Vote<Scheme, Sha256Digest>>,
4134            CodecConformance<Certificate<Scheme, Sha256Digest>>,
4135            CodecConformance<Artifact<Scheme, Sha256Digest>>,
4136            CodecConformance<Proposal<Sha256Digest>>,
4137            CodecConformance<Notarize<Scheme, Sha256Digest>>,
4138            CodecConformance<Notarization<Scheme, Sha256Digest>>,
4139            CodecConformance<Nullify<Scheme>>,
4140            CodecConformance<Nullification<Scheme>>,
4141            CodecConformance<Finalize<Scheme, Sha256Digest>>,
4142            CodecConformance<Finalization<Scheme, Sha256Digest>>,
4143            CodecConformance<Backfiller<Scheme, Sha256Digest>>,
4144            CodecConformance<Request>,
4145            CodecConformance<Response<Scheme, Sha256Digest>>,
4146            CodecConformance<Activity<Scheme, Sha256Digest>>,
4147            CodecConformance<ConflictingNotarize<Scheme, Sha256Digest>>,
4148            CodecConformance<ConflictingFinalize<Scheme, Sha256Digest>>,
4149            CodecConformance<NullifyFinalize<Scheme, Sha256Digest>>,
4150            CodecConformance<Context<Sha256Digest, PublicKey>>
4151        }
4152    }
4153}