commonware_cryptography/
certificate.rs

1//! Cryptographic primitives for generating and verifying certificates.
2//!
3//! This module provides the [`Scheme`] trait and implementations for producing
4//! signatures, validating them (individually or in batches), assembling
5//! certificates, and verifying recovered certificates.
6//!
7//! # Pluggable Cryptography
8//!
9//! Certificates are generic over the signing scheme, allowing users to choose
10//! the scheme best suited for their requirements:
11//!
12//! - [`ed25519`]: Attributable signatures with individual verification. HSM-friendly, no trusted
13//!   setup required, and widely supported. Certificates contain individual signatures from each
14//!   signer.
15//!
16//! - [`bls12381_multisig`]: Attributable signatures with aggregated verification. Signatures
17//!   can be aggregated into a single multi-signature for compact certificates while preserving
18//!   attribution (signer indices are stored alongside the aggregated signature).
19//!
20//! - [`bls12381_threshold`]: Non-attributable threshold signatures. Produces succinct
21//!   certificates that are constant-size regardless of committee size. Requires a trusted
22//!   setup (distributed key generation) and cannot attribute signatures to individual signers.
23//!
24//! # Attributable Schemes and Fault Evidence
25//!
26//! Signing schemes differ in whether per-participant activities can be used as evidence of
27//! either liveness or of committing a fault:
28//!
29//! - **Attributable Schemes** ([`ed25519`], [`bls12381_multisig`]): Individual signatures can be
30//!   presented to some third party as evidence of either liveness or of committing a fault.
31//!   Certificates contain signer indices alongside individual signatures, enabling secure
32//!   per-participant activity tracking and conflict detection.
33//!
34//! - **Non-Attributable Schemes** ([`bls12381_threshold`]): Individual signatures cannot be
35//!   presented to some third party as evidence of either liveness or of committing a fault
36//!   because they can be forged by other players (often after some quorum of partial signatures
37//!   are collected). With [`bls12381_threshold`], possession of any `t` valid partial signatures
38//!   can be used to forge a partial signature for any other player. Because peer connections are
39//!   authenticated, evidence can be used locally (as it must be sent by said participant) but
40//!   cannot be used by an external observer.
41//!
42//! The [`Scheme::is_attributable()`] method signals whether evidence can be safely exposed to
43//! third parties.
44//!
45//! # Identity Keys vs Signing Keys
46//!
47//! A participant may supply both an identity key and a signing key. The identity key
48//! is used for assigning a unique order to the participant set and authenticating connections
49//! whereas the signing key is used for producing and verifying signatures/certificates.
50//!
51//! This flexibility is supported because some cryptographic schemes are only performant when
52//! used in batch verification (like [bls12381_multisig]) and/or are refreshed frequently
53//! (like [bls12381_threshold]). Refer to [ed25519] for an example of a scheme that uses the
54//! same key for both purposes.
55
56pub use crate::{
57    bls12381::certificate::{multisig as bls12381_multisig, threshold as bls12381_threshold},
58    ed25519::certificate as ed25519,
59    impl_certificate_bls12381_multisig, impl_certificate_bls12381_threshold,
60    impl_certificate_ed25519,
61};
62use crate::{Digest, PublicKey};
63#[cfg(not(feature = "std"))]
64use alloc::{collections::BTreeSet, sync::Arc, vec::Vec};
65use bytes::{Buf, BufMut, Bytes};
66use commonware_codec::{varint::UInt, Codec, CodecFixed, EncodeSize, Error, Read, ReadExt, Write};
67use commonware_utils::{bitmap::BitMap, ordered::Set};
68use core::{fmt::Debug, hash::Hash};
69use rand::{CryptoRng, Rng};
70#[cfg(feature = "std")]
71use std::{collections::BTreeSet, sync::Arc, vec::Vec};
72
73/// A participant's attestation for a certificate.
74#[derive(Clone, Debug)]
75pub struct Attestation<S: Scheme> {
76    /// Index of the signer inside the participant set.
77    pub signer: u32,
78    /// Scheme-specific signature or share produced for a given subject.
79    pub signature: S::Signature,
80}
81
82impl<S: Scheme> PartialEq for Attestation<S> {
83    fn eq(&self, other: &Self) -> bool {
84        self.signer == other.signer && self.signature == other.signature
85    }
86}
87
88impl<S: Scheme> Eq for Attestation<S> {}
89
90impl<S: Scheme> Hash for Attestation<S> {
91    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
92        self.signer.hash(state);
93        self.signature.hash(state);
94    }
95}
96
97impl<S: Scheme> Write for Attestation<S> {
98    fn write(&self, writer: &mut impl BufMut) {
99        UInt(self.signer).write(writer);
100        self.signature.write(writer);
101    }
102}
103
104impl<S: Scheme> EncodeSize for Attestation<S> {
105    fn encode_size(&self) -> usize {
106        UInt(self.signer).encode_size() + self.signature.encode_size()
107    }
108}
109
110impl<S: Scheme> Read for Attestation<S> {
111    type Cfg = ();
112
113    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, Error> {
114        let signer = UInt::read(reader)?.into();
115        let signature = S::Signature::read(reader)?;
116
117        Ok(Self { signer, signature })
118    }
119}
120
121#[cfg(feature = "arbitrary")]
122impl<S: Scheme> arbitrary::Arbitrary<'_> for Attestation<S>
123where
124    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
125{
126    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
127        let signer = u32::arbitrary(u)?;
128        let signature = S::Signature::arbitrary(u)?;
129        Ok(Self { signer, signature })
130    }
131}
132
133/// Result of batch-verifying attestations.
134pub struct Verification<S: Scheme> {
135    /// Contains the attestations accepted by the scheme.
136    pub verified: Vec<Attestation<S>>,
137    /// Identifies the participant indices rejected during batch verification.
138    pub invalid: Vec<u32>,
139}
140
141impl<S: Scheme> Verification<S> {
142    /// Creates a new `Verification` result.
143    pub const fn new(verified: Vec<Attestation<S>>, invalid: Vec<u32>) -> Self {
144        Self { verified, invalid }
145    }
146}
147
148/// Identifies the subject of a signature or certificate.
149pub trait Subject: Clone + Debug + Send + Sync {
150    /// Returns the namespace and message for the subject, given some base namespace.
151    fn namespace_and_message(&self, namespace: &[u8]) -> (Bytes, Bytes);
152}
153
154/// Cryptographic surface for multi-party certificate schemes.
155///
156/// A `Scheme` produces attestations, validates them (individually or in batches), assembles
157/// certificates, and verifies recovered certificates. Implementations may override the
158/// provided defaults to take advantage of scheme-specific batching strategies.
159pub trait Scheme: Clone + Debug + Send + Sync + 'static {
160    /// Subject type for signing and verification.
161    type Subject<'a, D: Digest>: Subject;
162
163    /// Public key type for participant identity used to order and index the participant set.
164    type PublicKey: PublicKey;
165    /// Signature emitted by individual participants.
166    type Signature: Clone + Debug + PartialEq + Eq + Hash + Send + Sync + CodecFixed<Cfg = ()>;
167    /// Certificate assembled from a set of attestations.
168    type Certificate: Clone + Debug + PartialEq + Eq + Hash + Send + Sync + Codec;
169
170    /// Returns the index of "self" in the participant set, if available.
171    /// Returns `None` if the scheme is a verifier-only instance.
172    fn me(&self) -> Option<u32>;
173
174    /// Returns the ordered set of participant public identity keys managed by the scheme.
175    fn participants(&self) -> &Set<Self::PublicKey>;
176
177    /// Signs a subject using the supplied namespace for domain separation.
178    /// Returns `None` if the scheme cannot sign (e.g. it's a verifier-only instance).
179    fn sign<D: Digest>(
180        &self,
181        namespace: &[u8],
182        subject: Self::Subject<'_, D>,
183    ) -> Option<Attestation<Self>>;
184
185    /// Verifies a single attestation against the participant material managed by the scheme.
186    fn verify_attestation<D: Digest>(
187        &self,
188        namespace: &[u8],
189        subject: Self::Subject<'_, D>,
190        attestation: &Attestation<Self>,
191    ) -> bool;
192
193    /// Batch-verifies attestations and separates valid attestations from signer indices that failed
194    /// verification.
195    ///
196    /// Callers must not include duplicate attestations from the same signer.
197    fn verify_attestations<R, D, I>(
198        &self,
199        _rng: &mut R,
200        namespace: &[u8],
201        subject: Self::Subject<'_, D>,
202        attestations: I,
203    ) -> Verification<Self>
204    where
205        R: Rng + CryptoRng,
206        D: Digest,
207        I: IntoIterator<Item = Attestation<Self>>,
208    {
209        let mut invalid = BTreeSet::new();
210
211        let verified = attestations.into_iter().filter_map(|attestation| {
212            if self.verify_attestation(namespace, subject.clone(), &attestation) {
213                Some(attestation)
214            } else {
215                invalid.insert(attestation.signer);
216                None
217            }
218        });
219
220        Verification::new(verified.collect(), invalid.into_iter().collect())
221    }
222
223    /// Assembles attestations into a certificate, returning `None` if the threshold is not met.
224    ///
225    /// Callers must not include duplicate attestations from the same signer.
226    fn assemble<I>(&self, attestations: I) -> Option<Self::Certificate>
227    where
228        I: IntoIterator<Item = Attestation<Self>>;
229
230    /// Verifies a certificate that was recovered or received from the network.
231    fn verify_certificate<R: Rng + CryptoRng, D: Digest>(
232        &self,
233        rng: &mut R,
234        namespace: &[u8],
235        subject: Self::Subject<'_, D>,
236        certificate: &Self::Certificate,
237    ) -> bool;
238
239    /// Verifies a stream of certificates, returning `false` at the first failure.
240    fn verify_certificates<'a, R, D, I>(
241        &self,
242        rng: &mut R,
243        namespace: &[u8],
244        certificates: I,
245    ) -> bool
246    where
247        R: Rng + CryptoRng,
248        D: Digest,
249        I: Iterator<Item = (Self::Subject<'a, D>, &'a Self::Certificate)>,
250    {
251        for (subject, certificate) in certificates {
252            if !self.verify_certificate(rng, namespace, subject, certificate) {
253                return false;
254            }
255        }
256
257        true
258    }
259
260    /// Returns whether per-participant fault evidence can be safely exposed.
261    ///
262    /// Schemes where individual signatures can be safely reported as fault evidence should
263    /// return `true`.
264    fn is_attributable(&self) -> bool;
265
266    /// Encoding configuration for bounded-size certificate decoding used in network payloads.
267    fn certificate_codec_config(&self) -> <Self::Certificate as Read>::Cfg;
268
269    /// Encoding configuration that allows unbounded certificate decoding.
270    ///
271    /// Only use this when decoding data from trusted local storage, it must not be exposed to
272    /// adversarial inputs or network payloads.
273    fn certificate_codec_config_unbounded() -> <Self::Certificate as Read>::Cfg;
274}
275
276/// Supplies the signing scheme for a given scope.
277///
278/// This trait uses an associated `Scope` type, allowing implementations to work
279/// with any scope representation (e.g., epoch numbers, block heights, etc.).
280pub trait Provider: Clone + Send + Sync + 'static {
281    /// The scope type used to look up schemes.
282    type Scope: Clone + Send + Sync + 'static;
283    /// The signing scheme to provide.
284    type Scheme: Scheme;
285
286    /// Return the signing scheme that corresponds to `scope`.
287    fn scoped(&self, scope: Self::Scope) -> Option<Arc<Self::Scheme>>;
288
289    /// Return a certificate verifier that can validate certificates from all scopes.
290    ///
291    /// This method allows implementations to provide a verifier that can validate
292    /// certificates from all scopes (without scope-specific state). For example,
293    /// `bls12381_threshold::Scheme` maintains a static public key across epochs that
294    /// can be used to verify certificates from any epoch, even after the committee
295    /// has rotated and the underlying secret shares have been refreshed.
296    ///
297    /// The default implementation returns `None`. Callers should fall back to
298    /// [`Provider::scoped`] for scope-specific verification.
299    fn all(&self) -> Option<Arc<Self::Scheme>> {
300        None
301    }
302}
303
304/// Bitmap wrapper that tracks which participants signed a certificate.
305///
306/// Internally, it stores bits in 1-byte chunks for compact encoding.
307#[derive(Clone, Debug, PartialEq, Eq, Hash)]
308pub struct Signers {
309    bitmap: BitMap<1>,
310}
311
312impl Signers {
313    /// Builds [`Signers`] from an iterator of signer indices.
314    ///
315    /// # Panics
316    ///
317    /// Panics if the sequence contains indices larger than the size of the participant set
318    /// or duplicates.
319    pub fn from(participants: usize, signers: impl IntoIterator<Item = u32>) -> Self {
320        let mut bitmap = BitMap::zeroes(participants as u64);
321        for signer in signers.into_iter() {
322            assert!(
323                !bitmap.get(signer as u64),
324                "duplicate signer index: {signer}",
325            );
326            // We opt to not assert order here because some signing schemes allow
327            // for commutative aggregation of signatures (and sorting is unnecessary
328            // overhead).
329
330            bitmap.set(signer as u64, true);
331        }
332
333        Self { bitmap }
334    }
335
336    /// Returns the length of the bitmap (the size of the participant set).
337    #[allow(clippy::len_without_is_empty)]
338    pub const fn len(&self) -> usize {
339        self.bitmap.len() as usize
340    }
341
342    /// Returns how many participants are marked as signers.
343    pub fn count(&self) -> usize {
344        self.bitmap.count_ones() as usize
345    }
346
347    /// Iterates over signer indices in ascending order.
348    pub fn iter(&self) -> impl Iterator<Item = u32> + '_ {
349        self.bitmap
350            .iter()
351            .enumerate()
352            .filter_map(|(index, bit)| bit.then_some(index as u32))
353    }
354}
355
356impl Write for Signers {
357    fn write(&self, writer: &mut impl BufMut) {
358        self.bitmap.write(writer);
359    }
360}
361
362impl EncodeSize for Signers {
363    fn encode_size(&self) -> usize {
364        self.bitmap.encode_size()
365    }
366}
367
368impl Read for Signers {
369    type Cfg = usize;
370
371    fn read_cfg(reader: &mut impl Buf, max_participants: &usize) -> Result<Self, Error> {
372        let bitmap = BitMap::read_cfg(reader, &(*max_participants as u64))?;
373        // The participant count is treated as an upper bound for decoding flexibility, e.g. one
374        // might use `Scheme::certificate_codec_config_unbounded` for decoding certificates from
375        // local storage.
376        //
377        // Exact length validation **must** be enforced at verification time by the signing schemes
378        // against the actual participant set size.
379        Ok(Self { bitmap })
380    }
381}
382
383#[cfg(feature = "arbitrary")]
384impl arbitrary::Arbitrary<'_> for Signers {
385    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
386        let participants = u.arbitrary_len::<u8>()? % 10;
387        let signer_count = u.arbitrary_len::<u8>()?.min(participants);
388        let signers = (0..signer_count as u32).collect::<Vec<_>>();
389        Ok(Self::from(participants, signers))
390    }
391}
392
393/// A scheme provider that always returns the same scheme regardless of scope.
394#[derive(Clone, Debug)]
395pub struct ConstantProvider<S: Scheme, Sc = ()> {
396    scheme: Arc<S>,
397    _scope: core::marker::PhantomData<Sc>,
398}
399
400impl<S: Scheme, Sc> ConstantProvider<S, Sc> {
401    /// Creates a new provider that always returns the given scheme.
402    pub fn new(scheme: S) -> Self {
403        Self {
404            scheme: Arc::new(scheme),
405            _scope: core::marker::PhantomData,
406        }
407    }
408}
409
410impl<S: Scheme, Sc: Clone + Send + Sync + 'static> crate::certificate::Provider
411    for ConstantProvider<S, Sc>
412{
413    type Scope = Sc;
414    type Scheme = S;
415
416    fn scoped(&self, _: Sc) -> Option<Arc<S>> {
417        Some(self.scheme.clone())
418    }
419
420    fn all(&self) -> Option<Arc<Self::Scheme>> {
421        Some(self.scheme.clone())
422    }
423}
424
425#[cfg(feature = "mocks")]
426pub mod mocks {
427    //! Mocks for certificate signing schemes.
428
429    /// A fixture containing identities, identity private keys, per-participant
430    /// signing schemes, and a single verifier scheme.
431    #[derive(Clone, Debug)]
432    pub struct Fixture<S> {
433        /// A sorted vector of participant public identity keys.
434        pub participants: Vec<crate::ed25519::PublicKey>,
435        /// A sorted vector of participant private identity keys (matching order with `participants`).
436        pub private_keys: Vec<crate::ed25519::PrivateKey>,
437        /// A vector of per-participant scheme instances (matching order with `participants`).
438        pub schemes: Vec<S>,
439        /// A single scheme verifier.
440        pub verifier: S,
441    }
442}
443
444#[cfg(test)]
445mod tests {
446    use super::*;
447    use commonware_codec::{Decode, Encode};
448
449    #[test]
450    fn test_from_signers() {
451        let signers = Signers::from(6, [0, 3, 5]);
452        let collected: Vec<_> = signers.iter().collect();
453        assert_eq!(collected, vec![0, 3, 5]);
454        assert_eq!(signers.count(), 3);
455    }
456
457    #[test]
458    #[should_panic(expected = "bit 4 out of bounds (len: 4)")]
459    fn test_from_out_of_bounds() {
460        Signers::from(4, [0, 4]);
461    }
462
463    #[test]
464    #[should_panic(expected = "duplicate signer index: 0")]
465    fn test_from_duplicate() {
466        Signers::from(4, [0, 0, 1]);
467    }
468
469    #[test]
470    fn test_from_not_increasing() {
471        Signers::from(4, [2, 1]);
472    }
473
474    #[test]
475    fn test_codec_round_trip() {
476        let signers = Signers::from(9, [1, 6]);
477        let encoded = signers.encode();
478        let decoded = Signers::decode_cfg(encoded, &9).unwrap();
479        assert_eq!(decoded, signers);
480    }
481
482    #[test]
483    fn test_decode_respects_participant_limit() {
484        let signers = Signers::from(8, [0, 3, 7]);
485        let encoded = signers.encode();
486        // More participants than expected should fail.
487        assert!(Signers::decode_cfg(encoded.clone(), &2).is_err());
488        // Exact participant bound succeeds.
489        assert!(Signers::decode_cfg(encoded.clone(), &8).is_ok());
490        // Less participants than expected succeeds (upper bound).
491        assert!(Signers::decode_cfg(encoded, &10).is_ok());
492    }
493
494    #[cfg(feature = "arbitrary")]
495    mod conformance {
496        use super::*;
497        use commonware_codec::conformance::CodecConformance;
498
499        /// Test context type for generic scheme tests.
500        #[derive(Clone, Debug)]
501        pub struct TestSubject<'a> {
502            pub message: &'a [u8],
503        }
504
505        impl<'a> Subject for TestSubject<'a> {
506            fn namespace_and_message(&self, namespace: &[u8]) -> (Bytes, Bytes) {
507                (namespace.to_vec().into(), self.message.to_vec().into())
508            }
509        }
510
511        // Use the macro to generate the test scheme (signer/verifier are unused in conformance tests)
512        impl_certificate_ed25519!(TestSubject<'a>);
513
514        commonware_conformance::conformance_tests! {
515            CodecConformance<Signers>,
516            CodecConformance<Attestation<Scheme>>,
517        }
518    }
519}