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}