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