Skip to main content

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