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`], [`secp256r1`]): Individual signatures can be
9//! presented 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 conflict detection.
11//!
12//! - **Non-Attributable schemes** ([`bls12381_threshold`]): Individual signatures cannot be presented
13//! to some third party as evidence of either liveness or of committing a fault because they can be forged
14//! by other players (often after some quorum of partial signatures are collected). With [`bls12381_threshold`],
15//! possession of any `t` valid partial signatures can be used to forge a partial signature for any other player.
16//! Because peer connections are authenticated, evidence can be used locally (as it must be sent by said participant)
17//! but can't be used by an external observer.
18//!
19//! The [`certificate::Scheme::is_attributable()`] associated function signals whether evidence can be safely
20//! exposed. For applications only interested in collecting evidence for liveness/faults, use [`reporter::AttributableReporter`]
21//! which automatically handles filtering and verification based on scheme (hiding votes/proofs that are not attributable). If
22//! full observability is desired, process all messages passed through the [`crate::Reporter`] interface.
23//!
24//! # BLS12-381 Threshold Variants
25//!
26//! The [`bls12381_threshold`] module provides two variants:
27//!
28//! - [`bls12381_threshold::standard`]: Standard threshold signatures. Certificates contain only a
29//! vote signature recovered from partial signatures.
30//!
31//! - [`bls12381_threshold::vrf`]: Threshold VRF (Verifiable Random Function) that produces both
32//! vote signatures and per-round seed signatures. The seed can be used for randomness (e.g.,
33//! leader election, timelock encryption).
34//!
35//! **Security Warning for VRF Usage**: It is **not safe** to use a round's randomness to drive
36//! execution in that same round. A malicious leader can selectively distribute blocks to gain
37//! early visibility of the randomness output, then choose nullification if the outcome is
38//! unfavorable. Applications should employ a "commit-then-reveal" pattern by binding randomness
39//! requests in finalized blocks **before** the reveal occurs (e.g., `draw(view+100)`).
40
41use crate::simplex::types::Subject;
42use bytes::Bytes;
43use commonware_codec::Encode;
44use commonware_cryptography::{certificate, Digest};
45use commonware_utils::union;
46
47pub mod bls12381_multisig;
48pub mod bls12381_threshold;
49pub mod ed25519;
50commonware_macros::stability_mod!(ALPHA, pub mod secp256r1);
51
52#[cfg(not(target_arch = "wasm32"))]
53pub mod reporter;
54
55/// Pre-computed namespaces for simplex voting subjects.
56///
57/// This struct holds the pre-computed namespace bytes for each vote type.
58#[derive(Clone, Debug)]
59pub struct Namespace {
60 /// Namespace for notarize votes/certificates.
61 pub notarize: Vec<u8>,
62 /// Namespace for nullify votes/certificates.
63 pub nullify: Vec<u8>,
64 /// Namespace for finalize votes/certificates.
65 pub finalize: Vec<u8>,
66 /// Namespace for seed signatures (used by threshold schemes).
67 pub seed: Vec<u8>,
68}
69
70impl Namespace {
71 /// Creates a new SimplexNamespace from a base namespace.
72 pub fn new(namespace: &[u8]) -> Self {
73 Self {
74 notarize: notarize_namespace(namespace),
75 nullify: nullify_namespace(namespace),
76 finalize: finalize_namespace(namespace),
77 seed: seed_namespace(namespace),
78 }
79 }
80}
81
82impl certificate::Namespace for Namespace {
83 fn derive(namespace: &[u8]) -> Self {
84 Self::new(namespace)
85 }
86}
87
88impl<'a, D: Digest> certificate::Subject for Subject<'a, D> {
89 type Namespace = Namespace;
90
91 fn namespace<'b>(&self, derived: &'b Self::Namespace) -> &'b [u8] {
92 match self {
93 Self::Notarize { .. } => &derived.notarize,
94 Self::Nullify { .. } => &derived.nullify,
95 Self::Finalize { .. } => &derived.finalize,
96 }
97 }
98
99 fn message(&self) -> Bytes {
100 match self {
101 Self::Notarize { proposal } => proposal.encode(),
102 Self::Nullify { round } => round.encode(),
103 Self::Finalize { proposal } => proposal.encode(),
104 }
105 }
106}
107
108/// Marker trait for signing schemes compatible with `simplex`.
109///
110/// This trait binds a [`certificate::Scheme`] to the [`Subject`] subject type
111/// used by the simplex protocol. It is automatically implemented for any scheme
112/// whose subject type matches `Subject<'a, D>`.
113pub trait Scheme<D: Digest>: for<'a> certificate::Scheme<Subject<'a, D> = Subject<'a, D>> {}
114
115impl<D: Digest, S> Scheme<D> for S where
116 S: for<'a> certificate::Scheme<Subject<'a, D> = Subject<'a, D>>
117{
118}
119
120// Constants for domain separation in signature verification
121// These are used to prevent cross-protocol attacks and message-type confusion
122const SEED_SUFFIX: &[u8] = b"_SEED";
123const NOTARIZE_SUFFIX: &[u8] = b"_NOTARIZE";
124const NULLIFY_SUFFIX: &[u8] = b"_NULLIFY";
125const FINALIZE_SUFFIX: &[u8] = b"_FINALIZE";
126
127/// Creates a namespace for seed messages by appending the SEED_SUFFIX
128/// The seed is used for leader election and randomness generation
129#[inline]
130pub(crate) fn seed_namespace(namespace: &[u8]) -> Vec<u8> {
131 union(namespace, SEED_SUFFIX)
132}
133
134/// Creates a namespace for notarize messages by appending the NOTARIZE_SUFFIX
135/// Domain separation prevents cross-protocol attacks
136#[inline]
137pub(crate) fn notarize_namespace(namespace: &[u8]) -> Vec<u8> {
138 union(namespace, NOTARIZE_SUFFIX)
139}
140
141/// Creates a namespace for nullify messages by appending the NULLIFY_SUFFIX
142/// Domain separation prevents cross-protocol attacks
143#[inline]
144pub(crate) fn nullify_namespace(namespace: &[u8]) -> Vec<u8> {
145 union(namespace, NULLIFY_SUFFIX)
146}
147
148/// Creates a namespace for finalize messages by appending the FINALIZE_SUFFIX
149/// Domain separation prevents cross-protocol attacks
150#[inline]
151pub(crate) fn finalize_namespace(namespace: &[u8]) -> Vec<u8> {
152 union(namespace, FINALIZE_SUFFIX)
153}