//! Types used in [crate::simplex]. use crate::{ simplex::scheme, types::{Epoch, Round, View}, Epochable, Viewable, }; use bytes::{Buf, BufMut}; use commonware_codec::{varint::UInt, EncodeSize, Error, Read, ReadExt, ReadRangeExt, Write}; use commonware_cryptography::{ certificate::{Attestation, Scheme}, Digest, PublicKey, }; use rand::{CryptoRng, Rng}; use std::{collections::HashSet, fmt::Debug, hash::Hash}; /// Context is a collection of metadata from consensus about a given payload. /// It provides information about the current epoch/view and the parent payload that new proposals are built on. #[derive(Clone, Debug)] pub struct Context { /// Current round of consensus. pub round: Round, /// Leader of the current round. pub leader: P, /// Parent the payload is built on. /// /// If there is a gap between the current view and the parent view, the participant /// must possess a nullification for each discarded view to safely vote on the proposed /// payload (any view without a nullification may eventually be finalized and skipping /// it would result in a fork). pub parent: (View, D), } impl Epochable for Context { fn epoch(&self) -> Epoch { self.round.epoch() } } impl Viewable for Context { fn view(&self) -> View { self.round.view() } } /// Attributable is a trait that provides access to the signer index. /// This is used to identify which participant signed a given message. pub trait Attributable { /// Returns the index of the signer (validator) who produced this message. fn signer(&self) -> u32; } /// A map of [Attributable] items keyed by their signer index. /// /// The key for each item is automatically inferred from [Attributable::signer()]. /// Each signer can insert at most one item. pub struct AttributableMap { data: Vec>, added: usize, } impl AttributableMap { /// Creates a new [AttributableMap] with the given number of participants. pub fn new(participants: usize) -> Self { // `resize_with` avoids requiring `T: Clone` while pre-filling with `None`. let mut data = Vec::with_capacity(participants); data.resize_with(participants, || None); Self { data, added: 0 } } /// Clears all existing items from the [AttributableMap]. pub fn clear(&mut self) { self.data.fill_with(|| None); self.added = 0; } /// Inserts an item into the map, using [Attributable::signer()] as the key, /// if it has not been added yet. /// /// Returns `true` if the item was inserted, `false` if an item from this /// signer already exists or if the signer index is out of bounds. pub fn insert(&mut self, item: T) -> bool { let index = item.signer() as usize; if index >= self.data.len() { return false; } if self.data[index].is_some() { return false; } self.data[index] = Some(item); self.added += 1; true } /// Returns the number of items in the [AttributableMap]. pub const fn len(&self) -> usize { self.added } /// Returns `true` if the [AttributableMap] is empty. pub const fn is_empty(&self) -> bool { self.added == 0 } /// Returns a reference to the item associated with the given signer, if present. pub fn get(&self, signer: u32) -> Option<&T> { self.data.get(signer as usize)?.as_ref() } /// Returns an iterator over items in the map, ordered by signer index /// ([Attributable::signer()]). pub fn iter(&self) -> impl Iterator { self.data.iter().filter_map(|o| o.as_ref()) } } /// Tracks notarize/nullify/finalize votes for a view. /// /// Each vote type is stored in its own [`AttributableMap`] so a validator can only /// contribute one vote per phase. The tracker is reused across rounds/views to keep /// allocations stable. pub struct VoteTracker { /// Per-signer notarize votes keyed by validator index. notarizes: AttributableMap>, /// Per-signer nullify votes keyed by validator index. nullifies: AttributableMap>, /// Per-signer finalize votes keyed by validator index. /// /// Finalize votes include the proposal digest so the entire certificate can be /// reconstructed once the quorum threshold is hit. finalizes: AttributableMap>, } impl VoteTracker { /// Creates a tracker sized for `participants` validators. pub fn new(participants: usize) -> Self { Self { notarizes: AttributableMap::new(participants), nullifies: AttributableMap::new(participants), finalizes: AttributableMap::new(participants), } } /// Inserts a notarize vote if the signer has not already voted. pub fn insert_notarize(&mut self, vote: Notarize) -> bool { self.notarizes.insert(vote) } /// Inserts a nullify vote if the signer has not already voted. pub fn insert_nullify(&mut self, vote: Nullify) -> bool { self.nullifies.insert(vote) } /// Inserts a finalize vote if the signer has not already voted. pub fn insert_finalize(&mut self, vote: Finalize) -> bool { self.finalizes.insert(vote) } /// Returns the notarize vote for `signer`, if present. pub fn notarize(&self, signer: u32) -> Option<&Notarize> { self.notarizes.get(signer) } /// Returns the nullify vote for `signer`, if present. pub fn nullify(&self, signer: u32) -> Option<&Nullify> { self.nullifies.get(signer) } /// Returns the finalize vote for `signer`, if present. pub fn finalize(&self, signer: u32) -> Option<&Finalize> { self.finalizes.get(signer) } /// Iterates over notarize votes in signer order. pub fn iter_notarizes(&self) -> impl Iterator> { self.notarizes.iter() } /// Iterates over nullify votes in signer order. pub fn iter_nullifies(&self) -> impl Iterator> { self.nullifies.iter() } /// Iterates over finalize votes in signer order. pub fn iter_finalizes(&self) -> impl Iterator> { self.finalizes.iter() } /// Returns how many notarize votes have been recorded. pub fn len_notarizes(&self) -> u32 { u32::try_from(self.notarizes.len()).expect("too many notarize votes") } /// Returns how many nullify votes have been recorded. pub fn len_nullifies(&self) -> u32 { u32::try_from(self.nullifies.len()).expect("too many nullify votes") } /// Returns how many finalize votes have been recorded. pub fn len_finalizes(&self) -> u32 { u32::try_from(self.finalizes.len()).expect("too many finalize votes") } /// Returns `true` if the given signer has a notarize vote recorded. pub fn has_notarize(&self, signer: u32) -> bool { self.notarize(signer).is_some() } /// Returns `true` if the given signer has a nullify vote recorded. pub fn has_nullify(&self, signer: u32) -> bool { self.nullify(signer).is_some() } /// Returns `true` if the given signer has a finalize vote recorded. pub fn has_finalize(&self, signer: u32) -> bool { self.finalize(signer).is_some() } /// Clears all notarize votes but keeps the allocations for reuse. pub fn clear_notarizes(&mut self) { self.notarizes.clear(); } /// Clears all finalize votes but keeps the allocations for reuse. pub fn clear_finalizes(&mut self) { self.finalizes.clear(); } } /// Identifies the subject of a vote or certificate. /// /// Implementations use the subject to derive domain-separated message bytes for both /// individual votes and recovered certificates. #[derive(Copy, Clone, Debug)] pub enum Subject<'a, D: Digest> { /// Subject for notarize votes and certificates, carrying the proposal. Notarize { proposal: &'a Proposal }, /// Subject for nullify votes and certificates, scoped to a round. Nullify { round: Round }, /// Subject for finalize votes and certificates, carrying the proposal. Finalize { proposal: &'a Proposal }, } impl Viewable for Subject<'_, D> { fn view(&self) -> View { match self { Subject::Notarize { proposal } => proposal.view(), Subject::Nullify { round } => round.view(), Subject::Finalize { proposal } => proposal.view(), } } } /// Vote represents individual votes ([Notarize], [Nullify], [Finalize]). #[derive(Clone, Debug, PartialEq)] pub enum Vote { /// A validator's notarize vote over a proposal. Notarize(Notarize), /// A validator's nullify vote used to skip the current view. Nullify(Nullify), /// A validator's finalize vote over a proposal. Finalize(Finalize), } impl Write for Vote { fn write(&self, writer: &mut impl BufMut) { match self { Self::Notarize(v) => { 0u8.write(writer); v.write(writer); } Self::Nullify(v) => { 1u8.write(writer); v.write(writer); } Self::Finalize(v) => { 2u8.write(writer); v.write(writer); } } } } impl EncodeSize for Vote { fn encode_size(&self) -> usize { 1 + match self { Self::Notarize(v) => v.encode_size(), Self::Nullify(v) => v.encode_size(), Self::Finalize(v) => v.encode_size(), } } } impl Read for Vote { type Cfg = (); fn read_cfg(reader: &mut impl Buf, _: &()) -> Result { let tag = ::read(reader)?; match tag { 0 => { let v = Notarize::read(reader)?; Ok(Self::Notarize(v)) } 1 => { let v = Nullify::read(reader)?; Ok(Self::Nullify(v)) } 2 => { let v = Finalize::read(reader)?; Ok(Self::Finalize(v)) } _ => Err(Error::Invalid("consensus::simplex::Vote", "Invalid type")), } } } impl Epochable for Vote { fn epoch(&self) -> Epoch { match self { Self::Notarize(v) => v.epoch(), Self::Nullify(v) => v.epoch(), Self::Finalize(v) => v.epoch(), } } } impl Viewable for Vote { fn view(&self) -> View { match self { Self::Notarize(v) => v.view(), Self::Nullify(v) => v.view(), Self::Finalize(v) => v.view(), } } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for Vote where S::Signature: for<'a> arbitrary::Arbitrary<'a>, D: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let tag = u.int_in_range(0..=2)?; match tag { 0 => { let v = Notarize::arbitrary(u)?; Ok(Self::Notarize(v)) } 1 => { let v = Nullify::arbitrary(u)?; Ok(Self::Nullify(v)) } 2 => { let v = Finalize::arbitrary(u)?; Ok(Self::Finalize(v)) } _ => unreachable!(), } } } /// Certificate represents aggregated votes ([Notarization], [Nullification], [Finalization]). #[derive(Clone, Debug, PartialEq)] pub enum Certificate { /// A recovered certificate for a notarization. Notarization(Notarization), /// A recovered certificate for a nullification. Nullification(Nullification), /// A recovered certificate for a finalization. Finalization(Finalization), } impl Write for Certificate { fn write(&self, writer: &mut impl BufMut) { match self { Self::Notarization(v) => { 0u8.write(writer); v.write(writer); } Self::Nullification(v) => { 1u8.write(writer); v.write(writer); } Self::Finalization(v) => { 2u8.write(writer); v.write(writer); } } } } impl EncodeSize for Certificate { fn encode_size(&self) -> usize { 1 + match self { Self::Notarization(v) => v.encode_size(), Self::Nullification(v) => v.encode_size(), Self::Finalization(v) => v.encode_size(), } } } impl Read for Certificate { type Cfg = ::Cfg; fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result { let tag = ::read(reader)?; match tag { 0 => { let v = Notarization::read_cfg(reader, cfg)?; Ok(Self::Notarization(v)) } 1 => { let v = Nullification::read_cfg(reader, cfg)?; Ok(Self::Nullification(v)) } 2 => { let v = Finalization::read_cfg(reader, cfg)?; Ok(Self::Finalization(v)) } _ => Err(Error::Invalid( "consensus::simplex::Certificate", "Invalid type", )), } } } impl Epochable for Certificate { fn epoch(&self) -> Epoch { match self { Self::Notarization(v) => v.epoch(), Self::Nullification(v) => v.epoch(), Self::Finalization(v) => v.epoch(), } } } impl Viewable for Certificate { fn view(&self) -> View { match self { Self::Notarization(v) => v.view(), Self::Nullification(v) => v.view(), Self::Finalization(v) => v.view(), } } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for Certificate where S::Certificate: for<'a> arbitrary::Arbitrary<'a>, D: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let tag = u.int_in_range(0..=2)?; match tag { 0 => { let v = Notarization::arbitrary(u)?; Ok(Self::Notarization(v)) } 1 => { let v = Nullification::arbitrary(u)?; Ok(Self::Nullification(v)) } 2 => { let v = Finalization::arbitrary(u)?; Ok(Self::Finalization(v)) } _ => unreachable!(), } } } /// Artifact represents all consensus artifacts (votes and certificates) for storage. #[derive(Clone, Debug, PartialEq)] pub enum Artifact { /// A validator's notarize vote over a proposal. Notarize(Notarize), /// A recovered certificate for a notarization. Notarization(Notarization), /// A notarization was locally certified. Certification(Round, bool), /// A validator's nullify vote used to skip the current view. Nullify(Nullify), /// A recovered certificate for a nullification. Nullification(Nullification), /// A validator's finalize vote over a proposal. Finalize(Finalize), /// A recovered certificate for a finalization. Finalization(Finalization), } impl Write for Artifact { fn write(&self, writer: &mut impl BufMut) { match self { Self::Notarize(v) => { 0u8.write(writer); v.write(writer); } Self::Notarization(v) => { 1u8.write(writer); v.write(writer); } Self::Certification(r, b) => { 2u8.write(writer); r.write(writer); b.write(writer); } Self::Nullify(v) => { 3u8.write(writer); v.write(writer); } Self::Nullification(v) => { 4u8.write(writer); v.write(writer); } Self::Finalize(v) => { 5u8.write(writer); v.write(writer); } Self::Finalization(v) => { 6u8.write(writer); v.write(writer); } } } } impl EncodeSize for Artifact { fn encode_size(&self) -> usize { 1 + match self { Self::Notarize(v) => v.encode_size(), Self::Notarization(v) => v.encode_size(), Self::Certification(r, b) => r.encode_size() + b.encode_size(), Self::Nullify(v) => v.encode_size(), Self::Nullification(v) => v.encode_size(), Self::Finalize(v) => v.encode_size(), Self::Finalization(v) => v.encode_size(), } } } impl Read for Artifact { type Cfg = ::Cfg; fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result { let tag = ::read(reader)?; match tag { 0 => { let v = Notarize::read(reader)?; Ok(Self::Notarize(v)) } 1 => { let v = Notarization::read_cfg(reader, cfg)?; Ok(Self::Notarization(v)) } 2 => { let r = Round::read(reader)?; let b = bool::read(reader)?; Ok(Self::Certification(r, b)) } 3 => { let v = Nullify::read(reader)?; Ok(Self::Nullify(v)) } 4 => { let v = Nullification::read_cfg(reader, cfg)?; Ok(Self::Nullification(v)) } 5 => { let v = Finalize::read(reader)?; Ok(Self::Finalize(v)) } 6 => { let v = Finalization::read_cfg(reader, cfg)?; Ok(Self::Finalization(v)) } _ => Err(Error::Invalid( "consensus::simplex::Artifact", "Invalid type", )), } } } impl Epochable for Artifact { fn epoch(&self) -> Epoch { match self { Self::Notarize(v) => v.epoch(), Self::Notarization(v) => v.epoch(), Self::Certification(r, _) => r.epoch(), Self::Nullify(v) => v.epoch(), Self::Nullification(v) => v.epoch(), Self::Finalize(v) => v.epoch(), Self::Finalization(v) => v.epoch(), } } } impl Viewable for Artifact { fn view(&self) -> View { match self { Self::Notarize(v) => v.view(), Self::Notarization(v) => v.view(), Self::Certification(r, _) => r.view(), Self::Nullify(v) => v.view(), Self::Nullification(v) => v.view(), Self::Finalize(v) => v.view(), Self::Finalization(v) => v.view(), } } } impl From> for Artifact { fn from(vote: Vote) -> Self { match vote { Vote::Notarize(v) => Self::Notarize(v), Vote::Nullify(v) => Self::Nullify(v), Vote::Finalize(v) => Self::Finalize(v), } } } impl From> for Artifact { fn from(cert: Certificate) -> Self { match cert { Certificate::Notarization(v) => Self::Notarization(v), Certificate::Nullification(v) => Self::Nullification(v), Certificate::Finalization(v) => Self::Finalization(v), } } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for Artifact where S::Signature: for<'a> arbitrary::Arbitrary<'a>, S::Certificate: for<'a> arbitrary::Arbitrary<'a>, D: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let tag = u.int_in_range(0..=6)?; match tag { 0 => { let v = Notarize::arbitrary(u)?; Ok(Self::Notarize(v)) } 1 => { let v = Notarization::arbitrary(u)?; Ok(Self::Notarization(v)) } 2 => { let r = Round::arbitrary(u)?; let b = bool::arbitrary(u)?; Ok(Self::Certification(r, b)) } 3 => { let v = Nullify::arbitrary(u)?; Ok(Self::Nullify(v)) } 4 => { let v = Nullification::arbitrary(u)?; Ok(Self::Nullification(v)) } 5 => { let v = Finalize::arbitrary(u)?; Ok(Self::Finalize(v)) } 6 => { let v = Finalization::arbitrary(u)?; Ok(Self::Finalization(v)) } _ => unreachable!(), } } } /// Proposal represents a proposed block in the protocol. /// It includes the view number, the parent view, and the actual payload (typically a digest of block data). #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Proposal { /// The round in which this proposal is made pub round: Round, /// The view of the parent proposal that this one builds upon pub parent: View, /// The actual payload/content of the proposal (typically a digest of the block data) pub payload: D, } impl Proposal { /// Creates a new proposal with the specified view, parent view, and payload. pub const fn new(round: Round, parent: View, payload: D) -> Self { Self { round, parent, payload, } } } impl Write for Proposal { fn write(&self, writer: &mut impl BufMut) { self.round.write(writer); self.parent.write(writer); self.payload.write(writer) } } impl Read for Proposal { type Cfg = (); fn read_cfg(reader: &mut impl Buf, _: &()) -> Result { let round = Round::read(reader)?; let parent = View::read(reader)?; let payload = D::read(reader)?; Ok(Self { round, parent, payload, }) } } impl EncodeSize for Proposal { fn encode_size(&self) -> usize { self.round.encode_size() + self.parent.encode_size() + self.payload.encode_size() } } impl Epochable for Proposal { fn epoch(&self) -> Epoch { self.round.epoch() } } impl Viewable for Proposal { fn view(&self) -> View { self.round.view() } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for Proposal where D: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let round = Round::arbitrary(u)?; let parent = View::arbitrary(u)?; let payload = D::arbitrary(u)?; Ok(Self { round, parent, payload, }) } } /// Validator vote that endorses a proposal for notarization. #[derive(Clone, Debug)] pub struct Notarize { /// Proposal being notarized. pub proposal: Proposal, /// Scheme-specific attestation material. pub attestation: Attestation, } impl Notarize { /// Signs a notarize vote for the provided proposal. pub fn sign(scheme: &S, namespace: &[u8], proposal: Proposal) -> Option where S: scheme::Scheme, { let attestation = scheme.sign::( namespace, Subject::Notarize { proposal: &proposal, }, )?; Some(Self { proposal, attestation, }) } /// Verifies the notarize vote against the provided signing scheme. /// /// This ensures that the notarize signature is valid for the claimed proposal. pub fn verify(&self, scheme: &S, namespace: &[u8]) -> bool where S: scheme::Scheme, { scheme.verify_attestation::( namespace, Subject::Notarize { proposal: &self.proposal, }, &self.attestation, ) } /// Returns the round associated with this notarize vote. pub const fn round(&self) -> Round { self.proposal.round } } impl PartialEq for Notarize { fn eq(&self, other: &Self) -> bool { self.proposal == other.proposal && self.attestation == other.attestation } } impl Eq for Notarize {} impl Hash for Notarize { fn hash(&self, state: &mut H) { self.proposal.hash(state); self.attestation.hash(state); } } impl Write for Notarize { fn write(&self, writer: &mut impl BufMut) { self.proposal.write(writer); self.attestation.write(writer); } } impl EncodeSize for Notarize { fn encode_size(&self) -> usize { self.proposal.encode_size() + self.attestation.encode_size() } } impl Read for Notarize { type Cfg = (); fn read_cfg(reader: &mut impl Buf, _: &()) -> Result { let proposal = Proposal::read(reader)?; let attestation = Attestation::read(reader)?; Ok(Self { proposal, attestation, }) } } impl Attributable for Notarize { fn signer(&self) -> u32 { self.attestation.signer } } impl Epochable for Notarize { fn epoch(&self) -> Epoch { self.proposal.epoch() } } impl Viewable for Notarize { fn view(&self) -> View { self.proposal.view() } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for Notarize where S::Signature: for<'a> arbitrary::Arbitrary<'a>, D: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let proposal = Proposal::arbitrary(u)?; let attestation = Attestation::arbitrary(u)?; Ok(Self { proposal, attestation, }) } } /// Aggregated notarization certificate recovered from notarize votes. /// When a proposal is notarized, it means at least 2f+1 validators have voted for it. /// /// Some signing schemes (like [`super::scheme::bls12381_threshold`]) embed an additional /// randomness seed in the certificate. For threshold signatures, the seed can be accessed /// via [`super::scheme::bls12381_threshold::Seedable::seed`]. #[derive(Clone, Debug)] pub struct Notarization { /// The proposal that has been notarized. pub proposal: Proposal, /// The recovered certificate for the proposal. pub certificate: S::Certificate, } impl Notarization { /// Builds a notarization certificate from notarize votes for the same proposal. pub fn from_notarizes<'a>( scheme: &S, notarizes: impl IntoIterator>, ) -> Option { let mut iter = notarizes.into_iter().peekable(); let proposal = iter.peek()?.proposal.clone(); let certificate = scheme.assemble(iter.map(|n| n.attestation.clone()))?; Some(Self { proposal, certificate, }) } /// Verifies the notarization certificate against the provided signing scheme. /// /// This ensures that the certificate is valid for the claimed proposal. pub fn verify(&self, rng: &mut R, scheme: &S, namespace: &[u8]) -> bool where S: scheme::Scheme, { scheme.verify_certificate::<_, D>( rng, namespace, Subject::Notarize { proposal: &self.proposal, }, &self.certificate, ) } /// Returns the round associated with the notarized proposal. pub const fn round(&self) -> Round { self.proposal.round } } impl PartialEq for Notarization { fn eq(&self, other: &Self) -> bool { self.proposal == other.proposal && self.certificate == other.certificate } } impl Eq for Notarization {} impl Hash for Notarization { fn hash(&self, state: &mut H) { self.proposal.hash(state); self.certificate.hash(state); } } impl Write for Notarization { fn write(&self, writer: &mut impl BufMut) { self.proposal.write(writer); self.certificate.write(writer); } } impl EncodeSize for Notarization { fn encode_size(&self) -> usize { self.proposal.encode_size() + self.certificate.encode_size() } } impl Read for Notarization { type Cfg = ::Cfg; fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result { let proposal = Proposal::read(reader)?; let certificate = S::Certificate::read_cfg(reader, cfg)?; Ok(Self { proposal, certificate, }) } } impl Epochable for Notarization { fn epoch(&self) -> Epoch { self.proposal.epoch() } } impl Viewable for Notarization { fn view(&self) -> View { self.proposal.view() } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for Notarization where S::Certificate: for<'a> arbitrary::Arbitrary<'a>, D: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let proposal = Proposal::arbitrary(u)?; let certificate = S::Certificate::arbitrary(u)?; Ok(Self { proposal, certificate, }) } } /// Validator vote for nullifying the current round, i.e. skip the current round. /// This is typically used when the leader is unresponsive or fails to propose a valid block. #[derive(Clone, Debug)] pub struct Nullify { /// The round to be nullified (skipped). pub round: Round, /// Scheme-specific attestation material. pub attestation: Attestation, } impl PartialEq for Nullify { fn eq(&self, other: &Self) -> bool { self.round == other.round && self.attestation == other.attestation } } impl Eq for Nullify {} impl Hash for Nullify { fn hash(&self, state: &mut H) { self.round.hash(state); self.attestation.hash(state); } } impl Nullify { /// Signs a nullify vote for the given round. pub fn sign(scheme: &S, namespace: &[u8], round: Round) -> Option where S: scheme::Scheme, { let attestation = scheme.sign::(namespace, Subject::Nullify { round })?; Some(Self { round, attestation }) } /// Verifies the nullify vote against the provided signing scheme. /// /// This ensures that the nullify signature is valid for the given round. pub fn verify(&self, scheme: &S, namespace: &[u8]) -> bool where S: scheme::Scheme, { scheme.verify_attestation::( namespace, Subject::Nullify { round: self.round }, &self.attestation, ) } /// Returns the round associated with this nullify vote. pub const fn round(&self) -> Round { self.round } } impl Write for Nullify { fn write(&self, writer: &mut impl BufMut) { self.round.write(writer); self.attestation.write(writer); } } impl EncodeSize for Nullify { fn encode_size(&self) -> usize { self.round.encode_size() + self.attestation.encode_size() } } impl Read for Nullify { type Cfg = (); fn read_cfg(reader: &mut impl Buf, _: &()) -> Result { let round = Round::read(reader)?; let attestation = Attestation::read(reader)?; Ok(Self { round, attestation }) } } impl Attributable for Nullify { fn signer(&self) -> u32 { self.attestation.signer } } impl Epochable for Nullify { fn epoch(&self) -> Epoch { self.round.epoch() } } impl Viewable for Nullify { fn view(&self) -> View { self.round.view() } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for Nullify where S::Signature: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let round = Round::arbitrary(u)?; let attestation = Attestation::arbitrary(u)?; Ok(Self { round, attestation }) } } /// Aggregated nullification certificate recovered from nullify votes. /// When a view is nullified, the consensus moves to the next view without finalizing a block. #[derive(Clone, Debug)] pub struct Nullification { /// The round in which this nullification is made. pub round: Round, /// The recovered certificate for the nullification. pub certificate: S::Certificate, } impl Nullification { /// Builds a nullification certificate from nullify votes from the same round. pub fn from_nullifies<'a>( scheme: &S, nullifies: impl IntoIterator>, ) -> Option { let mut iter = nullifies.into_iter().peekable(); let round = iter.peek()?.round; let certificate = scheme.assemble(iter.map(|n| n.attestation.clone()))?; Some(Self { round, certificate }) } /// Verifies the nullification certificate against the provided signing scheme. /// /// This ensures that the certificate is valid for the claimed round. pub fn verify( &self, rng: &mut R, scheme: &S, namespace: &[u8], ) -> bool where S: scheme::Scheme, { scheme.verify_certificate::<_, D>( rng, namespace, Subject::Nullify { round: self.round }, &self.certificate, ) } /// Returns the round associated with this nullification. pub const fn round(&self) -> Round { self.round } } impl PartialEq for Nullification { fn eq(&self, other: &Self) -> bool { self.round == other.round && self.certificate == other.certificate } } impl Eq for Nullification {} impl Hash for Nullification { fn hash(&self, state: &mut H) { self.round.hash(state); self.certificate.hash(state); } } impl Write for Nullification { fn write(&self, writer: &mut impl BufMut) { self.round.write(writer); self.certificate.write(writer); } } impl EncodeSize for Nullification { fn encode_size(&self) -> usize { self.round.encode_size() + self.certificate.encode_size() } } impl Read for Nullification { type Cfg = ::Cfg; fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result { let round = Round::read(reader)?; let certificate = S::Certificate::read_cfg(reader, cfg)?; Ok(Self { round, certificate }) } } impl Epochable for Nullification { fn epoch(&self) -> Epoch { self.round.epoch() } } impl Viewable for Nullification { fn view(&self) -> View { self.round.view() } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for Nullification where S::Certificate: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let round = Round::arbitrary(u)?; let certificate = S::Certificate::arbitrary(u)?; Ok(Self { round, certificate }) } } /// Validator vote to finalize a proposal. /// This happens after a proposal has been notarized, confirming it as the canonical block /// for this round. #[derive(Clone, Debug)] pub struct Finalize { /// Proposal being finalized. pub proposal: Proposal, /// Scheme-specific attestation material. pub attestation: Attestation, } impl Finalize { /// Signs a finalize vote for the provided proposal. pub fn sign(scheme: &S, namespace: &[u8], proposal: Proposal) -> Option where S: scheme::Scheme, { let attestation = scheme.sign::( namespace, Subject::Finalize { proposal: &proposal, }, )?; Some(Self { proposal, attestation, }) } /// Verifies the finalize vote against the provided signing scheme. /// /// This ensures that the finalize signature is valid for the claimed proposal. pub fn verify(&self, scheme: &S, namespace: &[u8]) -> bool where S: scheme::Scheme, { scheme.verify_attestation::( namespace, Subject::Finalize { proposal: &self.proposal, }, &self.attestation, ) } /// Returns the round associated with this finalize vote. pub const fn round(&self) -> Round { self.proposal.round } } impl PartialEq for Finalize { fn eq(&self, other: &Self) -> bool { self.proposal == other.proposal && self.attestation == other.attestation } } impl Eq for Finalize {} impl Hash for Finalize { fn hash(&self, state: &mut H) { self.proposal.hash(state); self.attestation.hash(state); } } impl Write for Finalize { fn write(&self, writer: &mut impl BufMut) { self.proposal.write(writer); self.attestation.write(writer); } } impl EncodeSize for Finalize { fn encode_size(&self) -> usize { self.proposal.encode_size() + self.attestation.encode_size() } } impl Read for Finalize { type Cfg = (); fn read_cfg(reader: &mut impl Buf, _: &()) -> Result { let proposal = Proposal::read(reader)?; let attestation = Attestation::read(reader)?; Ok(Self { proposal, attestation, }) } } impl Attributable for Finalize { fn signer(&self) -> u32 { self.attestation.signer } } impl Epochable for Finalize { fn epoch(&self) -> Epoch { self.proposal.epoch() } } impl Viewable for Finalize { fn view(&self) -> View { self.proposal.view() } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for Finalize where S::Signature: for<'a> arbitrary::Arbitrary<'a>, D: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let proposal = Proposal::arbitrary(u)?; let attestation = Attestation::arbitrary(u)?; Ok(Self { proposal, attestation, }) } } /// Aggregated finalization certificate recovered from finalize votes. /// When a proposal is finalized, it becomes the canonical block for its view. /// /// Some signing schemes (like [`super::scheme::bls12381_threshold`]) embed an additional /// randomness seed in the certificate. For threshold signatures, the seed can be accessed /// via [`super::scheme::bls12381_threshold::Seedable::seed`]. #[derive(Clone, Debug)] pub struct Finalization { /// The proposal that has been finalized. pub proposal: Proposal, /// The recovered certificate for the proposal. pub certificate: S::Certificate, } impl Finalization { /// Builds a finalization certificate from finalize votes for the same proposal. pub fn from_finalizes<'a>( scheme: &S, finalizes: impl IntoIterator>, ) -> Option { let mut iter = finalizes.into_iter().peekable(); let proposal = iter.peek()?.proposal.clone(); let certificate = scheme.assemble(iter.map(|f| f.attestation.clone()))?; Some(Self { proposal, certificate, }) } /// Verifies the finalization certificate against the provided signing scheme. /// /// This ensures that the certificate is valid for the claimed proposal. pub fn verify(&self, rng: &mut R, scheme: &S, namespace: &[u8]) -> bool where S: scheme::Scheme, { scheme.verify_certificate::<_, D>( rng, namespace, Subject::Finalize { proposal: &self.proposal, }, &self.certificate, ) } /// Returns the round associated with the finalized proposal. pub const fn round(&self) -> Round { self.proposal.round } } impl PartialEq for Finalization { fn eq(&self, other: &Self) -> bool { self.proposal == other.proposal && self.certificate == other.certificate } } impl Eq for Finalization {} impl Hash for Finalization { fn hash(&self, state: &mut H) { self.proposal.hash(state); self.certificate.hash(state); } } impl Write for Finalization { fn write(&self, writer: &mut impl BufMut) { self.proposal.write(writer); self.certificate.write(writer); } } impl EncodeSize for Finalization { fn encode_size(&self) -> usize { self.proposal.encode_size() + self.certificate.encode_size() } } impl Read for Finalization { type Cfg = ::Cfg; fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result { let proposal = Proposal::read(reader)?; let certificate = S::Certificate::read_cfg(reader, cfg)?; Ok(Self { proposal, certificate, }) } } impl Epochable for Finalization { fn epoch(&self) -> Epoch { self.proposal.epoch() } } impl Viewable for Finalization { fn view(&self) -> View { self.proposal.view() } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for Finalization where S::Certificate: for<'a> arbitrary::Arbitrary<'a>, D: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let proposal = Proposal::arbitrary(u)?; let certificate = S::Certificate::arbitrary(u)?; Ok(Self { proposal, certificate, }) } } /// Backfiller is a message type for requesting and receiving missing consensus artifacts. /// This is used to synchronize validators that have fallen behind or just joined the network. #[derive(Clone, Debug, PartialEq)] pub enum Backfiller { /// Request for missing notarizations and nullifications Request(Request), /// Response containing requested notarizations and nullifications Response(Response), } impl Write for Backfiller { fn write(&self, writer: &mut impl BufMut) { match self { Self::Request(request) => { 0u8.write(writer); request.write(writer); } Self::Response(response) => { 1u8.write(writer); response.write(writer); } } } } impl EncodeSize for Backfiller { fn encode_size(&self) -> usize { 1 + match self { Self::Request(v) => v.encode_size(), Self::Response(v) => v.encode_size(), } } } impl Read for Backfiller { type Cfg = (usize, ::Cfg); fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result { let tag = ::read(reader)?; match tag { 0 => { let (max_len, _) = cfg; let v = Request::read_cfg(reader, max_len)?; Ok(Self::Request(v)) } 1 => { let v = Response::::read_cfg(reader, cfg)?; Ok(Self::Response(v)) } _ => Err(Error::Invalid( "consensus::simplex::Backfiller", "Invalid type", )), } } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for Backfiller where S::Certificate: for<'a> arbitrary::Arbitrary<'a>, D: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let tag = u.int_in_range(0..=1)?; match tag { 0 => { let v = Request::arbitrary(u)?; Ok(Self::Request(v)) } 1 => { let v = Response::::arbitrary(u)?; Ok(Self::Response(v)) } _ => unreachable!(), } } } /// Request is a message to request missing notarizations and nullifications. /// This is used by validators who need to catch up with the consensus state. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Request { /// Unique identifier for this request (used to match responses) pub id: u64, /// Views for which notarizations are requested pub notarizations: Vec, /// Views for which nullifications are requested pub nullifications: Vec, } impl Request { /// Creates a new request for missing notarizations and nullifications. pub const fn new(id: u64, notarizations: Vec, nullifications: Vec) -> Self { Self { id, notarizations, nullifications, } } } impl Write for Request { fn write(&self, writer: &mut impl BufMut) { UInt(self.id).write(writer); self.notarizations.write(writer); self.nullifications.write(writer); } } impl EncodeSize for Request { fn encode_size(&self) -> usize { UInt(self.id).encode_size() + self.notarizations.encode_size() + self.nullifications.encode_size() } } impl Read for Request { type Cfg = usize; fn read_cfg(reader: &mut impl Buf, max_len: &usize) -> Result { let id = UInt::read(reader)?.into(); let mut views = HashSet::new(); let notarizations = Vec::::read_range(reader, ..=*max_len)?; for view in notarizations.iter() { if !views.insert(view) { return Err(Error::Invalid( "consensus::simplex::Request", "Duplicate notarization", )); } } let remaining = max_len - notarizations.len(); views.clear(); let nullifications = Vec::::read_range(reader, ..=remaining)?; for view in nullifications.iter() { if !views.insert(view) { return Err(Error::Invalid( "consensus::simplex::Request", "Duplicate nullification", )); } } Ok(Self { id, notarizations, nullifications, }) } } /// Response is a message containing the requested notarizations and nullifications. /// This is sent in response to a Request message. #[derive(Clone, Debug, PartialEq)] pub struct Response { /// Identifier matching the original request pub id: u64, /// Notarizations for the requested views pub notarizations: Vec>, /// Nullifications for the requested views pub nullifications: Vec>, } impl Response { /// Creates a new response with the given id, notarizations, and nullifications. pub const fn new( id: u64, notarizations: Vec>, nullifications: Vec>, ) -> Self { Self { id, notarizations, nullifications, } } /// Verifies the certificates contained in this response against the signing scheme. pub fn verify(&self, rng: &mut R, scheme: &S, namespace: &[u8]) -> bool where S: scheme::Scheme, { // Prepare to verify if self.notarizations.is_empty() && self.nullifications.is_empty() { return true; } let notarizations = self.notarizations.iter().map(|notarization| { let context = Subject::Notarize { proposal: ¬arization.proposal, }; (context, ¬arization.certificate) }); let nullifications = self.nullifications.iter().map(|nullification| { let context = Subject::Nullify { round: nullification.round, }; (context, &nullification.certificate) }); scheme.verify_certificates::<_, D, _>(rng, namespace, notarizations.chain(nullifications)) } } impl Write for Response { fn write(&self, writer: &mut impl BufMut) { UInt(self.id).write(writer); self.notarizations.write(writer); self.nullifications.write(writer); } } impl EncodeSize for Response { fn encode_size(&self) -> usize { UInt(self.id).encode_size() + self.notarizations.encode_size() + self.nullifications.encode_size() } } impl Read for Response { type Cfg = (usize, ::Cfg); fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result { let (max_len, certificate_cfg) = cfg; let id = UInt::read(reader)?.into(); let mut views = HashSet::new(); let notarizations = Vec::>::read_cfg( reader, &((..=*max_len).into(), certificate_cfg.clone()), )?; for notarization in notarizations.iter() { if !views.insert(notarization.view()) { return Err(Error::Invalid( "consensus::simplex::Response", "Duplicate notarization", )); } } let remaining = max_len - notarizations.len(); views.clear(); let nullifications = Vec::>::read_cfg( reader, &((..=remaining).into(), certificate_cfg.clone()), )?; for nullification in nullifications.iter() { if !views.insert(nullification.view()) { return Err(Error::Invalid( "consensus::simplex::Response", "Duplicate nullification", )); } } Ok(Self { id, notarizations, nullifications, }) } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for Response where S::Certificate: for<'a> arbitrary::Arbitrary<'a>, D: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let id = u.arbitrary()?; let notarizations = u.arbitrary()?; let nullifications = u.arbitrary()?; Ok(Self { id, notarizations, nullifications, }) } } /// Activity represents all possible activities that can occur in the consensus protocol. /// This includes both regular consensus messages and fault evidence. /// /// # Verification /// /// Some activities issued by consensus are not guaranteed to be cryptographically verified (i.e. if not needed /// to produce a minimum quorum certificate). Use [`Activity::verified`] to check if an activity may not be verified, /// and [`Activity::verify`] to perform verification. /// /// # Activity Filtering /// /// For **non-attributable** schemes like [`crate::simplex::scheme::bls12381_threshold`], exposing /// per-validator activity as fault evidence is not safe: with threshold cryptography, any `t` valid partial signatures can /// be used to forge a partial signature for any player. /// /// Use [`crate::simplex::scheme::reporter::AttributableReporter`] to automatically filter and /// verify activities based on [`Scheme::is_attributable`]. #[derive(Clone, Debug)] pub enum Activity { /// A validator's notarize vote over a proposal. Notarize(Notarize), /// A recovered certificate for a notarization (scheme-specific). Notarization(Notarization), /// A notarization was locally certified. Certification(Notarization), /// A validator's nullify vote used to skip the current view. Nullify(Nullify), /// A recovered certificate for a nullification (scheme-specific). Nullification(Nullification), /// A validator's finalize vote over a proposal. Finalize(Finalize), /// A recovered certificate for a finalization (scheme-specific). Finalization(Finalization), /// Evidence of a validator sending conflicting notarizes (Byzantine behavior). ConflictingNotarize(ConflictingNotarize), /// Evidence of a validator sending conflicting finalizes (Byzantine behavior). ConflictingFinalize(ConflictingFinalize), /// Evidence of a validator sending both nullify and finalize for the same view (Byzantine behavior). NullifyFinalize(NullifyFinalize), } impl PartialEq for Activity { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Notarize(a), Self::Notarize(b)) => a == b, (Self::Notarization(a), Self::Notarization(b)) => a == b, (Self::Certification(a), Self::Certification(b)) => a == b, (Self::Nullify(a), Self::Nullify(b)) => a == b, (Self::Nullification(a), Self::Nullification(b)) => a == b, (Self::Finalize(a), Self::Finalize(b)) => a == b, (Self::Finalization(a), Self::Finalization(b)) => a == b, (Self::ConflictingNotarize(a), Self::ConflictingNotarize(b)) => a == b, (Self::ConflictingFinalize(a), Self::ConflictingFinalize(b)) => a == b, (Self::NullifyFinalize(a), Self::NullifyFinalize(b)) => a == b, _ => false, } } } impl Eq for Activity {} impl Hash for Activity { fn hash(&self, state: &mut H) { match self { Self::Notarize(v) => { 0u8.hash(state); v.hash(state); } Self::Notarization(v) => { 1u8.hash(state); v.hash(state); } Self::Certification(v) => { 2u8.hash(state); v.hash(state); } Self::Nullify(v) => { 3u8.hash(state); v.hash(state); } Self::Nullification(v) => { 4u8.hash(state); v.hash(state); } Self::Finalize(v) => { 5u8.hash(state); v.hash(state); } Self::Finalization(v) => { 6u8.hash(state); v.hash(state); } Self::ConflictingNotarize(v) => { 7u8.hash(state); v.hash(state); } Self::ConflictingFinalize(v) => { 8u8.hash(state); v.hash(state); } Self::NullifyFinalize(v) => { 9u8.hash(state); v.hash(state); } } } } impl Activity { /// Indicates whether the activity is guaranteed to have been verified by consensus. pub const fn verified(&self) -> bool { match self { Self::Notarize(_) => false, Self::Notarization(_) => true, Self::Certification(_) => false, Self::Nullify(_) => false, Self::Nullification(_) => true, Self::Finalize(_) => false, Self::Finalization(_) => true, Self::ConflictingNotarize(_) => false, Self::ConflictingFinalize(_) => false, Self::NullifyFinalize(_) => false, } } /// Verifies the validity of this activity against the signing scheme. /// /// This method **always** performs verification regardless of whether the activity has been /// previously verified. Callers can use [`Activity::verified`] to check if verification is /// necessary before calling this method. pub fn verify(&self, rng: &mut R, scheme: &S, namespace: &[u8]) -> bool where S: scheme::Scheme, { match self { Self::Notarize(n) => n.verify(scheme, namespace), Self::Notarization(n) => n.verify(rng, scheme, namespace), Self::Certification(n) => n.verify(rng, scheme, namespace), Self::Nullify(n) => n.verify(scheme, namespace), Self::Nullification(n) => n.verify(rng, scheme, namespace), Self::Finalize(f) => f.verify(scheme, namespace), Self::Finalization(f) => f.verify(rng, scheme, namespace), Self::ConflictingNotarize(c) => c.verify(scheme, namespace), Self::ConflictingFinalize(c) => c.verify(scheme, namespace), Self::NullifyFinalize(c) => c.verify(scheme, namespace), } } } impl Write for Activity { fn write(&self, writer: &mut impl BufMut) { match self { Self::Notarize(v) => { 0u8.write(writer); v.write(writer); } Self::Notarization(v) => { 1u8.write(writer); v.write(writer); } Self::Certification(v) => { 2u8.write(writer); v.write(writer); } Self::Nullify(v) => { 3u8.write(writer); v.write(writer); } Self::Nullification(v) => { 4u8.write(writer); v.write(writer); } Self::Finalize(v) => { 5u8.write(writer); v.write(writer); } Self::Finalization(v) => { 6u8.write(writer); v.write(writer); } Self::ConflictingNotarize(v) => { 7u8.write(writer); v.write(writer); } Self::ConflictingFinalize(v) => { 8u8.write(writer); v.write(writer); } Self::NullifyFinalize(v) => { 9u8.write(writer); v.write(writer); } } } } impl EncodeSize for Activity { fn encode_size(&self) -> usize { 1 + match self { Self::Notarize(v) => v.encode_size(), Self::Notarization(v) => v.encode_size(), Self::Certification(v) => v.encode_size(), Self::Nullify(v) => v.encode_size(), Self::Nullification(v) => v.encode_size(), Self::Finalize(v) => v.encode_size(), Self::Finalization(v) => v.encode_size(), Self::ConflictingNotarize(v) => v.encode_size(), Self::ConflictingFinalize(v) => v.encode_size(), Self::NullifyFinalize(v) => v.encode_size(), } } } impl Read for Activity { type Cfg = ::Cfg; fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result { let tag = ::read(reader)?; match tag { 0 => { let v = Notarize::::read(reader)?; Ok(Self::Notarize(v)) } 1 => { let v = Notarization::::read_cfg(reader, cfg)?; Ok(Self::Notarization(v)) } 2 => { let v = Notarization::::read_cfg(reader, cfg)?; Ok(Self::Certification(v)) } 3 => { let v = Nullify::::read(reader)?; Ok(Self::Nullify(v)) } 4 => { let v = Nullification::::read_cfg(reader, cfg)?; Ok(Self::Nullification(v)) } 5 => { let v = Finalize::::read(reader)?; Ok(Self::Finalize(v)) } 6 => { let v = Finalization::::read_cfg(reader, cfg)?; Ok(Self::Finalization(v)) } 7 => { let v = ConflictingNotarize::::read(reader)?; Ok(Self::ConflictingNotarize(v)) } 8 => { let v = ConflictingFinalize::::read(reader)?; Ok(Self::ConflictingFinalize(v)) } 9 => { let v = NullifyFinalize::::read(reader)?; Ok(Self::NullifyFinalize(v)) } _ => Err(Error::Invalid( "consensus::simplex::Activity", "Invalid type", )), } } } impl Epochable for Activity { fn epoch(&self) -> Epoch { match self { Self::Notarize(v) => v.epoch(), Self::Notarization(v) => v.epoch(), Self::Certification(v) => v.epoch(), Self::Nullify(v) => v.epoch(), Self::Nullification(v) => v.epoch(), Self::Finalize(v) => v.epoch(), Self::Finalization(v) => v.epoch(), Self::ConflictingNotarize(v) => v.epoch(), Self::ConflictingFinalize(v) => v.epoch(), Self::NullifyFinalize(v) => v.epoch(), } } } impl Viewable for Activity { fn view(&self) -> View { match self { Self::Notarize(v) => v.view(), Self::Notarization(v) => v.view(), Self::Certification(v) => v.view(), Self::Nullify(v) => v.view(), Self::Nullification(v) => v.view(), Self::Finalize(v) => v.view(), Self::Finalization(v) => v.view(), Self::ConflictingNotarize(v) => v.view(), Self::ConflictingFinalize(v) => v.view(), Self::NullifyFinalize(v) => v.view(), } } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for Activity where S::Signature: for<'a> arbitrary::Arbitrary<'a>, S::Certificate: for<'a> arbitrary::Arbitrary<'a>, D: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let tag = u.int_in_range(0..=9)?; match tag { 0 => { let v = Notarize::::arbitrary(u)?; Ok(Self::Notarize(v)) } 1 => { let v = Notarization::::arbitrary(u)?; Ok(Self::Notarization(v)) } 2 => { let v = Notarization::::arbitrary(u)?; Ok(Self::Certification(v)) } 3 => { let v = Nullify::::arbitrary(u)?; Ok(Self::Nullify(v)) } 4 => { let v = Nullification::::arbitrary(u)?; Ok(Self::Nullification(v)) } 5 => { let v = Finalize::::arbitrary(u)?; Ok(Self::Finalize(v)) } 6 => { let v = Finalization::::arbitrary(u)?; Ok(Self::Finalization(v)) } 7 => { let v = ConflictingNotarize::::arbitrary(u)?; Ok(Self::ConflictingNotarize(v)) } 8 => { let v = ConflictingFinalize::::arbitrary(u)?; Ok(Self::ConflictingFinalize(v)) } 9 => { let v = NullifyFinalize::::arbitrary(u)?; Ok(Self::NullifyFinalize(v)) } _ => unreachable!(), } } } /// ConflictingNotarize represents evidence of a Byzantine validator sending conflicting notarizes. /// This is used to prove that a validator has equivocated (voted for different proposals in the same view). #[derive(Clone, Debug)] pub struct ConflictingNotarize { /// The first conflicting notarize notarize_1: Notarize, /// The second conflicting notarize notarize_2: Notarize, } impl PartialEq for ConflictingNotarize { fn eq(&self, other: &Self) -> bool { self.notarize_1 == other.notarize_1 && self.notarize_2 == other.notarize_2 } } impl Eq for ConflictingNotarize {} impl Hash for ConflictingNotarize { fn hash(&self, state: &mut H) { self.notarize_1.hash(state); self.notarize_2.hash(state); } } impl ConflictingNotarize { /// Creates a new conflicting notarize evidence from two conflicting notarizes. pub fn new(notarize_1: Notarize, notarize_2: Notarize) -> Self { assert_eq!(notarize_1.round(), notarize_2.round()); assert_eq!(notarize_1.signer(), notarize_2.signer()); Self { notarize_1, notarize_2, } } /// Verifies that both conflicting signatures are valid, proving Byzantine behavior. pub fn verify(&self, scheme: &S, namespace: &[u8]) -> bool where S: scheme::Scheme, { self.notarize_1.verify(scheme, namespace) && self.notarize_2.verify(scheme, namespace) } } impl Attributable for ConflictingNotarize { fn signer(&self) -> u32 { self.notarize_1.signer() } } impl Epochable for ConflictingNotarize { fn epoch(&self) -> Epoch { self.notarize_1.epoch() } } impl Viewable for ConflictingNotarize { fn view(&self) -> View { self.notarize_1.view() } } impl Write for ConflictingNotarize { fn write(&self, writer: &mut impl BufMut) { self.notarize_1.write(writer); self.notarize_2.write(writer); } } impl Read for ConflictingNotarize { type Cfg = (); fn read_cfg(reader: &mut impl Buf, _: &()) -> Result { let notarize_1 = Notarize::read(reader)?; let notarize_2 = Notarize::read(reader)?; if notarize_1.signer() != notarize_2.signer() || notarize_1.round() != notarize_2.round() { return Err(Error::Invalid( "consensus::simplex::ConflictingNotarize", "invalid conflicting notarize", )); } Ok(Self { notarize_1, notarize_2, }) } } impl EncodeSize for ConflictingNotarize { fn encode_size(&self) -> usize { self.notarize_1.encode_size() + self.notarize_2.encode_size() } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for ConflictingNotarize where S::Signature: for<'a> arbitrary::Arbitrary<'a>, D: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let notarize_1 = Notarize::arbitrary(u)?; let notarize_2 = Notarize::arbitrary(u)?; Ok(Self { notarize_1, notarize_2, }) } } /// ConflictingFinalize represents evidence of a Byzantine validator sending conflicting finalizes. /// Similar to ConflictingNotarize, but for finalizes. #[derive(Clone, Debug)] pub struct ConflictingFinalize { /// The second conflicting finalize finalize_1: Finalize, /// The second conflicting finalize finalize_2: Finalize, } impl PartialEq for ConflictingFinalize { fn eq(&self, other: &Self) -> bool { self.finalize_1 == other.finalize_1 && self.finalize_2 == other.finalize_2 } } impl Eq for ConflictingFinalize {} impl Hash for ConflictingFinalize { fn hash(&self, state: &mut H) { self.finalize_1.hash(state); self.finalize_2.hash(state); } } impl ConflictingFinalize { /// Creates a new conflicting finalize evidence from two conflicting finalizes. pub fn new(finalize_1: Finalize, finalize_2: Finalize) -> Self { assert_eq!(finalize_1.round(), finalize_2.round()); assert_eq!(finalize_1.signer(), finalize_2.signer()); Self { finalize_1, finalize_2, } } /// Verifies that both conflicting signatures are valid, proving Byzantine behavior. pub fn verify(&self, scheme: &S, namespace: &[u8]) -> bool where S: scheme::Scheme, { self.finalize_1.verify(scheme, namespace) && self.finalize_2.verify(scheme, namespace) } } impl Attributable for ConflictingFinalize { fn signer(&self) -> u32 { self.finalize_1.signer() } } impl Epochable for ConflictingFinalize { fn epoch(&self) -> Epoch { self.finalize_1.epoch() } } impl Viewable for ConflictingFinalize { fn view(&self) -> View { self.finalize_1.view() } } impl Write for ConflictingFinalize { fn write(&self, writer: &mut impl BufMut) { self.finalize_1.write(writer); self.finalize_2.write(writer); } } impl Read for ConflictingFinalize { type Cfg = (); fn read_cfg(reader: &mut impl Buf, _: &()) -> Result { let finalize_1 = Finalize::read(reader)?; let finalize_2 = Finalize::read(reader)?; if finalize_1.signer() != finalize_2.signer() || finalize_1.round() != finalize_2.round() { return Err(Error::Invalid( "consensus::simplex::ConflictingFinalize", "invalid conflicting finalize", )); } Ok(Self { finalize_1, finalize_2, }) } } impl EncodeSize for ConflictingFinalize { fn encode_size(&self) -> usize { self.finalize_1.encode_size() + self.finalize_2.encode_size() } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for ConflictingFinalize where S::Signature: for<'a> arbitrary::Arbitrary<'a>, D: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let finalize_1 = Finalize::arbitrary(u)?; let finalize_2 = Finalize::arbitrary(u)?; Ok(Self { finalize_1, finalize_2, }) } } /// NullifyFinalize represents evidence of a Byzantine validator sending both a nullify and finalize /// for the same view, which is contradictory behavior (a validator should either try to skip a view OR /// finalize a proposal, not both). #[derive(Clone, Debug)] pub struct NullifyFinalize { /// The conflicting nullify nullify: Nullify, /// The conflicting finalize finalize: Finalize, } impl PartialEq for NullifyFinalize { fn eq(&self, other: &Self) -> bool { self.nullify == other.nullify && self.finalize == other.finalize } } impl Eq for NullifyFinalize {} impl Hash for NullifyFinalize { fn hash(&self, state: &mut H) { self.nullify.hash(state); self.finalize.hash(state); } } impl NullifyFinalize { /// Creates a new nullify-finalize evidence from a nullify and a finalize. pub fn new(nullify: Nullify, finalize: Finalize) -> Self { assert_eq!(nullify.round, finalize.round()); assert_eq!(nullify.signer(), finalize.signer()); Self { nullify, finalize } } /// Verifies that both the nullify and finalize signatures are valid, proving Byzantine behavior. pub fn verify(&self, scheme: &S, namespace: &[u8]) -> bool where S: scheme::Scheme, { self.nullify.verify(scheme, namespace) && self.finalize.verify(scheme, namespace) } } impl Attributable for NullifyFinalize { fn signer(&self) -> u32 { self.nullify.signer() } } impl Epochable for NullifyFinalize { fn epoch(&self) -> Epoch { self.nullify.epoch() } } impl Viewable for NullifyFinalize { fn view(&self) -> View { self.nullify.view() } } impl Write for NullifyFinalize { fn write(&self, writer: &mut impl BufMut) { self.nullify.write(writer); self.finalize.write(writer); } } impl Read for NullifyFinalize { type Cfg = (); fn read_cfg(reader: &mut impl Buf, _: &()) -> Result { let nullify = Nullify::read(reader)?; let finalize = Finalize::read(reader)?; if nullify.signer() != finalize.signer() || nullify.round != finalize.round() { return Err(Error::Invalid( "consensus::simplex::NullifyFinalize", "mismatched signatures", )); } Ok(Self { nullify, finalize }) } } impl EncodeSize for NullifyFinalize { fn encode_size(&self) -> usize { self.nullify.encode_size() + self.finalize.encode_size() } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for NullifyFinalize where S::Signature: for<'a> arbitrary::Arbitrary<'a>, D: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let nullify = Nullify::arbitrary(u)?; let finalize = Finalize::arbitrary(u)?; Ok(Self { nullify, finalize }) } } #[cfg(test)] mod tests { use super::*; use crate::simplex::scheme::{bls12381_multisig, bls12381_threshold, ed25519, Scheme}; use commonware_codec::{Decode, DecodeExt, Encode}; use commonware_cryptography::{ bls12381::primitives::variant::{MinPk, MinSig}, certificate::mocks::Fixture, sha256::Digest as Sha256, }; use commonware_utils::{quorum, quorum_from_slice}; use rand::{ rngs::{OsRng, StdRng}, SeedableRng, }; const NAMESPACE: &[u8] = b"test"; // Helper function to create a sample digest fn sample_digest(v: u8) -> Sha256 { Sha256::from([v; 32]) // Simple fixed digest for testing } /// Generate a fixture using the provided generator function. fn setup(n: u32, fixture: F) -> Fixture where F: FnOnce(&mut StdRng, u32) -> Fixture, { let mut rng = StdRng::seed_from_u64(0); fixture(&mut rng, n) } /// Generate a fixture using the provided generator function with a specific seed. fn setup_seeded(n: u32, seed: u64, fixture: F) -> Fixture where F: FnOnce(&mut StdRng, u32) -> Fixture, { let mut rng = StdRng::seed_from_u64(seed); fixture(&mut rng, n) } #[test] fn test_proposal_encode_decode() { let proposal = Proposal::new( Round::new(Epoch::new(0), View::new(10)), View::new(5), sample_digest(1), ); let encoded = proposal.encode(); let decoded = Proposal::::decode(encoded).unwrap(); assert_eq!(proposal, decoded); } fn notarize_encode_decode(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, u32) -> Fixture, { let fixture = setup(5, fixture); let round = Round::new(Epoch::new(0), View::new(10)); let proposal = Proposal::new(round, View::new(5), sample_digest(1)); let notarize = Notarize::sign(&fixture.schemes[0], NAMESPACE, proposal).unwrap(); let encoded = notarize.encode(); let decoded = Notarize::decode(encoded).unwrap(); assert_eq!(notarize, decoded); assert!(decoded.verify(&fixture.schemes[0], NAMESPACE)); } #[test] fn test_notarize_encode_decode() { notarize_encode_decode(ed25519::fixture); notarize_encode_decode(bls12381_multisig::fixture::); notarize_encode_decode(bls12381_multisig::fixture::); notarize_encode_decode(bls12381_threshold::fixture::); notarize_encode_decode(bls12381_threshold::fixture::); } fn notarization_encode_decode(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, u32) -> Fixture, { let fixture = setup(5, fixture); let proposal = Proposal::new( Round::new(Epoch::new(0), View::new(10)), View::new(5), sample_digest(1), ); let notarizes: Vec<_> = fixture .schemes .iter() .map(|scheme| Notarize::sign(scheme, NAMESPACE, proposal.clone()).unwrap()) .collect(); let notarization = Notarization::from_notarizes(&fixture.schemes[0], ¬arizes).unwrap(); let encoded = notarization.encode(); let cfg = fixture.schemes[0].certificate_codec_config(); let decoded = Notarization::decode_cfg(encoded, &cfg).unwrap(); assert_eq!(notarization, decoded); assert!(decoded.verify(&mut OsRng, &fixture.schemes[0], NAMESPACE)); } #[test] fn test_notarization_encode_decode() { notarization_encode_decode(ed25519::fixture); notarization_encode_decode(bls12381_multisig::fixture::); notarization_encode_decode(bls12381_multisig::fixture::); notarization_encode_decode(bls12381_threshold::fixture::); notarization_encode_decode(bls12381_threshold::fixture::); } fn nullify_encode_decode(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, u32) -> Fixture, { let fixture = setup(5, fixture); let round = Round::new(Epoch::new(0), View::new(10)); let nullify = Nullify::sign::(&fixture.schemes[0], NAMESPACE, round).unwrap(); let encoded = nullify.encode(); let decoded = Nullify::decode(encoded).unwrap(); assert_eq!(nullify, decoded); assert!(decoded.verify::(&fixture.schemes[0], NAMESPACE)); } #[test] fn test_nullify_encode_decode() { nullify_encode_decode(ed25519::fixture); nullify_encode_decode(bls12381_multisig::fixture::); nullify_encode_decode(bls12381_multisig::fixture::); nullify_encode_decode(bls12381_threshold::fixture::); nullify_encode_decode(bls12381_threshold::fixture::); } fn nullification_encode_decode(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, u32) -> Fixture, { let fixture = setup(5, fixture); let round = Round::new(Epoch::new(333), View::new(10)); let nullifies: Vec<_> = fixture .schemes .iter() .map(|scheme| Nullify::sign::(scheme, NAMESPACE, round).unwrap()) .collect(); let nullification = Nullification::from_nullifies(&fixture.schemes[0], &nullifies).unwrap(); let encoded = nullification.encode(); let cfg = fixture.schemes[0].certificate_codec_config(); let decoded = Nullification::decode_cfg(encoded, &cfg).unwrap(); assert_eq!(nullification, decoded); assert!(decoded.verify::<_, Sha256>(&mut OsRng, &fixture.schemes[0], NAMESPACE)); } #[test] fn test_nullification_encode_decode() { nullification_encode_decode(ed25519::fixture); nullification_encode_decode(bls12381_multisig::fixture::); nullification_encode_decode(bls12381_multisig::fixture::); nullification_encode_decode(bls12381_threshold::fixture::); nullification_encode_decode(bls12381_threshold::fixture::); } fn finalize_encode_decode(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, u32) -> Fixture, { let fixture = setup(5, fixture); let round = Round::new(Epoch::new(0), View::new(10)); let proposal = Proposal::new(round, View::new(5), sample_digest(1)); let finalize = Finalize::sign(&fixture.schemes[0], NAMESPACE, proposal).unwrap(); let encoded = finalize.encode(); let decoded = Finalize::decode(encoded).unwrap(); assert_eq!(finalize, decoded); assert!(decoded.verify(&fixture.schemes[0], NAMESPACE)); } #[test] fn test_finalize_encode_decode() { finalize_encode_decode(ed25519::fixture); finalize_encode_decode(bls12381_multisig::fixture::); finalize_encode_decode(bls12381_multisig::fixture::); finalize_encode_decode(bls12381_threshold::fixture::); finalize_encode_decode(bls12381_threshold::fixture::); } fn finalization_encode_decode(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, u32) -> Fixture, { let fixture = setup(5, fixture); let round = Round::new(Epoch::new(0), View::new(10)); let proposal = Proposal::new(round, View::new(5), sample_digest(1)); let finalizes: Vec<_> = fixture .schemes .iter() .map(|scheme| Finalize::sign(scheme, NAMESPACE, proposal.clone()).unwrap()) .collect(); let finalization = Finalization::from_finalizes(&fixture.schemes[0], &finalizes).unwrap(); let encoded = finalization.encode(); let cfg = fixture.schemes[0].certificate_codec_config(); let decoded = Finalization::decode_cfg(encoded, &cfg).unwrap(); assert_eq!(finalization, decoded); assert!(decoded.verify(&mut OsRng, &fixture.schemes[0], NAMESPACE)); } #[test] fn test_finalization_encode_decode() { finalization_encode_decode(ed25519::fixture); finalization_encode_decode(bls12381_multisig::fixture::); finalization_encode_decode(bls12381_multisig::fixture::); finalization_encode_decode(bls12381_threshold::fixture::); finalization_encode_decode(bls12381_threshold::fixture::); } fn backfiller_encode_decode(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, u32) -> Fixture, { let fixture = setup(5, fixture); let cfg = fixture.schemes[0].certificate_codec_config(); let request = Request::new( 1, vec![View::new(10), View::new(11)], vec![View::new(12), View::new(13)], ); let encoded_request = Backfiller::::Request(request.clone()).encode(); let decoded_request = Backfiller::::decode_cfg(encoded_request, &(usize::MAX, cfg.clone())) .unwrap(); assert!(matches!(decoded_request, Backfiller::Request(r) if r == request)); let round = Round::new(Epoch::new(0), View::new(10)); let proposal = Proposal::new(round, View::new(5), sample_digest(1)); let notarizes: Vec<_> = fixture .schemes .iter() .map(|scheme| Notarize::sign(scheme, NAMESPACE, proposal.clone()).unwrap()) .collect(); let notarization = Notarization::from_notarizes(&fixture.schemes[0], ¬arizes).unwrap(); let nullifies: Vec<_> = fixture .schemes .iter() .map(|scheme| Nullify::sign::(scheme, NAMESPACE, round).unwrap()) .collect(); let nullification = Nullification::from_nullifies(&fixture.schemes[0], &nullifies).unwrap(); let response = Response::::new(1, vec![notarization], vec![nullification]); let encoded_response = Backfiller::::Response(response.clone()).encode(); let decoded_response = Backfiller::::decode_cfg(encoded_response, &(usize::MAX, cfg)).unwrap(); assert!(matches!(decoded_response, Backfiller::Response(r) if r.id == response.id)); } #[test] fn test_backfiller_encode_decode() { backfiller_encode_decode(ed25519::fixture); backfiller_encode_decode(bls12381_multisig::fixture::); backfiller_encode_decode(bls12381_multisig::fixture::); backfiller_encode_decode(bls12381_threshold::fixture::); backfiller_encode_decode(bls12381_threshold::fixture::); } #[test] fn test_request_encode_decode() { let request = Request::new( 1, vec![View::new(10), View::new(11)], vec![View::new(12), View::new(13)], ); let encoded = request.encode(); let decoded = Request::decode_cfg(encoded, &usize::MAX).unwrap(); assert_eq!(request, decoded); } fn response_encode_decode(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, u32) -> Fixture, { let fixture = setup(5, fixture); let round = Round::new(Epoch::new(0), View::new(10)); let proposal = Proposal::new(round, View::new(5), sample_digest(1)); let notarizes: Vec<_> = fixture .schemes .iter() .map(|scheme| Notarize::sign(scheme, NAMESPACE, proposal.clone()).unwrap()) .collect(); let notarization = Notarization::from_notarizes(&fixture.schemes[0], ¬arizes).unwrap(); let nullifies: Vec<_> = fixture .schemes .iter() .map(|scheme| Nullify::sign::(scheme, NAMESPACE, round).unwrap()) .collect(); let nullification = Nullification::from_nullifies(&fixture.schemes[0], &nullifies).unwrap(); let response = Response::::new(1, vec![notarization], vec![nullification]); let cfg = fixture.schemes[0].certificate_codec_config(); let mut decoded = Response::::decode_cfg(response.encode(), &(usize::MAX, cfg)).unwrap(); assert_eq!(response.id, decoded.id); assert_eq!(response.notarizations.len(), decoded.notarizations.len()); assert_eq!(response.nullifications.len(), decoded.nullifications.len()); let mut rng = OsRng; assert!(decoded.verify(&mut rng, &fixture.schemes[0], NAMESPACE)); decoded.nullifications[0].round = Round::new( decoded.nullifications[0].round.epoch(), decoded.nullifications[0].round.view().next(), ); assert!(!decoded.verify(&mut rng, &fixture.schemes[0], NAMESPACE)); } #[test] fn test_response_encode_decode() { response_encode_decode(ed25519::fixture); response_encode_decode(bls12381_multisig::fixture::); response_encode_decode(bls12381_multisig::fixture::); response_encode_decode(bls12381_threshold::fixture::); response_encode_decode(bls12381_threshold::fixture::); } fn conflicting_notarize_encode_decode(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, u32) -> Fixture, { let fixture = setup(5, fixture); let proposal1 = Proposal::new( Round::new(Epoch::new(0), View::new(10)), View::new(5), sample_digest(1), ); let proposal2 = Proposal::new( Round::new(Epoch::new(0), View::new(10)), View::new(5), sample_digest(2), ); let notarize1 = Notarize::sign(&fixture.schemes[0], NAMESPACE, proposal1).unwrap(); let notarize2 = Notarize::sign(&fixture.schemes[0], NAMESPACE, proposal2).unwrap(); let conflicting = ConflictingNotarize::new(notarize1, notarize2); let encoded = conflicting.encode(); let decoded = ConflictingNotarize::::decode(encoded).unwrap(); assert_eq!(conflicting, decoded); assert!(decoded.verify(&fixture.schemes[0], NAMESPACE)); } #[test] fn test_conflicting_notarize_encode_decode() { conflicting_notarize_encode_decode(ed25519::fixture); conflicting_notarize_encode_decode(bls12381_multisig::fixture::); conflicting_notarize_encode_decode(bls12381_multisig::fixture::); conflicting_notarize_encode_decode(bls12381_threshold::fixture::); conflicting_notarize_encode_decode(bls12381_threshold::fixture::); } fn conflicting_finalize_encode_decode(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, u32) -> Fixture, { let fixture = setup(5, fixture); let proposal1 = Proposal::new( Round::new(Epoch::new(0), View::new(10)), View::new(5), sample_digest(1), ); let proposal2 = Proposal::new( Round::new(Epoch::new(0), View::new(10)), View::new(5), sample_digest(2), ); let finalize1 = Finalize::sign(&fixture.schemes[0], NAMESPACE, proposal1).unwrap(); let finalize2 = Finalize::sign(&fixture.schemes[0], NAMESPACE, proposal2).unwrap(); let conflicting = ConflictingFinalize::new(finalize1, finalize2); let encoded = conflicting.encode(); let decoded = ConflictingFinalize::::decode(encoded).unwrap(); assert_eq!(conflicting, decoded); assert!(decoded.verify(&fixture.schemes[0], NAMESPACE)); } #[test] fn test_conflicting_finalize_encode_decode() { conflicting_finalize_encode_decode(ed25519::fixture); conflicting_finalize_encode_decode(bls12381_multisig::fixture::); conflicting_finalize_encode_decode(bls12381_multisig::fixture::); conflicting_finalize_encode_decode(bls12381_threshold::fixture::); conflicting_finalize_encode_decode(bls12381_threshold::fixture::); } fn nullify_finalize_encode_decode(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, u32) -> Fixture, { let fixture = setup(5, fixture); let round = Round::new(Epoch::new(0), View::new(10)); let proposal = Proposal::new(round, View::new(5), sample_digest(1)); let nullify = Nullify::sign::(&fixture.schemes[0], NAMESPACE, round).unwrap(); let finalize = Finalize::sign(&fixture.schemes[0], NAMESPACE, proposal).unwrap(); let conflict = NullifyFinalize::new(nullify, finalize); let encoded = conflict.encode(); let decoded = NullifyFinalize::::decode(encoded).unwrap(); assert_eq!(conflict, decoded); assert!(decoded.verify(&fixture.schemes[0], NAMESPACE)); } #[test] fn test_nullify_finalize_encode_decode() { nullify_finalize_encode_decode(ed25519::fixture); nullify_finalize_encode_decode(bls12381_multisig::fixture::); nullify_finalize_encode_decode(bls12381_multisig::fixture::); nullify_finalize_encode_decode(bls12381_threshold::fixture::); nullify_finalize_encode_decode(bls12381_threshold::fixture::); } fn notarize_verify_wrong_namespace(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, u32) -> Fixture, { let fixture = setup(5, fixture); let round = Round::new(Epoch::new(0), View::new(10)); let proposal = Proposal::new(round, View::new(5), sample_digest(1)); let notarize = Notarize::sign(&fixture.schemes[0], NAMESPACE, proposal).unwrap(); assert!(notarize.verify(&fixture.schemes[0], NAMESPACE)); assert!(!notarize.verify(&fixture.schemes[0], b"wrong_namespace")); } #[test] fn test_notarize_verify_wrong_namespace() { notarize_verify_wrong_namespace(ed25519::fixture); notarize_verify_wrong_namespace(bls12381_multisig::fixture::); notarize_verify_wrong_namespace(bls12381_multisig::fixture::); notarize_verify_wrong_namespace(bls12381_threshold::fixture::); notarize_verify_wrong_namespace(bls12381_threshold::fixture::); } fn notarize_verify_wrong_scheme(f: F) where S: Scheme, F: Fn(&mut StdRng, u32) -> Fixture, { let fixture = setup_seeded(5, 0, &f); let wrong_fixture = setup_seeded(5, 1, &f); let round = Round::new(Epoch::new(0), View::new(10)); let proposal = Proposal::new(round, View::new(5), sample_digest(2)); let notarize = Notarize::sign(&fixture.schemes[0], NAMESPACE, proposal).unwrap(); assert!(notarize.verify(&fixture.schemes[0], NAMESPACE)); assert!(!notarize.verify(&wrong_fixture.verifier, NAMESPACE)); } #[test] fn test_notarize_verify_wrong_scheme() { notarize_verify_wrong_scheme(ed25519::fixture); notarize_verify_wrong_scheme(bls12381_multisig::fixture::); notarize_verify_wrong_scheme(bls12381_multisig::fixture::); notarize_verify_wrong_scheme(bls12381_threshold::fixture::); notarize_verify_wrong_scheme(bls12381_threshold::fixture::); } fn notarization_verify_wrong_scheme(f: F) where S: Scheme, F: Fn(&mut StdRng, u32) -> Fixture, { let fixture = setup_seeded(5, 0, &f); let wrong_fixture = setup_seeded(5, 1, &f); let round = Round::new(Epoch::new(0), View::new(10)); let proposal = Proposal::new(round, View::new(5), sample_digest(3)); let quorum = quorum_from_slice(&fixture.schemes) as usize; let notarizes: Vec<_> = fixture .schemes .iter() .take(quorum) .map(|scheme| Notarize::sign(scheme, NAMESPACE, proposal.clone()).unwrap()) .collect(); let notarization = Notarization::from_notarizes(&fixture.schemes[0], ¬arizes) .expect("quorum notarization"); let mut rng = OsRng; assert!(notarization.verify(&mut rng, &fixture.schemes[0], NAMESPACE)); let mut rng = OsRng; assert!(!notarization.verify(&mut rng, &wrong_fixture.verifier, NAMESPACE)); } #[test] fn test_notarization_verify_wrong_scheme() { notarization_verify_wrong_scheme(ed25519::fixture); notarization_verify_wrong_scheme(bls12381_multisig::fixture::); notarization_verify_wrong_scheme(bls12381_multisig::fixture::); notarization_verify_wrong_scheme(bls12381_threshold::fixture::); notarization_verify_wrong_scheme(bls12381_threshold::fixture::); } fn notarization_verify_wrong_namespace(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, u32) -> Fixture, { let fixture = setup(5, fixture); let round = Round::new(Epoch::new(0), View::new(10)); let proposal = Proposal::new(round, View::new(5), sample_digest(4)); let quorum = quorum_from_slice(&fixture.schemes) as usize; let notarizes: Vec<_> = fixture .schemes .iter() .take(quorum) .map(|scheme| Notarize::sign(scheme, NAMESPACE, proposal.clone()).unwrap()) .collect(); let notarization = Notarization::from_notarizes(&fixture.schemes[0], ¬arizes) .expect("quorum notarization"); let mut rng = OsRng; assert!(notarization.verify(&mut rng, &fixture.schemes[0], NAMESPACE)); let mut rng = OsRng; assert!(!notarization.verify(&mut rng, &fixture.schemes[0], b"wrong_namespace")); } #[test] fn test_notarization_verify_wrong_namespace() { notarization_verify_wrong_namespace(ed25519::fixture); notarization_verify_wrong_namespace(bls12381_multisig::fixture::); notarization_verify_wrong_namespace(bls12381_multisig::fixture::); notarization_verify_wrong_namespace(bls12381_threshold::fixture::); notarization_verify_wrong_namespace(bls12381_threshold::fixture::); } fn notarization_recover_insufficient_signatures(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, u32) -> Fixture, { let fixture = setup(5, fixture); let quorum_size = quorum(fixture.schemes.len() as u32) as usize; assert!(quorum_size > 1, "test requires quorum larger than one"); let round = Round::new(Epoch::new(0), View::new(10)); let proposal = Proposal::new(round, View::new(5), sample_digest(5)); let notarizes: Vec<_> = fixture .schemes .iter() .take(quorum_size - 1) .map(|scheme| Notarize::sign(scheme, NAMESPACE, proposal.clone()).unwrap()) .collect(); assert!( Notarization::from_notarizes(&fixture.schemes[0], ¬arizes).is_none(), "insufficient votes should not form a notarization" ); } #[test] fn test_notarization_recover_insufficient_signatures() { notarization_recover_insufficient_signatures(ed25519::fixture); notarization_recover_insufficient_signatures(bls12381_multisig::fixture::); notarization_recover_insufficient_signatures(bls12381_multisig::fixture::); notarization_recover_insufficient_signatures(bls12381_threshold::fixture::); notarization_recover_insufficient_signatures(bls12381_threshold::fixture::); } fn conflicting_notarize_detection(f: F) where S: Scheme, F: Fn(&mut StdRng, u32) -> Fixture, { let fixture = setup_seeded(5, 0, &f); let wrong_fixture = setup_seeded(5, 1, &f); let round = Round::new(Epoch::new(0), View::new(10)); let proposal1 = Proposal::new(round, View::new(5), sample_digest(6)); let proposal2 = Proposal::new(round, View::new(5), sample_digest(7)); let notarize1 = Notarize::sign(&fixture.schemes[0], NAMESPACE, proposal1).unwrap(); let notarize2 = Notarize::sign(&fixture.schemes[0], NAMESPACE, proposal2).unwrap(); let conflict = ConflictingNotarize::new(notarize1, notarize2); assert!(conflict.verify(&fixture.schemes[0], NAMESPACE)); assert!(!conflict.verify(&fixture.schemes[0], b"wrong_namespace")); assert!(!conflict.verify(&wrong_fixture.verifier, NAMESPACE)); } #[test] fn test_conflicting_notarize_detection() { conflicting_notarize_detection(ed25519::fixture); conflicting_notarize_detection(bls12381_multisig::fixture::); conflicting_notarize_detection(bls12381_multisig::fixture::); conflicting_notarize_detection(bls12381_threshold::fixture::); conflicting_notarize_detection(bls12381_threshold::fixture::); } fn nullify_finalize_detection(f: F) where S: Scheme, F: Fn(&mut StdRng, u32) -> Fixture, { let fixture = setup_seeded(5, 0, &f); let wrong_fixture = setup_seeded(5, 1, &f); let round = Round::new(Epoch::new(0), View::new(10)); let proposal = Proposal::new(round, View::new(5), sample_digest(8)); let nullify = Nullify::sign::(&fixture.schemes[0], NAMESPACE, round).unwrap(); let finalize = Finalize::sign(&fixture.schemes[0], NAMESPACE, proposal).unwrap(); let conflict = NullifyFinalize::new(nullify, finalize); assert!(conflict.verify(&fixture.schemes[0], NAMESPACE)); assert!(!conflict.verify(&fixture.schemes[0], b"wrong_namespace")); assert!(!conflict.verify(&wrong_fixture.verifier, NAMESPACE)); } #[test] fn test_nullify_finalize_detection() { nullify_finalize_detection(ed25519::fixture); nullify_finalize_detection(bls12381_multisig::fixture::); nullify_finalize_detection(bls12381_multisig::fixture::); nullify_finalize_detection(bls12381_threshold::fixture::); nullify_finalize_detection(bls12381_threshold::fixture::); } fn finalization_verify_wrong_scheme(f: F) where S: Scheme, F: Fn(&mut StdRng, u32) -> Fixture, { let fixture = setup_seeded(5, 0, &f); let wrong_fixture = setup_seeded(5, 1, &f); let round = Round::new(Epoch::new(0), View::new(10)); let proposal = Proposal::new(round, View::new(5), sample_digest(9)); let quorum = quorum_from_slice(&fixture.schemes) as usize; let finalizes: Vec<_> = fixture .schemes .iter() .take(quorum) .map(|scheme| Finalize::sign(scheme, NAMESPACE, proposal.clone()).unwrap()) .collect(); let finalization = Finalization::from_finalizes(&fixture.schemes[0], &finalizes) .expect("quorum finalization"); let mut rng = OsRng; assert!(finalization.verify(&mut rng, &fixture.schemes[0], NAMESPACE)); let mut rng = OsRng; assert!(!finalization.verify(&mut rng, &wrong_fixture.verifier, NAMESPACE)); } #[test] fn test_finalization_wrong_scheme() { finalization_verify_wrong_scheme(ed25519::fixture); finalization_verify_wrong_scheme(bls12381_multisig::fixture::); finalization_verify_wrong_scheme(bls12381_multisig::fixture::); finalization_verify_wrong_scheme(bls12381_threshold::fixture::); finalization_verify_wrong_scheme(bls12381_threshold::fixture::); } struct MockAttributable(u32); impl Attributable for MockAttributable { fn signer(&self) -> u32 { self.0 } } #[test] fn test_attributable_map() { let mut map = AttributableMap::new(5); assert_eq!(map.len(), 0); assert!(map.is_empty()); // Test get on empty map for i in 0..5 { assert!(map.get(i).is_none()); } assert!(map.insert(MockAttributable(3))); assert_eq!(map.len(), 1); assert!(!map.is_empty()); let mut iter = map.iter(); assert!(matches!(iter.next(), Some(a) if a.signer() == 3)); assert!(iter.next().is_none()); drop(iter); // Test get on existing item assert!(matches!(map.get(3), Some(a) if a.signer() == 3)); assert!(map.insert(MockAttributable(1))); assert_eq!(map.len(), 2); assert!(!map.is_empty()); let mut iter = map.iter(); assert!(matches!(iter.next(), Some(a) if a.signer() == 1)); assert!(matches!(iter.next(), Some(a) if a.signer() == 3)); assert!(iter.next().is_none()); drop(iter); // Test get on both items assert!(matches!(map.get(1), Some(a) if a.signer() == 1)); assert!(matches!(map.get(3), Some(a) if a.signer() == 3)); // Test get on non-existing items assert!(map.get(0).is_none()); assert!(map.get(2).is_none()); assert!(map.get(4).is_none()); assert!(!map.insert(MockAttributable(3))); assert_eq!(map.len(), 2); assert!(!map.is_empty()); let mut iter = map.iter(); assert!(matches!(iter.next(), Some(a) if a.signer() == 1)); assert!(matches!(iter.next(), Some(a) if a.signer() == 3)); assert!(iter.next().is_none()); drop(iter); // Test out-of-bounds signer indices assert!(!map.insert(MockAttributable(5))); assert!(!map.insert(MockAttributable(100))); assert_eq!(map.len(), 2); // Test clear map.clear(); assert_eq!(map.len(), 0); assert!(map.is_empty()); assert!(map.iter().next().is_none()); // Verify can insert after clear assert!(map.insert(MockAttributable(2))); assert_eq!(map.len(), 1); let mut iter = map.iter(); assert!(matches!(iter.next(), Some(a) if a.signer() == 2)); assert!(iter.next().is_none()); } #[cfg(feature = "arbitrary")] mod conformance { use super::*; use crate::simplex::scheme::bls12381_threshold; use commonware_codec::conformance::CodecConformance; use commonware_cryptography::{ed25519::PublicKey, sha256::Digest as Sha256Digest}; type Scheme = bls12381_threshold::Scheme; commonware_conformance::conformance_tests! { CodecConformance>, CodecConformance>, CodecConformance>, CodecConformance>, CodecConformance>, CodecConformance>, CodecConformance>, CodecConformance>, CodecConformance>, CodecConformance>, CodecConformance>, CodecConformance, CodecConformance>, CodecConformance>, CodecConformance>, CodecConformance>, CodecConformance>, } } }