Skip to main content

commonware_cryptography/
certificate.rs

1//! Cryptographic primitives for generating and verifying certificates.
2//!
3//! This module provides the [`Verifier`] and [`Scheme`] traits and implementations for
4//! producing signatures, validating them (individually or in batches), assembling certificates, and
5//! 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::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::CryptoRng;
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 recovered certificate verification.
186///
187/// A `Verifier` verifies recovered certificates, but does not expose participant
188/// metadata or signing operations.
189pub trait Verifier: Clone + Debug + Send + Sync + 'static {
190    /// Subject type for certificate verification.
191    type Subject<'a, D: Digest>: Subject;
192
193    /// Public key type for participant identity used to order and index the participant set.
194    type PublicKey: PublicKey;
195    /// Certificate assembled from a set of attestations.
196    type Certificate: Clone + Debug + PartialEq + Eq + Hash + Send + Sync + Codec;
197
198    /// Verifies a certificate that was recovered or received from the network.
199    fn verify_certificate<R, D, M>(
200        &self,
201        rng: &mut R,
202        subject: Self::Subject<'_, D>,
203        certificate: &Self::Certificate,
204        strategy: &impl Strategy,
205    ) -> bool
206    where
207        R: CryptoRng,
208        D: Digest,
209        M: Faults;
210
211    /// Verifies a stream of certificates, returning `false` at the first failure.
212    fn verify_certificates<'a, R, D, I, M>(
213        &self,
214        rng: &mut R,
215        certificates: I,
216        strategy: &impl Strategy,
217    ) -> bool
218    where
219        R: CryptoRng,
220        D: Digest,
221        I: Iterator<Item = (Self::Subject<'a, D>, &'a Self::Certificate)>,
222        M: Faults,
223    {
224        for (subject, certificate) in certificates {
225            if !self.verify_certificate::<_, _, M>(rng, subject, certificate, strategy) {
226                return false;
227            }
228        }
229
230        true
231    }
232
233    /// Batch-verifies certificates and returns a per-item result.
234    ///
235    /// For batchable schemes, attempts batch verification first and bisects
236    /// on failure to efficiently identify invalid certificates. For
237    /// non-batchable schemes, verifies each certificate individually.
238    fn verify_certificates_bisect<'a, R, D, M>(
239        &self,
240        rng: &mut R,
241        certificates: &[(Self::Subject<'a, D>, &'a Self::Certificate)],
242        strategy: &impl Strategy,
243    ) -> Vec<bool>
244    where
245        R: CryptoRng,
246        D: Digest,
247        Self::Subject<'a, D>: Copy,
248        Self::Certificate: 'a,
249        M: Faults,
250    {
251        let len = certificates.len();
252        let mut verified = vec![false; len];
253        if len == 0 {
254            return verified;
255        }
256
257        // Non-batchable schemes (e.g. secp256r1) gain nothing from bisection
258        // since verify_certificates already checks one-by-one.
259        if !Self::is_batchable() {
260            for (i, (subject, certificate)) in certificates.iter().enumerate() {
261                verified[i] =
262                    self.verify_certificate::<_, _, M>(rng, *subject, certificate, strategy);
263            }
264            return verified;
265        }
266
267        // Iterative bisection: try the full range first. If batch verification
268        // passes, mark the entire range valid. If it fails, split in half and
269        // retry each half. Singletons that fail remain false.
270        //
271        //       [0..8) fail
272        //      /            \
273        //   [0..4) pass   [4..8) fail
274        //                /          \
275        //            [4..6) pass  [6..8) fail
276        //                        /        \
277        //                    [6..7) pass  [7..8) fail
278        let mut stack = vec![(0, len)];
279        while let Some((start, end)) = stack.pop() {
280            if self.verify_certificates::<_, D, _, M>(
281                rng,
282                certificates[start..end].iter().copied(),
283                strategy,
284            ) {
285                verified[start..end].fill(true);
286            } else if end - start > 1 {
287                let mid = start + (end - start) / 2;
288                stack.push((mid, end));
289                stack.push((start, mid));
290            }
291        }
292
293        verified
294    }
295
296    /// Returns whether this scheme benefits from batch verification.
297    ///
298    /// Schemes that benefit from batch verification (like [`ed25519`], [`bls12381_multisig`]
299    /// and [`bls12381_threshold`]) should return `true`, allowing callers to optimize by
300    /// deferring verification until multiple signatures are available.
301    ///
302    /// Schemes that don't benefit from batch verification (like [`secp256r1`]) should
303    /// return `false`, indicating that eager per-signature verification is preferred.
304    fn is_batchable() -> bool;
305
306    /// Encoding configuration for bounded-size certificate decoding used in network payloads.
307    fn certificate_codec_config(&self) -> <Self::Certificate as Read>::Cfg;
308
309    /// Encoding configuration that allows unbounded certificate decoding.
310    ///
311    /// Only use this when decoding data from trusted local storage, it must not be exposed to
312    /// adversarial inputs or network payloads.
313    fn certificate_codec_config_unbounded() -> <Self::Certificate as Read>::Cfg;
314}
315
316/// Cryptographic surface for multi-party certificate schemes.
317///
318/// A `Scheme` extends [`Verifier`] with the signing surface: it produces attestations, validates
319/// them (individually or in batches), and assembles certificates. Implementations may override the
320/// provided defaults to take advantage of scheme-specific batching strategies.
321pub trait Scheme: Verifier {
322    /// Signature emitted by individual participants.
323    type Signature: Clone + Debug + PartialEq + Eq + Hash + Send + Sync + CodecFixed<Cfg = ()>;
324
325    /// Returns the index of "self" in the participant set, if available.
326    /// Returns `None` if the scheme is a verifier-only instance.
327    fn me(&self) -> Option<Participant>;
328
329    /// Returns the ordered set of participant public identity keys managed by the scheme.
330    fn participants(&self) -> &Set<Self::PublicKey>;
331
332    /// Signs a subject.
333    /// Returns `None` if the scheme cannot sign (e.g. it's a verifier-only instance).
334    fn sign<D: Digest>(&self, subject: Self::Subject<'_, D>) -> Option<Attestation<Self>>;
335
336    /// Verifies a single attestation against the participant material managed by the scheme.
337    fn verify_attestation<R, D>(
338        &self,
339        rng: &mut R,
340        subject: Self::Subject<'_, D>,
341        attestation: &Attestation<Self>,
342        strategy: &impl Strategy,
343    ) -> bool
344    where
345        R: CryptoRng,
346        D: Digest;
347
348    /// Batch-verifies attestations and separates valid attestations from signer indices that failed
349    /// verification.
350    ///
351    /// Callers must not include duplicate attestations from the same signer. Passing duplicates
352    /// is undefined behavior, implementations may panic or produce incorrect results.
353    fn verify_attestations<R, D, I>(
354        &self,
355        rng: &mut R,
356        subject: Self::Subject<'_, D>,
357        attestations: I,
358        strategy: &impl Strategy,
359    ) -> Verification<Self>
360    where
361        R: CryptoRng,
362        D: Digest,
363        I: IntoIterator<Item = Attestation<Self>>,
364        I::IntoIter: Send,
365    {
366        let mut invalid = BTreeSet::new();
367
368        let verified = attestations.into_iter().filter_map(|attestation| {
369            if self.verify_attestation(&mut *rng, subject.clone(), &attestation, strategy) {
370                Some(attestation)
371            } else {
372                invalid.insert(attestation.signer);
373                None
374            }
375        });
376
377        Verification::new(verified.collect(), invalid.into_iter().collect())
378    }
379
380    /// Assembles attestations into a certificate, returning `None` if the threshold is not met.
381    ///
382    /// Callers must not include duplicate attestations from the same signer. Passing duplicates
383    /// is undefined behavior, implementations may panic or produce incorrect results.
384    fn assemble<I, M>(
385        &self,
386        attestations: I,
387        strategy: &impl Strategy,
388    ) -> Option<Self::Certificate>
389    where
390        I: IntoIterator<Item = Attestation<Self>>,
391        I::IntoIter: Send,
392        M: Faults;
393
394    /// Returns whether per-participant fault evidence can be safely exposed.
395    ///
396    /// Schemes where individual signatures can be safely reported as fault evidence should
397    /// return `true`.
398    fn is_attributable() -> bool;
399}
400
401/// A scheme handle returned by a [`Provider`] for one scope.
402///
403/// Always usable as a [`Verifier`] for certificate verification. The full signing scheme is
404/// recoverable with [`Scoped::into_scheme`] only when the scope was built with [`Scoped::scheme`].
405/// A scope built with [`Scoped::verifier`] yields `None`.
406#[derive(Clone, Debug)]
407pub struct Scoped<S: Scheme> {
408    scheme: Arc<S>,
409    can_sign: bool,
410}
411
412impl<S: Scheme> Scoped<S> {
413    /// Builds a verify-only scope.
414    pub const fn verifier(scheme: Arc<S>) -> Self {
415        Self {
416            scheme,
417            can_sign: false,
418        }
419    }
420
421    /// Builds a full signing scope.
422    pub const fn scheme(scheme: Arc<S>) -> Self {
423        Self {
424            scheme,
425            can_sign: true,
426        }
427    }
428
429    /// Returns the full signing scheme, or `None` for a verify-only scope.
430    pub fn into_scheme(self) -> Option<Arc<S>> {
431        self.can_sign.then_some(self.scheme)
432    }
433}
434
435impl<S: Scheme> Verifier for Scoped<S> {
436    type Subject<'a, D: Digest> = S::Subject<'a, D>;
437    type PublicKey = S::PublicKey;
438    type Certificate = S::Certificate;
439
440    fn verify_certificate<R, D, M>(
441        &self,
442        rng: &mut R,
443        subject: Self::Subject<'_, D>,
444        certificate: &Self::Certificate,
445        strategy: &impl Strategy,
446    ) -> bool
447    where
448        R: CryptoRng,
449        D: Digest,
450        M: Faults,
451    {
452        self.scheme
453            .verify_certificate::<_, D, M>(rng, subject, certificate, strategy)
454    }
455
456    fn verify_certificates<'a, R, D, I, M>(
457        &self,
458        rng: &mut R,
459        certificates: I,
460        strategy: &impl Strategy,
461    ) -> bool
462    where
463        R: CryptoRng,
464        D: Digest,
465        I: Iterator<Item = (Self::Subject<'a, D>, &'a Self::Certificate)>,
466        M: Faults,
467    {
468        self.scheme
469            .verify_certificates::<_, D, _, M>(rng, certificates, strategy)
470    }
471
472    fn is_batchable() -> bool {
473        S::is_batchable()
474    }
475
476    fn certificate_codec_config(&self) -> <Self::Certificate as Read>::Cfg {
477        self.scheme.certificate_codec_config()
478    }
479
480    fn certificate_codec_config_unbounded() -> <Self::Certificate as Read>::Cfg {
481        S::certificate_codec_config_unbounded()
482    }
483}
484
485/// Supplies the signing scheme for a given scope.
486///
487/// This trait uses an associated `Scope` type, allowing implementations to work
488/// with any scope representation (e.g., epoch numbers, block heights, etc.).
489pub trait Provider: Clone + Send + Sync + 'static {
490    /// The scope type used to look up schemes.
491    type Scope: Clone + Send + Sync + 'static;
492    /// The signing scheme to provide.
493    type Scheme: Scheme;
494
495    /// Return a [`Scoped`] for `scope` capable of verifying certificates produced under it.
496    ///
497    /// A scheme that can verify certificates from any scope without scope-specific state should
498    /// return a verify-only [`Scoped`] (via [`Scoped::verifier`]) for every scope. A fixed group
499    /// public key that survives committee rotation is one such case. A scheme that needs
500    /// scope-specific verification state should return `None` once that state is unavailable.
501    fn scoped(&self, scope: Self::Scope) -> Option<Scoped<Self::Scheme>>;
502
503    /// Return the full signing scheme that corresponds to `scope`, if available.
504    ///
505    /// The default returns a scheme only when [`Provider::scoped`] yields a signing scope, so a
506    /// verify-only scope produces `None`. Override this when the signing scheme is available even
507    /// for scopes that [`Provider::scoped`] serves with a verify-only result.
508    fn scheme(&self, scope: Self::Scope) -> Option<Arc<Self::Scheme>> {
509        self.scoped(scope).and_then(Scoped::into_scheme)
510    }
511}
512
513/// Bitmap wrapper that tracks which participants signed a certificate.
514///
515/// Internally, it stores bits in 1-byte chunks for compact encoding.
516#[derive(Clone, Debug, PartialEq, Eq, Hash)]
517pub struct Signers {
518    bitmap: BitMap<1>,
519}
520
521impl Signers {
522    /// Builds [`Signers`] from an iterator of signer indices.
523    ///
524    /// # Panics
525    ///
526    /// Panics if the sequence contains indices larger than the size of the participant set
527    /// or duplicates.
528    pub fn from(participants: usize, signers: impl IntoIterator<Item = Participant>) -> Self {
529        let mut bitmap = BitMap::zeroes(participants as u64);
530        for signer in signers.into_iter() {
531            assert!(
532                !bitmap.get(signer.get() as u64),
533                "duplicate signer index: {signer}",
534            );
535            // We opt to not assert order here because some signing schemes allow
536            // for commutative aggregation of signatures (and sorting is unnecessary
537            // overhead).
538
539            bitmap.set(signer.get() as u64, true);
540        }
541
542        Self { bitmap }
543    }
544
545    /// Returns the length of the bitmap (the size of the participant set).
546    #[allow(clippy::len_without_is_empty)]
547    pub const fn len(&self) -> usize {
548        self.bitmap.len() as usize
549    }
550
551    /// Returns how many participants are marked as signers.
552    pub fn count(&self) -> usize {
553        self.bitmap.count_ones() as usize
554    }
555
556    /// Iterates over signer indices in ascending order.
557    pub fn iter(&self) -> impl Iterator<Item = Participant> + '_ {
558        self.bitmap
559            .iter()
560            .enumerate()
561            .filter_map(|(index, bit)| bit.then_some(Participant::from_usize(index)))
562    }
563}
564
565impl Write for Signers {
566    fn write(&self, writer: &mut impl BufMut) {
567        self.bitmap.write(writer);
568    }
569}
570
571impl EncodeSize for Signers {
572    fn encode_size(&self) -> usize {
573        self.bitmap.encode_size()
574    }
575}
576
577impl Read for Signers {
578    type Cfg = usize;
579
580    fn read_cfg(reader: &mut impl Buf, max_participants: &usize) -> Result<Self, Error> {
581        let bitmap = BitMap::read_cfg(reader, &(*max_participants as u64))?;
582        // The participant count is treated as an upper bound for decoding flexibility, e.g. one
583        // might use `Scheme::certificate_codec_config_unbounded` for decoding certificates from
584        // local storage.
585        //
586        // Exact length validation **must** be enforced at verification time by the signing schemes
587        // against the actual participant set size.
588        Ok(Self { bitmap })
589    }
590}
591
592#[cfg(feature = "arbitrary")]
593impl arbitrary::Arbitrary<'_> for Signers {
594    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
595        let participants = u.arbitrary_len::<u8>()? % 10;
596        let signer_count = u.arbitrary_len::<u8>()?.min(participants);
597        let signers = (0..signer_count as u32)
598            .map(Participant::new)
599            .collect::<Vec<_>>();
600        Ok(Self::from(participants, signers))
601    }
602}
603
604/// A scheme provider that always returns the same scheme regardless of scope.
605#[derive(Clone, Debug)]
606pub struct ConstantProvider<S: Scheme, Sc = ()> {
607    scheme: Arc<S>,
608    _scope: core::marker::PhantomData<Sc>,
609}
610
611impl<S: Scheme, Sc> ConstantProvider<S, Sc> {
612    /// Creates a new provider that always returns the given scheme.
613    pub fn new(scheme: S) -> Self {
614        Self {
615            scheme: Arc::new(scheme),
616            _scope: core::marker::PhantomData,
617        }
618    }
619}
620
621impl<S: Scheme, Sc: Clone + Send + Sync + 'static> crate::certificate::Provider
622    for ConstantProvider<S, Sc>
623{
624    type Scope = Sc;
625    type Scheme = S;
626
627    fn scoped(&self, _: Sc) -> Option<Scoped<S>> {
628        Some(Scoped::scheme(self.scheme.clone()))
629    }
630}
631
632#[cfg(feature = "mocks")]
633pub mod mocks;
634
635#[cfg(test)]
636mod tests {
637    use super::*;
638    use crate::{ed25519::PrivateKey, sha256::Digest as Sha256Digest, Signer as _};
639    use commonware_codec::{Decode, Encode};
640    use commonware_math::algebra::Random;
641    use commonware_parallel::Sequential;
642    use commonware_utils::{ordered::Set, test_rng, N3f1, TryCollect};
643    use ed25519_fixture::{Scheme as Ed25519Scheme, TestSubject};
644
645    #[test]
646    fn test_from_signers() {
647        let signers = Signers::from(6, [0, 3, 5].map(Participant::new));
648        let collected: Vec<_> = signers.iter().collect();
649        assert_eq!(
650            collected,
651            vec![0, 3, 5]
652                .into_iter()
653                .map(Participant::new)
654                .collect::<Vec<_>>()
655        );
656        assert_eq!(signers.count(), 3);
657    }
658
659    #[test]
660    #[should_panic(expected = "bit 4 out of bounds (len: 4)")]
661    fn test_from_out_of_bounds() {
662        Signers::from(4, [0, 4].map(Participant::new));
663    }
664
665    #[test]
666    #[should_panic(expected = "duplicate signer index: 0")]
667    fn test_from_duplicate() {
668        Signers::from(4, [0, 0, 1].map(Participant::new));
669    }
670
671    #[test]
672    fn test_from_not_increasing() {
673        Signers::from(4, [2, 1].map(Participant::new));
674    }
675
676    #[test]
677    fn test_codec_round_trip() {
678        let signers = Signers::from(9, [1, 6].map(Participant::new));
679        let encoded = signers.encode();
680        let decoded = Signers::decode_cfg(encoded, &9).unwrap();
681        assert_eq!(decoded, signers);
682    }
683
684    #[test]
685    fn test_decode_respects_participant_limit() {
686        let signers = Signers::from(8, [0, 3, 7].map(Participant::new));
687        let encoded = signers.encode();
688        // More participants than expected should fail.
689        assert!(Signers::decode_cfg(encoded.clone(), &2).is_err());
690        // Exact participant bound succeeds.
691        assert!(Signers::decode_cfg(encoded.clone(), &8).is_ok());
692        // Less participants than expected succeeds (upper bound).
693        assert!(Signers::decode_cfg(encoded, &10).is_ok());
694    }
695
696    mod ed25519_fixture {
697        use crate::{certificate::Subject, impl_certificate_ed25519};
698
699        /// Test subject for certificate verification tests.
700        #[derive(Copy, Clone, Debug)]
701        pub struct TestSubject {
702            pub message: &'static [u8],
703        }
704
705        impl Subject for TestSubject {
706            type Namespace = Vec<u8>;
707
708            fn namespace<'a>(&self, derived: &'a Self::Namespace) -> &'a [u8] {
709                derived
710            }
711
712            fn message(&self) -> bytes::Bytes {
713                bytes::Bytes::from_static(self.message)
714            }
715        }
716
717        // Use the macro to generate the test scheme
718        impl_certificate_ed25519!(TestSubject, Vec<u8>);
719    }
720
721    const NAMESPACE: &[u8] = b"test-bisect";
722    const MESSAGE: &[u8] = b"good message";
723    const BAD_MESSAGE: &[u8] = b"bad message";
724
725    fn make_certificate(
726        schemes: &[Ed25519Scheme],
727        message: &'static [u8],
728    ) -> <Ed25519Scheme as Verifier>::Certificate {
729        let attestations: Vec<_> = schemes
730            .iter()
731            .filter_map(|s| s.sign::<Sha256Digest>(TestSubject { message }))
732            .collect();
733        schemes[0]
734            .assemble::<_, N3f1>(attestations, &Sequential)
735            .expect("assembly failed")
736    }
737
738    fn setup_ed25519(n: u32) -> (Vec<Ed25519Scheme>, Ed25519Scheme) {
739        let mut rng = test_rng();
740        let private_keys: Vec<_> = (0..n).map(|_| PrivateKey::random(&mut rng)).collect();
741        let participants: Set<crate::ed25519::PublicKey> = private_keys
742            .iter()
743            .map(|sk| sk.public_key())
744            .try_collect()
745            .unwrap();
746        let signers: Vec<_> = private_keys
747            .into_iter()
748            .map(|sk| Ed25519Scheme::signer(NAMESPACE, participants.clone(), sk).unwrap())
749            .collect();
750        let verifier = Ed25519Scheme::verifier(NAMESPACE, participants);
751        (signers, verifier)
752    }
753
754    #[test]
755    fn test_bisect_empty() {
756        let mut rng = test_rng();
757        let (_, verifier) = setup_ed25519(4);
758        let result = verifier.verify_certificates_bisect::<_, Sha256Digest, N3f1>(
759            &mut rng,
760            &[],
761            &Sequential,
762        );
763        assert!(result.is_empty());
764    }
765
766    #[test]
767    fn test_bisect_all_valid() {
768        let mut rng = test_rng();
769        let (schemes, verifier) = setup_ed25519(4);
770        let cert = make_certificate(&schemes, MESSAGE);
771        let good = TestSubject { message: MESSAGE };
772        let pairs: Vec<_> = (0..5).map(|_| (good, &cert)).collect();
773        let result = verifier.verify_certificates_bisect::<_, Sha256Digest, N3f1>(
774            &mut rng,
775            &pairs,
776            &Sequential,
777        );
778        assert_eq!(result, vec![true; 5]);
779    }
780
781    #[test]
782    fn test_bisect_mixed() {
783        let mut rng = test_rng();
784        let (schemes, verifier) = setup_ed25519(4);
785        let cert = make_certificate(&schemes, MESSAGE);
786        let good = TestSubject { message: MESSAGE };
787        let bad = TestSubject {
788            message: BAD_MESSAGE,
789        };
790        let pairs = vec![
791            (good, &cert),
792            (bad, &cert),
793            (good, &cert),
794            (bad, &cert),
795            (good, &cert),
796            (good, &cert),
797            (bad, &cert),
798            (bad, &cert),
799        ];
800        let expected = vec![true, false, true, false, true, true, false, false];
801        let result = verifier.verify_certificates_bisect::<_, Sha256Digest, N3f1>(
802            &mut rng,
803            &pairs,
804            &Sequential,
805        );
806        assert_eq!(result, expected);
807    }
808
809    #[test]
810    fn test_bisect_all_invalid() {
811        let mut rng = test_rng();
812        let (schemes, verifier) = setup_ed25519(4);
813        let cert = make_certificate(&schemes, MESSAGE);
814        let bad = TestSubject {
815            message: BAD_MESSAGE,
816        };
817        let pairs: Vec<_> = (0..4).map(|_| (bad, &cert)).collect();
818        let result = verifier.verify_certificates_bisect::<_, Sha256Digest, N3f1>(
819            &mut rng,
820            &pairs,
821            &Sequential,
822        );
823        assert_eq!(result, vec![false; 4]);
824    }
825
826    #[test]
827    fn test_bisect_single_valid() {
828        let mut rng = test_rng();
829        let (schemes, verifier) = setup_ed25519(4);
830        let cert = make_certificate(&schemes, MESSAGE);
831        let pairs = vec![(TestSubject { message: MESSAGE }, &cert)];
832        let result = verifier.verify_certificates_bisect::<_, Sha256Digest, N3f1>(
833            &mut rng,
834            &pairs,
835            &Sequential,
836        );
837        assert_eq!(result, vec![true]);
838    }
839
840    #[test]
841    fn test_bisect_single_invalid() {
842        let mut rng = test_rng();
843        let (schemes, verifier) = setup_ed25519(4);
844        let cert = make_certificate(&schemes, MESSAGE);
845        let pairs = vec![(
846            TestSubject {
847                message: BAD_MESSAGE,
848            },
849            &cert,
850        )];
851        let result = verifier.verify_certificates_bisect::<_, Sha256Digest, N3f1>(
852            &mut rng,
853            &pairs,
854            &Sequential,
855        );
856        assert_eq!(result, vec![false]);
857    }
858
859    #[test]
860    fn test_scoped_verifies_and_into_scheme() {
861        let mut rng = test_rng();
862        let (schemes, verifier) = setup_ed25519(4);
863        let cert = make_certificate(&schemes, MESSAGE);
864        let subject = TestSubject { message: MESSAGE };
865
866        let as_verifier = Scoped::verifier(Arc::new(verifier));
867        let as_scheme = Scoped::scheme(Arc::new(schemes[0].clone()));
868
869        // Both scopes verify a valid certificate through the `Verifier` impl.
870        assert!(as_verifier.verify_certificate::<_, Sha256Digest, N3f1>(
871            &mut rng,
872            subject,
873            &cert,
874            &Sequential,
875        ));
876        assert!(as_scheme.verify_certificate::<_, Sha256Digest, N3f1>(
877            &mut rng,
878            subject,
879            &cert,
880            &Sequential,
881        ));
882        let pairs = [(subject, &cert)];
883        assert!(as_verifier.verify_certificates::<_, Sha256Digest, _, N3f1>(
884            &mut rng,
885            pairs.iter().copied(),
886            &Sequential,
887        ));
888        assert_eq!(
889            <Scoped<Ed25519Scheme> as Verifier>::is_batchable(),
890            <Ed25519Scheme as Verifier>::is_batchable(),
891        );
892        assert_eq!(
893            as_verifier.certificate_codec_config(),
894            schemes[0].certificate_codec_config(),
895        );
896        let _ = <Scoped<Ed25519Scheme> as Verifier>::certificate_codec_config_unbounded();
897
898        // A verify-only scope never yields its scheme; a full scope always does.
899        assert!(as_verifier.into_scheme().is_none());
900        assert!(as_scheme.into_scheme().is_some());
901
902        let provider = ConstantProvider::<_, ()>::new(schemes[0].clone());
903        assert!(provider.scoped(()).is_some());
904        assert!(provider.scheme(()).is_some());
905    }
906
907    #[cfg(feature = "arbitrary")]
908    mod conformance {
909        use super::{ed25519_fixture::Scheme, *};
910        use commonware_codec::conformance::CodecConformance;
911
912        commonware_conformance::conformance_tests! {
913            CodecConformance<Signers>,
914            CodecConformance<Attestation<Scheme>>,
915        }
916    }
917}