commonware_consensus/simplex/signing_scheme/
mod.rs

1//! Signing scheme implementations for `simplex`.
2//!
3//! # Attributable Schemes and Liveness/Fault Evidence
4//!
5//! Signing schemes differ in whether per-validator activities can be used as evidence of either
6//! liveness or of committing a fault:
7//!
8//! - **Attributable Schemes** ([`ed25519`], [`bls12381_multisig`]): Individual signatures can be presented
9//!   to some third party as evidence of either liveness or of committing a fault. Certificates contain signer
10//!   indices alongside individual signatures, enabling secure per-validator activity tracking and
11//!   conflict detection.
12//!
13//! - **Non-Attributable schemes** ([`bls12381_threshold`]): Individual signatures cannot be presented
14//!   to some third party as evidence of either liveness or of committing a fault because they can be forged
15//!   by other players (often after some quorum of partial signatures are collected). With [`bls12381_threshold`],
16//!   possession of any `t` valid partial signatures can be used to forge a partial signature for any other player.
17//!   Because peer connections are authenticated, evidence can be used locally (as it must be sent by said participant)
18//!   but can't be used by an external observer.
19//!
20//! The [`Scheme::is_attributable()`] method signals whether evidence can be safely
21//! exposed. For applications only interested in collecting evidence for liveness/faults, use [`reporter::AttributableReporter`]
22//! which automatically handles filtering and verification based on scheme (hiding votes/proofs that are not attributable). If
23//! full observability is desired, process all messages passed through the [`crate::Reporter`] interface.
24
25pub mod bls12381_multisig;
26pub mod bls12381_threshold;
27pub mod ed25519;
28pub mod utils;
29
30cfg_if::cfg_if! {
31    if #[cfg(not(target_arch = "wasm32"))] {
32      pub mod reporter;
33    }
34}
35
36use crate::{
37    simplex::types::{Vote, VoteContext, VoteVerification},
38    types::Round,
39};
40use commonware_codec::{Codec, CodecFixed, Encode, Read};
41use commonware_cryptography::{Digest, PublicKey};
42use commonware_utils::{set::Ordered, union};
43use rand::{CryptoRng, Rng};
44use std::{collections::BTreeSet, fmt::Debug, hash::Hash};
45
46/// Cryptographic surface required by `simplex`.
47///
48/// A `Scheme` produces validator votes, validates them (individually or in batches), assembles
49/// quorum certificates, checks recovered certificates and, when available, derives a randomness
50/// seed for leader rotation. Implementations may override the provided defaults to take advantage
51/// of scheme-specific batching strategies.
52///
53/// # Identity Keys vs Consensus Keys
54///
55/// A participant may supply both an identity key and a consensus key. The identity key
56/// is used for assigning a unique order to the committee and authenticating connections whereas the consensus key
57/// is used for actually signing and verifying votes/certificates.
58///
59/// This flexibility is supported because some cryptographic schemes are only performant when used in batch verification
60/// (like [bls12381_multisig]) and/or are refreshed frequently (like [bls12381_threshold]). Refer to [ed25519]
61/// for an example of a scheme that uses the same key for both purposes.
62pub trait Scheme: Clone + Debug + Send + Sync + 'static {
63    /// Public key type for participant identity used to order and index the committee.
64    type PublicKey: PublicKey;
65    /// Vote signature emitted by individual validators.
66    type Signature: Clone + Debug + PartialEq + Eq + Hash + Send + Sync + CodecFixed<Cfg = ()>;
67    /// Quorum certificate recovered from a set of votes.
68    type Certificate: Clone + Debug + PartialEq + Eq + Hash + Send + Sync + Codec;
69    /// Randomness seed derived from a certificate, if the scheme supports it.
70    type Seed: Clone + Encode + Send;
71
72    /// Returns the index of "self" in the participant set, if available.
73    /// Returns `None` if the scheme is a verifier-only instance.
74    fn me(&self) -> Option<u32>;
75
76    /// Returns the ordered set of participant public identity keys managed by the scheme.
77    fn participants(&self) -> &Ordered<Self::PublicKey>;
78
79    /// Signs a vote for the given context using the supplied namespace for domain separation.
80    /// Returns `None` if the scheme cannot sign (e.g. it's a verifier-only instance).
81    fn sign_vote<D: Digest>(
82        &self,
83        namespace: &[u8],
84        context: VoteContext<'_, D>,
85    ) -> Option<Vote<Self>>;
86
87    /// Verifies a single vote against the participant material managed by the scheme.
88    fn verify_vote<D: Digest>(
89        &self,
90        namespace: &[u8],
91        context: VoteContext<'_, D>,
92        vote: &Vote<Self>,
93    ) -> bool;
94
95    /// Batch-verifies votes and separates valid messages from the voter indices that failed
96    /// verification.
97    ///
98    /// Callers must not include duplicate votes from the same signer.
99    fn verify_votes<R, D, I>(
100        &self,
101        _rng: &mut R,
102        namespace: &[u8],
103        context: VoteContext<'_, D>,
104        votes: I,
105    ) -> VoteVerification<Self>
106    where
107        R: Rng + CryptoRng,
108        D: Digest,
109        I: IntoIterator<Item = Vote<Self>>,
110    {
111        let mut invalid = BTreeSet::new();
112
113        let verified = votes.into_iter().filter_map(|vote| {
114            if self.verify_vote(namespace, context, &vote) {
115                Some(vote)
116            } else {
117                invalid.insert(vote.signer);
118                None
119            }
120        });
121
122        VoteVerification::new(verified.collect(), invalid.into_iter().collect())
123    }
124
125    /// Aggregates a quorum of votes into a certificate, returning `None` if the quorum is not met.
126    ///
127    /// Callers must not include duplicate votes from the same signer.
128    fn assemble_certificate<I>(&self, votes: I) -> Option<Self::Certificate>
129    where
130        I: IntoIterator<Item = Vote<Self>>;
131
132    /// Verifies a certificate that was recovered or received from the network.
133    fn verify_certificate<R: Rng + CryptoRng, D: Digest>(
134        &self,
135        rng: &mut R,
136        namespace: &[u8],
137        context: VoteContext<'_, D>,
138        certificate: &Self::Certificate,
139    ) -> bool;
140
141    /// Verifies a stream of certificates, returning `false` at the first failure.
142    fn verify_certificates<'a, R, D, I>(
143        &self,
144        rng: &mut R,
145        namespace: &[u8],
146        certificates: I,
147    ) -> bool
148    where
149        R: Rng + CryptoRng,
150        D: Digest,
151        I: Iterator<Item = (VoteContext<'a, D>, &'a Self::Certificate)>,
152    {
153        for (context, certificate) in certificates {
154            if !self.verify_certificate(rng, namespace, context, certificate) {
155                return false;
156            }
157        }
158
159        true
160    }
161
162    /// Extracts randomness seed, if provided by the scheme, derived from the certificate
163    /// for the given round.
164    fn seed(&self, round: Round, certificate: &Self::Certificate) -> Option<Self::Seed>;
165
166    /// Returns whether per-validator fault evidence can be safely exposed.
167    ///
168    /// Schemes where individual signatures can be safely reported as fault evidence should
169    /// return `true`.
170    ///
171    /// This is used by [`reporter::AttributableReporter`] to safely expose consensus
172    /// activities.
173    fn is_attributable(&self) -> bool;
174
175    /// Encoding configuration for bounded-size certificate decoding used in network payloads.
176    fn certificate_codec_config(&self) -> <Self::Certificate as Read>::Cfg;
177
178    /// Encoding configuration that allows unbounded certificate decoding.
179    ///
180    /// Only use this when decoding data from trusted local storage, it must not be exposed to
181    /// adversarial inputs or network payloads.
182    fn certificate_codec_config_unbounded() -> <Self::Certificate as Read>::Cfg;
183}
184
185// Constants for domain separation in signature verification
186// These are used to prevent cross-protocol attacks and message-type confusion
187const SEED_SUFFIX: &[u8] = b"_SEED";
188const NOTARIZE_SUFFIX: &[u8] = b"_NOTARIZE";
189const NULLIFY_SUFFIX: &[u8] = b"_NULLIFY";
190const FINALIZE_SUFFIX: &[u8] = b"_FINALIZE";
191
192/// Creates a namespace for seed messages by appending the SEED_SUFFIX
193/// The seed is used for leader election and randomness generation
194#[inline]
195pub(crate) fn seed_namespace(namespace: &[u8]) -> Vec<u8> {
196    union(namespace, SEED_SUFFIX)
197}
198
199/// Creates a namespace for notarize messages by appending the NOTARIZE_SUFFIX
200/// Domain separation prevents cross-protocol attacks
201#[inline]
202pub(crate) fn notarize_namespace(namespace: &[u8]) -> Vec<u8> {
203    union(namespace, NOTARIZE_SUFFIX)
204}
205
206/// Creates a namespace for nullify messages by appending the NULLIFY_SUFFIX
207/// Domain separation prevents cross-protocol attacks
208#[inline]
209pub(crate) fn nullify_namespace(namespace: &[u8]) -> Vec<u8> {
210    union(namespace, NULLIFY_SUFFIX)
211}
212
213/// Creates a namespace for finalize messages by appending the FINALIZE_SUFFIX
214/// Domain separation prevents cross-protocol attacks
215#[inline]
216pub(crate) fn finalize_namespace(namespace: &[u8]) -> Vec<u8> {
217    union(namespace, FINALIZE_SUFFIX)
218}
219
220/// Produces the vote namespace and message bytes for a given vote context.
221///
222/// Returns the final namespace (with the context-specific suffix) and the
223/// serialized message to sign or verify.
224#[inline]
225pub(crate) fn vote_namespace_and_message<D: Digest>(
226    namespace: &[u8],
227    context: VoteContext<'_, D>,
228) -> (Vec<u8>, Vec<u8>) {
229    match context {
230        VoteContext::Notarize { proposal } => {
231            (notarize_namespace(namespace), proposal.encode().to_vec())
232        }
233        VoteContext::Nullify { round } => (nullify_namespace(namespace), round.encode().to_vec()),
234        VoteContext::Finalize { proposal } => {
235            (finalize_namespace(namespace), proposal.encode().to_vec())
236        }
237    }
238}
239
240/// Produces the seed namespace and message bytes for a given vote context.
241///
242/// Returns the final namespace (with the seed suffix) and the serialized
243/// message to sign or verify.
244#[inline]
245pub(crate) fn seed_namespace_and_message<D: Digest>(
246    namespace: &[u8],
247    context: VoteContext<'_, D>,
248) -> (Vec<u8>, Vec<u8>) {
249    (
250        seed_namespace(namespace),
251        match context {
252            VoteContext::Notarize { proposal } | VoteContext::Finalize { proposal } => {
253                proposal.round.encode().to_vec()
254            }
255            VoteContext::Nullify { round } => round.encode().to_vec(),
256        },
257    )
258}