commonware_consensus/simplex/scheme/
mod.rs

1//! Signing scheme implementations for `simplex`.
2//!
3//! # Attributable Schemes and 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 [`certificate::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
25use crate::simplex::types::Subject;
26use bytes::Bytes;
27use commonware_codec::Encode;
28use commonware_cryptography::{certificate, Digest};
29use commonware_utils::union;
30
31pub mod bls12381_multisig;
32pub mod bls12381_threshold;
33pub mod ed25519;
34
35#[cfg(not(target_arch = "wasm32"))]
36pub mod reporter;
37
38impl<'a, D: Digest> certificate::Subject for Subject<'a, D> {
39    fn namespace_and_message(&self, namespace: &[u8]) -> (Bytes, Bytes) {
40        vote_namespace_and_message(namespace, self)
41    }
42}
43
44/// Marker trait for signing schemes compatible with `simplex`.
45///
46/// This trait binds a [`certificate::Scheme`] to the [`Subject`] subject type
47/// used by the simplex protocol. It is automatically implemented for any scheme
48/// whose subject type matches `Subject<'a, D>`.
49pub trait Scheme<D: Digest>: for<'a> certificate::Scheme<Subject<'a, D> = Subject<'a, D>> {}
50
51impl<D: Digest, S> Scheme<D> for S where
52    S: for<'a> certificate::Scheme<Subject<'a, D> = Subject<'a, D>>
53{
54}
55
56// Constants for domain separation in signature verification
57// These are used to prevent cross-protocol attacks and message-type confusion
58const SEED_SUFFIX: &[u8] = b"_SEED";
59const NOTARIZE_SUFFIX: &[u8] = b"_NOTARIZE";
60const NULLIFY_SUFFIX: &[u8] = b"_NULLIFY";
61const FINALIZE_SUFFIX: &[u8] = b"_FINALIZE";
62
63/// Creates a namespace for seed messages by appending the SEED_SUFFIX
64/// The seed is used for leader election and randomness generation
65#[inline]
66pub(crate) fn seed_namespace(namespace: &[u8]) -> Vec<u8> {
67    union(namespace, SEED_SUFFIX)
68}
69
70/// Creates a namespace for notarize messages by appending the NOTARIZE_SUFFIX
71/// Domain separation prevents cross-protocol attacks
72#[inline]
73pub(crate) fn notarize_namespace(namespace: &[u8]) -> Vec<u8> {
74    union(namespace, NOTARIZE_SUFFIX)
75}
76
77/// Creates a namespace for nullify messages by appending the NULLIFY_SUFFIX
78/// Domain separation prevents cross-protocol attacks
79#[inline]
80pub(crate) fn nullify_namespace(namespace: &[u8]) -> Vec<u8> {
81    union(namespace, NULLIFY_SUFFIX)
82}
83
84/// Creates a namespace for finalize messages by appending the FINALIZE_SUFFIX
85/// Domain separation prevents cross-protocol attacks
86#[inline]
87pub(crate) fn finalize_namespace(namespace: &[u8]) -> Vec<u8> {
88    union(namespace, FINALIZE_SUFFIX)
89}
90
91/// Produces the vote namespace and message bytes for a given vote context.
92///
93/// Returns the final namespace (with the context-specific suffix) and the
94/// serialized message to sign or verify.
95#[inline]
96pub(crate) fn vote_namespace_and_message<D: Digest>(
97    namespace: &[u8],
98    subject: &Subject<'_, D>,
99) -> (Bytes, Bytes) {
100    match subject {
101        Subject::Notarize { proposal } => (
102            notarize_namespace(namespace).into(),
103            proposal.encode().freeze(),
104        ),
105        Subject::Nullify { round } => {
106            (nullify_namespace(namespace).into(), round.encode().freeze())
107        }
108        Subject::Finalize { proposal } => (
109            finalize_namespace(namespace).into(),
110            proposal.encode().freeze(),
111        ),
112    }
113}
114
115/// Produces the seed namespace and message bytes for a given vote context.
116///
117/// Returns the final namespace (with the seed suffix) and the serialized
118/// message to sign or verify.
119#[inline]
120pub(crate) fn seed_namespace_and_message<D: Digest>(
121    namespace: &[u8],
122    subject: &Subject<'_, D>,
123) -> (Bytes, Bytes) {
124    (
125        seed_namespace(namespace).into(),
126        match subject {
127            Subject::Notarize { proposal } | Subject::Finalize { proposal } => {
128                proposal.round.encode().freeze()
129            }
130            Subject::Nullify { round } => round.encode().freeze(),
131        },
132    )
133}