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::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    /// Batch-verifies certificates and returns a per-item result.
306    ///
307    /// For batchable schemes, attempts batch verification first and bisects
308    /// on failure to efficiently identify invalid certificates. For
309    /// non-batchable schemes, verifies each certificate individually.
310    fn verify_certificates_bisect<'a, R, D, M>(
311        &self,
312        rng: &mut R,
313        certificates: &[(Self::Subject<'a, D>, &'a Self::Certificate)],
314        strategy: &impl Strategy,
315    ) -> Vec<bool>
316    where
317        R: CryptoRngCore,
318        D: Digest,
319        Self::Subject<'a, D>: Copy,
320        Self::Certificate: 'a,
321        M: Faults,
322    {
323        let len = certificates.len();
324        let mut verified = vec![false; len];
325        if len == 0 {
326            return verified;
327        }
328
329        // Non-batchable schemes (e.g. secp256r1) gain nothing from bisection
330        // since verify_certificates already checks one-by-one.
331        if !Self::is_batchable() {
332            for (i, (subject, certificate)) in certificates.iter().enumerate() {
333                verified[i] =
334                    self.verify_certificate::<_, _, M>(rng, *subject, certificate, strategy);
335            }
336            return verified;
337        }
338
339        // Iterative bisection: try the full range first. If batch verification
340        // passes, mark the entire range valid. If it fails, split in half and
341        // retry each half. Singletons that fail remain false.
342        //
343        //       [0..8) fail
344        //      /            \
345        //   [0..4) pass   [4..8) fail
346        //                /          \
347        //            [4..6) pass  [6..8) fail
348        //                        /        \
349        //                    [6..7) pass  [7..8) fail
350        let mut stack = vec![(0, len)];
351        while let Some((start, end)) = stack.pop() {
352            if self.verify_certificates::<_, D, _, M>(
353                rng,
354                certificates[start..end].iter().copied(),
355                strategy,
356            ) {
357                verified[start..end].fill(true);
358            } else if end - start > 1 {
359                let mid = start + (end - start) / 2;
360                stack.push((mid, end));
361                stack.push((start, mid));
362            }
363        }
364
365        verified
366    }
367
368    /// Returns whether per-participant fault evidence can be safely exposed.
369    ///
370    /// Schemes where individual signatures can be safely reported as fault evidence should
371    /// return `true`.
372    fn is_attributable() -> bool;
373
374    /// Returns whether this scheme benefits from batch verification.
375    ///
376    /// Schemes that benefit from batch verification (like [`ed25519`], [`bls12381_multisig`]
377    /// and [`bls12381_threshold`]) should return `true`, allowing callers to optimize by
378    /// deferring verification until multiple signatures are available.
379    ///
380    /// Schemes that don't benefit from batch verification (like [`secp256r1`]) should
381    /// return `false`, indicating that eager per-signature verification is preferred.
382    fn is_batchable() -> bool;
383
384    /// Encoding configuration for bounded-size certificate decoding used in network payloads.
385    fn certificate_codec_config(&self) -> <Self::Certificate as Read>::Cfg;
386
387    /// Encoding configuration that allows unbounded certificate decoding.
388    ///
389    /// Only use this when decoding data from trusted local storage, it must not be exposed to
390    /// adversarial inputs or network payloads.
391    fn certificate_codec_config_unbounded() -> <Self::Certificate as Read>::Cfg;
392}
393
394/// Supplies the signing scheme for a given scope.
395///
396/// This trait uses an associated `Scope` type, allowing implementations to work
397/// with any scope representation (e.g., epoch numbers, block heights, etc.).
398pub trait Provider: Clone + Send + Sync + 'static {
399    /// The scope type used to look up schemes.
400    type Scope: Clone + Send + Sync + 'static;
401    /// The signing scheme to provide.
402    type Scheme: Scheme;
403
404    /// Return the signing scheme that corresponds to `scope`.
405    fn scoped(&self, scope: Self::Scope) -> Option<Arc<Self::Scheme>>;
406
407    /// Return a certificate verifier that can validate certificates from all scopes.
408    ///
409    /// This method allows implementations to provide a verifier that can validate
410    /// certificates from all scopes (without scope-specific state). For example,
411    /// `bls12381_threshold::Scheme` maintains a static public key across epochs that
412    /// can be used to verify certificates from any epoch, even after the committee
413    /// has rotated and the underlying secret shares have been refreshed.
414    ///
415    /// The default implementation returns `None`. Callers should fall back to
416    /// [`Provider::scoped`] for scope-specific verification.
417    fn all(&self) -> Option<Arc<Self::Scheme>> {
418        None
419    }
420}
421
422/// Bitmap wrapper that tracks which participants signed a certificate.
423///
424/// Internally, it stores bits in 1-byte chunks for compact encoding.
425#[derive(Clone, Debug, PartialEq, Eq, Hash)]
426pub struct Signers {
427    bitmap: BitMap<1>,
428}
429
430impl Signers {
431    /// Builds [`Signers`] from an iterator of signer indices.
432    ///
433    /// # Panics
434    ///
435    /// Panics if the sequence contains indices larger than the size of the participant set
436    /// or duplicates.
437    pub fn from(participants: usize, signers: impl IntoIterator<Item = Participant>) -> Self {
438        let mut bitmap = BitMap::zeroes(participants as u64);
439        for signer in signers.into_iter() {
440            assert!(
441                !bitmap.get(signer.get() as u64),
442                "duplicate signer index: {signer}",
443            );
444            // We opt to not assert order here because some signing schemes allow
445            // for commutative aggregation of signatures (and sorting is unnecessary
446            // overhead).
447
448            bitmap.set(signer.get() as u64, true);
449        }
450
451        Self { bitmap }
452    }
453
454    /// Returns the length of the bitmap (the size of the participant set).
455    #[allow(clippy::len_without_is_empty)]
456    pub const fn len(&self) -> usize {
457        self.bitmap.len() as usize
458    }
459
460    /// Returns how many participants are marked as signers.
461    pub fn count(&self) -> usize {
462        self.bitmap.count_ones() as usize
463    }
464
465    /// Iterates over signer indices in ascending order.
466    pub fn iter(&self) -> impl Iterator<Item = Participant> + '_ {
467        self.bitmap
468            .iter()
469            .enumerate()
470            .filter_map(|(index, bit)| bit.then_some(Participant::from_usize(index)))
471    }
472}
473
474impl Write for Signers {
475    fn write(&self, writer: &mut impl BufMut) {
476        self.bitmap.write(writer);
477    }
478}
479
480impl EncodeSize for Signers {
481    fn encode_size(&self) -> usize {
482        self.bitmap.encode_size()
483    }
484}
485
486impl Read for Signers {
487    type Cfg = usize;
488
489    fn read_cfg(reader: &mut impl Buf, max_participants: &usize) -> Result<Self, Error> {
490        let bitmap = BitMap::read_cfg(reader, &(*max_participants as u64))?;
491        // The participant count is treated as an upper bound for decoding flexibility, e.g. one
492        // might use `Scheme::certificate_codec_config_unbounded` for decoding certificates from
493        // local storage.
494        //
495        // Exact length validation **must** be enforced at verification time by the signing schemes
496        // against the actual participant set size.
497        Ok(Self { bitmap })
498    }
499}
500
501#[cfg(feature = "arbitrary")]
502impl arbitrary::Arbitrary<'_> for Signers {
503    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
504        let participants = u.arbitrary_len::<u8>()? % 10;
505        let signer_count = u.arbitrary_len::<u8>()?.min(participants);
506        let signers = (0..signer_count as u32)
507            .map(Participant::new)
508            .collect::<Vec<_>>();
509        Ok(Self::from(participants, signers))
510    }
511}
512
513/// A scheme provider that always returns the same scheme regardless of scope.
514#[derive(Clone, Debug)]
515pub struct ConstantProvider<S: Scheme, Sc = ()> {
516    scheme: Arc<S>,
517    _scope: core::marker::PhantomData<Sc>,
518}
519
520impl<S: Scheme, Sc> ConstantProvider<S, Sc> {
521    /// Creates a new provider that always returns the given scheme.
522    pub fn new(scheme: S) -> Self {
523        Self {
524            scheme: Arc::new(scheme),
525            _scope: core::marker::PhantomData,
526        }
527    }
528}
529
530impl<S: Scheme, Sc: Clone + Send + Sync + 'static> crate::certificate::Provider
531    for ConstantProvider<S, Sc>
532{
533    type Scope = Sc;
534    type Scheme = S;
535
536    fn scoped(&self, _: Sc) -> Option<Arc<S>> {
537        Some(self.scheme.clone())
538    }
539
540    fn all(&self) -> Option<Arc<Self::Scheme>> {
541        Some(self.scheme.clone())
542    }
543}
544
545#[cfg(feature = "mocks")]
546pub mod mocks {
547    //! Mocks for certificate signing schemes.
548
549    /// A fixture containing identities, identity private keys, per-participant
550    /// signing schemes, and a single verifier scheme.
551    #[derive(Clone, Debug)]
552    pub struct Fixture<S> {
553        /// A sorted vector of participant public identity keys.
554        pub participants: Vec<crate::ed25519::PublicKey>,
555        /// A sorted vector of participant private identity keys (matching order with `participants`).
556        pub private_keys: Vec<crate::ed25519::PrivateKey>,
557        /// A vector of per-participant scheme instances (matching order with `participants`).
558        pub schemes: Vec<S>,
559        /// A single scheme verifier.
560        pub verifier: S,
561    }
562}
563
564#[cfg(test)]
565mod tests {
566    use super::*;
567    use crate::{ed25519::PrivateKey, sha256::Digest as Sha256Digest, Signer as _};
568    use commonware_codec::{Decode, Encode};
569    use commonware_math::algebra::Random;
570    use commonware_parallel::Sequential;
571    use commonware_utils::{ordered::Set, test_rng, N3f1, TryCollect};
572    use ed25519_fixture::{Scheme as Ed25519Scheme, TestSubject};
573
574    #[test]
575    fn test_from_signers() {
576        let signers = Signers::from(6, [0, 3, 5].map(Participant::new));
577        let collected: Vec<_> = signers.iter().collect();
578        assert_eq!(
579            collected,
580            vec![0, 3, 5]
581                .into_iter()
582                .map(Participant::new)
583                .collect::<Vec<_>>()
584        );
585        assert_eq!(signers.count(), 3);
586    }
587
588    #[test]
589    #[should_panic(expected = "bit 4 out of bounds (len: 4)")]
590    fn test_from_out_of_bounds() {
591        Signers::from(4, [0, 4].map(Participant::new));
592    }
593
594    #[test]
595    #[should_panic(expected = "duplicate signer index: 0")]
596    fn test_from_duplicate() {
597        Signers::from(4, [0, 0, 1].map(Participant::new));
598    }
599
600    #[test]
601    fn test_from_not_increasing() {
602        Signers::from(4, [2, 1].map(Participant::new));
603    }
604
605    #[test]
606    fn test_codec_round_trip() {
607        let signers = Signers::from(9, [1, 6].map(Participant::new));
608        let encoded = signers.encode();
609        let decoded = Signers::decode_cfg(encoded, &9).unwrap();
610        assert_eq!(decoded, signers);
611    }
612
613    #[test]
614    fn test_decode_respects_participant_limit() {
615        let signers = Signers::from(8, [0, 3, 7].map(Participant::new));
616        let encoded = signers.encode();
617        // More participants than expected should fail.
618        assert!(Signers::decode_cfg(encoded.clone(), &2).is_err());
619        // Exact participant bound succeeds.
620        assert!(Signers::decode_cfg(encoded.clone(), &8).is_ok());
621        // Less participants than expected succeeds (upper bound).
622        assert!(Signers::decode_cfg(encoded, &10).is_ok());
623    }
624
625    mod ed25519_fixture {
626        use crate::{certificate::Subject, impl_certificate_ed25519};
627
628        /// Test subject for certificate verification tests.
629        #[derive(Copy, Clone, Debug)]
630        pub struct TestSubject {
631            pub message: &'static [u8],
632        }
633
634        impl Subject for TestSubject {
635            type Namespace = Vec<u8>;
636
637            fn namespace<'a>(&self, derived: &'a Self::Namespace) -> &'a [u8] {
638                derived
639            }
640
641            fn message(&self) -> bytes::Bytes {
642                bytes::Bytes::from_static(self.message)
643            }
644        }
645
646        // Use the macro to generate the test scheme
647        impl_certificate_ed25519!(TestSubject, Vec<u8>);
648    }
649
650    const NAMESPACE: &[u8] = b"test-bisect";
651    const MESSAGE: &[u8] = b"good message";
652    const BAD_MESSAGE: &[u8] = b"bad message";
653
654    fn make_certificate(
655        schemes: &[Ed25519Scheme],
656        message: &'static [u8],
657    ) -> <Ed25519Scheme as Scheme>::Certificate {
658        let attestations: Vec<_> = schemes
659            .iter()
660            .filter_map(|s| s.sign::<Sha256Digest>(TestSubject { message }))
661            .collect();
662        schemes[0]
663            .assemble::<_, N3f1>(attestations, &Sequential)
664            .expect("assembly failed")
665    }
666
667    fn setup_ed25519(n: u32) -> (Vec<Ed25519Scheme>, Ed25519Scheme) {
668        let mut rng = test_rng();
669        let private_keys: Vec<_> = (0..n).map(|_| PrivateKey::random(&mut rng)).collect();
670        let participants: Set<crate::ed25519::PublicKey> = private_keys
671            .iter()
672            .map(|sk| sk.public_key())
673            .try_collect()
674            .unwrap();
675        let signers: Vec<_> = private_keys
676            .into_iter()
677            .map(|sk| Ed25519Scheme::signer(NAMESPACE, participants.clone(), sk).unwrap())
678            .collect();
679        let verifier = Ed25519Scheme::verifier(NAMESPACE, participants);
680        (signers, verifier)
681    }
682
683    #[test]
684    fn test_bisect_empty() {
685        let mut rng = test_rng();
686        let (_, verifier) = setup_ed25519(4);
687        let result = verifier.verify_certificates_bisect::<_, Sha256Digest, N3f1>(
688            &mut rng,
689            &[],
690            &Sequential,
691        );
692        assert!(result.is_empty());
693    }
694
695    #[test]
696    fn test_bisect_all_valid() {
697        let mut rng = test_rng();
698        let (schemes, verifier) = setup_ed25519(4);
699        let cert = make_certificate(&schemes, MESSAGE);
700        let good = TestSubject { message: MESSAGE };
701        let pairs: Vec<_> = (0..5).map(|_| (good, &cert)).collect();
702        let result = verifier.verify_certificates_bisect::<_, Sha256Digest, N3f1>(
703            &mut rng,
704            &pairs,
705            &Sequential,
706        );
707        assert_eq!(result, vec![true; 5]);
708    }
709
710    #[test]
711    fn test_bisect_mixed() {
712        let mut rng = test_rng();
713        let (schemes, verifier) = setup_ed25519(4);
714        let cert = make_certificate(&schemes, MESSAGE);
715        let good = TestSubject { message: MESSAGE };
716        let bad = TestSubject {
717            message: BAD_MESSAGE,
718        };
719        let pairs = vec![
720            (good, &cert),
721            (bad, &cert),
722            (good, &cert),
723            (bad, &cert),
724            (good, &cert),
725            (good, &cert),
726            (bad, &cert),
727            (bad, &cert),
728        ];
729        let expected = vec![true, false, true, false, true, true, false, false];
730        let result = verifier.verify_certificates_bisect::<_, Sha256Digest, N3f1>(
731            &mut rng,
732            &pairs,
733            &Sequential,
734        );
735        assert_eq!(result, expected);
736    }
737
738    #[test]
739    fn test_bisect_all_invalid() {
740        let mut rng = test_rng();
741        let (schemes, verifier) = setup_ed25519(4);
742        let cert = make_certificate(&schemes, MESSAGE);
743        let bad = TestSubject {
744            message: BAD_MESSAGE,
745        };
746        let pairs: Vec<_> = (0..4).map(|_| (bad, &cert)).collect();
747        let result = verifier.verify_certificates_bisect::<_, Sha256Digest, N3f1>(
748            &mut rng,
749            &pairs,
750            &Sequential,
751        );
752        assert_eq!(result, vec![false; 4]);
753    }
754
755    #[test]
756    fn test_bisect_single_valid() {
757        let mut rng = test_rng();
758        let (schemes, verifier) = setup_ed25519(4);
759        let cert = make_certificate(&schemes, MESSAGE);
760        let pairs = vec![(TestSubject { message: MESSAGE }, &cert)];
761        let result = verifier.verify_certificates_bisect::<_, Sha256Digest, N3f1>(
762            &mut rng,
763            &pairs,
764            &Sequential,
765        );
766        assert_eq!(result, vec![true]);
767    }
768
769    #[test]
770    fn test_bisect_single_invalid() {
771        let mut rng = test_rng();
772        let (schemes, verifier) = setup_ed25519(4);
773        let cert = make_certificate(&schemes, MESSAGE);
774        let pairs = vec![(
775            TestSubject {
776                message: BAD_MESSAGE,
777            },
778            &cert,
779        )];
780        let result = verifier.verify_certificates_bisect::<_, Sha256Digest, N3f1>(
781            &mut rng,
782            &pairs,
783            &Sequential,
784        );
785        assert_eq!(result, vec![false]);
786    }
787
788    #[cfg(feature = "arbitrary")]
789    mod conformance {
790        use super::{ed25519_fixture::Scheme, *};
791        use commonware_codec::conformance::CodecConformance;
792
793        commonware_conformance::conformance_tests! {
794            CodecConformance<Signers>,
795            CodecConformance<Attestation<Scheme>>,
796        }
797    }
798}