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#![cfg_attr(
20    feature = "bls12381",
21    doc = "
22- [`bls12381_multisig`]: Attributable signatures with aggregated verification. Signatures
23  can be aggregated into a single multi-signature for compact certificates while preserving
24  attribution (signer indices are stored alongside the aggregated signature). Callers must verify
25  a proof of possession for every BLS signing key before constructing the scheme.
26
27- [`bls12381_threshold`]: Non-attributable threshold signatures. Produces succinct
28  certificates that are constant-size regardless of committee size. Requires a trusted
29  setup (distributed key generation) and cannot attribute signatures to individual signers.
30"
31)]
32//! # Attributable Schemes and Fault Evidence
33//!
34//! Signing schemes differ in whether per-participant activities can be used as evidence of
35//! either liveness or of committing a fault:
36//!
37//! - **Attributable Schemes**: Individual signatures can be presented to some third party as
38//!   evidence of either liveness or of committing a fault. Certificates contain signer indices
39//!   alongside individual signatures, enabling secure per-participant activity tracking and
40//!   conflict detection.
41//!
42//! - **Non-Attributable Schemes**: Individual signatures cannot be presented to some third party
43//!   as evidence because they can be forged after collecting a quorum of partial signatures.
44//!   Authenticated peer connections allow this evidence to be used locally, but not by an external
45//!   observer.
46//!
47//! The [`Scheme::is_attributable()`] associated function signals whether evidence can be safely
48//! exposed to third parties.
49//!
50//! # Identity Keys vs Signing Keys
51//!
52//! A participant may supply both an identity key and a signing key. The identity key
53//! is used for assigning a unique order to the participant set and authenticating connections
54//! whereas the signing key is used for producing and verifying signatures/certificates.
55//!
56#![cfg_attr(
57    feature = "bls12381",
58    doc = "Some cryptographic schemes are only performant when used in batch verification (like
59[`bls12381_multisig`]) and/or are refreshed frequently (like [`bls12381_threshold`])."
60)]
61//! Refer to [ed25519] for an example of a scheme that uses the same key for both purposes.
62
63#[cfg(feature = "bls12381")]
64pub use crate::bls12381::certificate::{
65    multisig as bls12381_multisig, threshold as bls12381_threshold,
66};
67pub use crate::ed25519::certificate as ed25519;
68#[commonware_macros::stability(ALPHA)]
69pub use crate::secp256r1::certificate as secp256r1;
70use crate::{Digest, PublicKey};
71#[cfg(not(feature = "std"))]
72use alloc::{collections::BTreeSet, sync::Arc, vec, vec::Vec};
73use bytes::{BufMut, Bytes};
74use commonware_codec::{
75    Buf, Codec, CodecFixed, EncodeSize, Error as CodecError, FixedSize, MaxEncodeSize, Read,
76    ReadExt, Write, types::lazy::Lazy, util::max_items_size,
77};
78use commonware_parallel::Strategy;
79use commonware_utils::{Faults, Participant, Widen, bitmap::BitMap, iter::NonEmpty, ordered::Set};
80use core::{fmt::Debug, hash::Hash};
81use rand_chacha::ChaCha20Rng;
82use rand_core::{CryptoRng, SeedableRng};
83#[cfg(feature = "std")]
84use std::{collections::BTreeSet, sync::Arc, vec::Vec};
85use thiserror::Error;
86
87/// The scheme's own place in the participant set.
88#[derive(Clone, Copy, Debug, PartialEq, Eq)]
89pub enum Membership {
90    /// A participant that holds signing material at this index.
91    Signer(Participant),
92    /// A participant without signing material at this index.
93    Member(Participant),
94}
95
96impl Membership {
97    /// Returns the participant's index.
98    pub const fn index(&self) -> Participant {
99        match self {
100            Self::Signer(index) | Self::Member(index) => *index,
101        }
102    }
103
104    /// Returns the participant's index if it holds signing material.
105    pub const fn signer(&self) -> Option<Participant> {
106        match self {
107            Self::Signer(index) => Some(*index),
108            Self::Member(_) => None,
109        }
110    }
111}
112
113/// A participant's attestation for a certificate.
114#[derive(Clone, Debug)]
115pub struct Attestation<S: Scheme> {
116    /// Index of the signer inside the participant set.
117    pub signer: Participant,
118    /// Scheme-specific signature or share produced for a given subject.
119    pub signature: Lazy<S::Signature>,
120}
121
122impl<S: Scheme> PartialEq for Attestation<S> {
123    fn eq(&self, other: &Self) -> bool {
124        self.signer == other.signer && self.signature == other.signature
125    }
126}
127
128impl<S: Scheme> Eq for Attestation<S> {}
129
130impl<S: Scheme> Hash for Attestation<S> {
131    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
132        self.signer.hash(state);
133        self.signature.hash(state);
134    }
135}
136
137impl<S: Scheme> Write for Attestation<S> {
138    fn write(&self, writer: &mut impl BufMut) {
139        self.signer.write(writer);
140        self.signature.write(writer);
141    }
142}
143
144impl<S: Scheme> EncodeSize for Attestation<S> {
145    fn encode_size(&self) -> usize {
146        self.signer.encode_size() + self.signature.encode_size()
147    }
148}
149
150impl<S: Scheme> MaxEncodeSize for Attestation<S> {
151    const MAX_ENCODE_SIZE: usize = Participant::MAX_ENCODE_SIZE + S::Signature::SIZE;
152}
153
154impl<S: Scheme> Read for Attestation<S> {
155    type Cfg = ();
156
157    fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
158        let signer = Participant::read(reader)?;
159        let signature = ReadExt::read(reader)?;
160
161        Ok(Self { signer, signature })
162    }
163}
164
165#[cfg(feature = "arbitrary")]
166impl<S: Scheme> arbitrary::Arbitrary<'_> for Attestation<S>
167where
168    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
169{
170    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
171        let signer = Participant::arbitrary(u)?;
172        let signature = S::Signature::arbitrary(u)?;
173        Ok(Self {
174            signer,
175            signature: signature.into(),
176        })
177    }
178}
179
180/// Result of batch-verifying attestations.
181pub struct Verification<S: Scheme> {
182    /// Contains the attestations accepted by the scheme.
183    pub verified: Vec<Attestation<S>>,
184    /// Identifies the participant indices rejected during batch verification.
185    pub invalid: Vec<Participant>,
186}
187
188impl<S: Scheme> Verification<S> {
189    /// Creates a new `Verification` result.
190    pub const fn new(verified: Vec<Attestation<S>>, invalid: Vec<Participant>) -> Self {
191        Self { verified, invalid }
192    }
193}
194
195/// Errors returned while assembling attestations into a certificate.
196#[derive(Debug, Error, PartialEq, Eq)]
197pub enum AssemblyError {
198    /// Structurally valid attestations do not meet the scheme's quorum.
199    #[error("insufficient attestations: expected={0}, found={1}")]
200    InsufficientAttestations(u32, u32),
201    /// An attestation references an index outside the participant set.
202    #[error("unknown signer: {0}")]
203    UnknownSigner(Participant),
204    /// More than one attestation references the same signer.
205    #[error("duplicate signer: {0}")]
206    DuplicateSigner(Participant),
207    /// An attestation contains a malformed encoded signature.
208    #[error("malformed signature from signer: {0}")]
209    MalformedSignature(Participant),
210    /// The scheme could not recover a certificate from structurally valid attestations.
211    #[error("certificate recovery failed")]
212    RecoveryFailed,
213}
214
215/// Trait for namespace types that can derive themselves from a base namespace.
216///
217/// This trait is implemented by namespace types to define how they are computed
218/// from a base namespace string.
219pub trait Namespace: Clone + Send + Sync {
220    /// Derive a namespace from the given base.
221    fn derive(namespace: &[u8]) -> Self;
222}
223
224impl Namespace for Vec<u8> {
225    fn derive(namespace: &[u8]) -> Self {
226        namespace.to_vec()
227    }
228}
229
230/// Identifies the subject of a signature or certificate.
231pub trait Subject: Clone + Debug + Send + Sync {
232    /// Pre-computed namespace(s) for this subject type.
233    type Namespace: Namespace;
234
235    /// Get the namespace bytes for this subject instance.
236    fn namespace<'a>(&self, derived: &'a Self::Namespace) -> &'a [u8];
237
238    /// Get the message bytes for this subject instance.
239    fn message(&self) -> Bytes;
240}
241
242/// Cryptographic surface for recovered certificate verification.
243///
244/// A `Verifier` verifies recovered certificates, but does not expose participant
245/// metadata or signing operations.
246pub trait Verifier: Clone + Debug + Send + Sync + 'static {
247    /// Subject type for certificate verification.
248    type Subject<'a, D: Digest>: Subject;
249
250    /// Fault model used to compute certificate quorums.
251    type Faults: Faults;
252
253    /// Public key type for participant identity used to order and index the participant set.
254    type PublicKey: PublicKey;
255
256    /// Certificate assembled from a set of attestations.
257    type Certificate: Clone + Debug + Eq + Hash + Send + Sync + Codec;
258
259    /// Verifies a certificate that was recovered or received from the network.
260    ///
261    /// Acceptance authenticates a quorum for `subject` under the scheme's key setup, fault model,
262    /// and cryptographic assumptions. It need not authenticate the individual attestations used
263    /// to assemble the certificate.
264    fn verify_certificate<R, D>(
265        &self,
266        rng: &mut R,
267        subject: Self::Subject<'_, D>,
268        certificate: &Self::Certificate,
269        strategy: &impl Strategy,
270    ) -> bool
271    where
272        R: CryptoRng,
273        D: Digest;
274
275    /// Verifies a non-empty stream of certificates, returning `false` at the first verification
276    /// failure.
277    fn verify_certificates<'a, R, D, I>(
278        &self,
279        rng: &mut R,
280        certificates: NonEmpty<I>,
281        strategy: &impl Strategy,
282    ) -> bool
283    where
284        R: CryptoRng,
285        D: Digest,
286        I: Iterator<Item = (Self::Subject<'a, D>, &'a Self::Certificate)>,
287    {
288        for (subject, certificate) in certificates {
289            if !self.verify_certificate(rng, subject, certificate, strategy) {
290                return false;
291            }
292        }
293
294        true
295    }
296
297    /// Batch-verifies certificates and returns a per-item result.
298    ///
299    /// For batchable schemes, attempts batch verification first and bisects
300    /// on failure to efficiently identify invalid certificates. For
301    /// non-batchable schemes, verifies each certificate individually.
302    /// Empty input returns an empty result.
303    fn verify_certificates_bisect<'a, R, D>(
304        &self,
305        rng: &mut R,
306        certificates: &[(Self::Subject<'a, D>, &'a Self::Certificate)],
307        strategy: &impl Strategy,
308    ) -> Vec<bool>
309    where
310        R: CryptoRng,
311        D: Digest,
312        Self::Subject<'a, D>: Copy,
313    {
314        let len = certificates.len();
315        let mut verified = vec![false; len];
316        if len == 0 {
317            return verified;
318        }
319
320        // Non-batchable schemes (e.g. secp256r1) gain nothing from bisection
321        // since verify_certificates already checks one-by-one.
322        if !Self::is_batchable() {
323            for (i, (subject, certificate)) in certificates.iter().enumerate() {
324                verified[i] = self.verify_certificate(rng, *subject, certificate, strategy);
325            }
326            return verified;
327        }
328
329        // Iterative bisection: try the full range first. If batch verification
330        // passes, mark the entire range valid. If it fails, split in half and
331        // retry each half. Singletons that fail remain false.
332        //
333        //       [0..8) fail
334        //      /            \
335        //   [0..4) pass   [4..8) fail
336        //                /          \
337        //            [4..6) pass  [6..8) fail
338        //                        /        \
339        //                    [6..7) pass  [7..8) fail
340        let mut stack = vec![(0, len)];
341        while let Some((start, end)) = stack.pop() {
342            let (first, rest) = certificates[start..end]
343                .split_first()
344                .expect("bisection ranges are non-empty");
345            let certificates = NonEmpty::new(*first, rest.iter().copied());
346            if self.verify_certificates(rng, certificates, strategy) {
347                verified[start..end].fill(true);
348            } else if end - start > 1 {
349                let mid = start + (end - start) / 2;
350                stack.push((mid, end));
351                stack.push((start, mid));
352            }
353        }
354
355        verified
356    }
357
358    /// Returns whether this scheme benefits from batch verification.
359    ///
360    /// Schemes that benefit from batch verification should return `true`, allowing callers to
361    /// optimize by deferring verification until multiple signatures are available.
362    ///
363    /// Schemes that don't benefit from batch verification (like [`secp256r1`]) should
364    /// return `false`, indicating that eager per-signature verification is preferred.
365    fn is_batchable() -> bool;
366
367    /// Returns an upper bound on the encoded size of a certificate that decodes under
368    /// [`Self::certificate_codec_config`] for a verifier over at most `participants` participants.
369    fn max_certificate_size(participants: u32) -> usize;
370
371    /// Encoding configuration for bounded-size certificate decoding used in network payloads.
372    fn certificate_codec_config(&self) -> <Self::Certificate as Read>::Cfg;
373
374    /// Encoding configuration that allows unbounded certificate decoding.
375    ///
376    /// Only use this when decoding data from trusted local storage, it must not be exposed to
377    /// adversarial inputs or network payloads.
378    fn certificate_codec_config_unbounded() -> <Self::Certificate as Read>::Cfg;
379}
380
381/// Cryptographic surface for multi-party certificate schemes.
382///
383/// A `Scheme` extends [`Verifier`] with the signing surface: it produces attestations, validates
384/// them (individually or in batches), and assembles certificates. Implementations may override the
385/// provided defaults to take advantage of scheme-specific batching strategies.
386pub trait Scheme: Verifier {
387    /// Signature emitted by individual participants.
388    type Signature: Clone + Debug + Eq + Hash + Send + Sync + CodecFixed<Cfg = ()>;
389
390    /// Returns the scheme's own [`Membership`] in [`Self::participants`], or `None` if it
391    /// holds no participant identity.
392    fn me(&self) -> Option<Membership>;
393
394    /// Returns the ordered set of participant public identity keys managed by the scheme.
395    fn participants(&self) -> &Set<Self::PublicKey>;
396
397    /// Signs a subject, or returns `None` if the scheme cannot sign (a verifier or member
398    /// instance).
399    fn sign<D: Digest>(&self, subject: Self::Subject<'_, D>) -> Option<Attestation<Self>>;
400
401    /// Verifies a single attestation against the participant material managed by the scheme.
402    fn verify_attestation<R, D>(
403        &self,
404        rng: &mut R,
405        subject: Self::Subject<'_, D>,
406        attestation: &Attestation<Self>,
407        strategy: &impl Strategy,
408    ) -> bool
409    where
410        R: CryptoRng,
411        D: Digest;
412
413    /// Batch-verifies attestations and separates valid attestations from signer indices that failed
414    /// verification. Empty input produces an empty result in both sets.
415    ///
416    /// Callers must not include duplicate attestations from the same signer: duplicates may
417    /// produce incorrect results.
418    fn verify_attestations<R, D, I>(
419        &self,
420        rng: &mut R,
421        subject: Self::Subject<'_, D>,
422        attestations: I,
423        strategy: &impl Strategy,
424    ) -> Verification<Self>
425    where
426        R: CryptoRng,
427        D: Digest,
428        I: IntoIterator<Item = Attestation<Self>>,
429        I::IntoIter: Send,
430    {
431        let mut invalid = BTreeSet::new();
432
433        let verified = attestations.into_iter().filter_map(|attestation| {
434            if self.verify_attestation(&mut *rng, subject.clone(), &attestation, strategy) {
435                Some(attestation)
436            } else {
437                invalid.insert(attestation.signer);
438                None
439            }
440        });
441
442        Verification::new(verified.collect(), invalid.into_iter().collect())
443    }
444
445    /// Attempts to construct an authenticated certificate from pending and verified attestations.
446    ///
447    /// `verified` must already be valid for this exact scheme configuration and `subject`.
448    /// Signers must be unique across both inputs. Only pending attestations undergo fallback
449    /// verification.
450    ///
451    /// `Ok` authenticates the certificate for `subject`, but does not guarantee that each pending
452    /// attestation was individually verified. Callers requiring individual correctness should use
453    /// [`Self::verify_attestations`] followed by [`Self::assemble`].
454    ///
455    /// On failure, `Err` contains the verification results for `pending`. Accepted attestations
456    /// can be retained alongside `verified` for subsequent assembly, and may already form a quorum.
457    fn optimistic_assemble<'a, R, D, I, J>(
458        &self,
459        rng: &mut R,
460        subject: Self::Subject<'_, D>,
461        pending: I,
462        verified: J,
463        strategy: &impl Strategy,
464    ) -> Result<Self::Certificate, Verification<Self>>
465    where
466        R: CryptoRng,
467        D: Digest,
468        I: IntoIterator<Item = Attestation<Self>>,
469        I::IntoIter: ExactSizeIterator + Send,
470        J: IntoIterator<Item = &'a Attestation<Self>>,
471        J::IntoIter: Send;
472
473    /// Assembles a non-empty stream of attestations into a candidate certificate.
474    ///
475    /// Inputs may be unverified. `Ok` does not authenticate them or the resulting certificate.
476    /// Callers using such inputs must call [`Verifier::verify_certificate`] for the intended subject.
477    ///
478    /// Insufficient input returns [`AssemblyError::InsufficientAttestations`].
479    /// A signer-unique quorum already verified for one subject must assemble successfully into a
480    /// certificate that verifies for that subject.
481    fn assemble<I>(
482        &self,
483        attestations: NonEmpty<I>,
484        strategy: &impl Strategy,
485    ) -> Result<Self::Certificate, AssemblyError>
486    where
487        I: Iterator<Item = Attestation<Self>> + Send;
488
489    /// Returns whether per-participant fault evidence can be safely exposed.
490    ///
491    /// Schemes where individual signatures can be safely reported as fault evidence should
492    /// return `true`.
493    fn is_attributable() -> bool;
494}
495
496/// Verifies pending attestations before assembling them with verified attestations.
497///
498/// Inputs must satisfy [`Scheme::optimistic_assemble`]'s requirements.
499/// Rejected attestations return `Err` even if the remaining attestations could certify,
500/// preserving fault evidence for the caller. Assembly failure also returns the pending results.
501pub fn verify_then_assemble<'a, S, R, D, I, J>(
502    scheme: &S,
503    rng: &mut R,
504    subject: S::Subject<'_, D>,
505    pending: I,
506    verified: J,
507    strategy: &impl Strategy,
508) -> Result<S::Certificate, Verification<S>>
509where
510    S: Scheme,
511    R: CryptoRng,
512    D: Digest,
513    I: IntoIterator<Item = Attestation<S>>,
514    I::IntoIter: Send,
515    J: IntoIterator<Item = &'a Attestation<S>>,
516    J::IntoIter: Send,
517{
518    // Empty input already has a known verification result. Preserve rejected signer
519    // evidence for non-empty input even when the accepted attestations could certify.
520    let result = NonEmpty::try_new(pending.into_iter()).map_or_else(
521        || Verification::new(Vec::new(), Vec::new()),
522        |pending| scheme.verify_attestations::<_, D, _>(rng, subject, pending, strategy),
523    );
524    if !result.invalid.is_empty() {
525        return Err(result);
526    }
527
528    // A verified quorum needs no further authentication. Keep the pending results
529    // available if assembly fails.
530    let Some(attestations) = NonEmpty::try_new(
531        result
532            .verified
533            .iter()
534            .cloned()
535            .chain(verified.into_iter().cloned()),
536    ) else {
537        return Err(result);
538    };
539
540    scheme.assemble(attestations, strategy).map_err(|_| result)
541}
542
543/// Authenticates a candidate certificate first, verifying pending attestations only on failure.
544///
545/// Implements [`Scheme::optimistic_assemble`]'s input and result contract. A quorum consisting
546/// entirely of verified attestations is assembled without another certificate check.
547pub fn optimistic_assemble<'a, S, R, D, I, J>(
548    scheme: &S,
549    rng: &mut R,
550    subject: S::Subject<'_, D>,
551    pending: I,
552    verified: J,
553    strategy: &impl Strategy,
554) -> Result<S::Certificate, Verification<S>>
555where
556    S: Scheme,
557    R: CryptoRng,
558    D: Digest,
559    I: IntoIterator<Item = Attestation<S>>,
560    I::IntoIter: ExactSizeIterator + Send,
561    J: IntoIterator<Item = &'a Attestation<S>>,
562    J::IntoIter: Send,
563{
564    let pending = pending.into_iter();
565    let verified = verified.into_iter();
566
567    // A verified-only quorum needs no further authentication.
568    if pending.len() == 0 {
569        return verify_then_assemble::<_, _, D, _, _>(
570            scheme, rng, subject, pending, verified, strategy,
571        );
572    }
573
574    // Decode through the strategy before cloning so assembly and fallback reuse
575    // the decoded values.
576    let mut pending = strategy.map_collect_vec(pending, |attestation| {
577        let _ = attestation.signature.get();
578        attestation
579    });
580    let attestations = pending.iter().cloned().chain(verified.cloned());
581    if let Some(attestations) = NonEmpty::try_new(attestations)
582        && let Ok(certificate) = scheme.assemble(attestations, strategy)
583        && scheme.verify_certificate::<_, D>(rng, subject.clone(), &certificate, strategy)
584    {
585        return Ok(certificate);
586    }
587
588    // A single pending attestation is the leaf of the fallback search.
589    if pending.len() == 1 {
590        return Err(scheme.verify_attestations::<_, D, _>(rng, subject, pending, strategy));
591    }
592
593    // Each half needs ordinary verification, including every signed component
594    // of schemes with more than one signature per attestation. Independent RNGs
595    // allow both halves to run concurrently without sharing the caller's RNG.
596    let rest = pending.split_off(pending.len().div_ceil(2));
597    let mut first_rng = ChaCha20Rng::from_rng(&mut *rng);
598    let mut rest_rng = ChaCha20Rng::from_rng(rng);
599    let first_subject = subject.clone();
600    let (mut result, rest) = strategy.join(
601        || scheme.verify_attestations::<_, D, _>(&mut first_rng, first_subject, pending, strategy),
602        || scheme.verify_attestations::<_, D, _>(&mut rest_rng, subject, rest, strategy),
603    );
604    result.verified.extend(rest.verified);
605    result.invalid.extend(rest.invalid);
606    Err(result)
607}
608
609/// A scheme handle returned by a [`Provider`] for one scope.
610///
611/// Always usable as a [`Verifier`] for certificate verification. The full signing scheme is
612/// recoverable with [`Scoped::into_scheme`] only when the scope was built with [`Scoped::scheme`].
613/// A scope built with [`Scoped::verifier`] yields `None`.
614#[derive(Clone, Debug)]
615pub struct Scoped<S: Scheme> {
616    scheme: Arc<S>,
617    can_sign: bool,
618}
619
620impl<S: Scheme> Scoped<S> {
621    /// Builds a verify-only scope.
622    pub const fn verifier(scheme: Arc<S>) -> Self {
623        Self {
624            scheme,
625            can_sign: false,
626        }
627    }
628
629    /// Builds a full signing scope.
630    pub const fn scheme(scheme: Arc<S>) -> Self {
631        Self {
632            scheme,
633            can_sign: true,
634        }
635    }
636
637    /// Returns the full signing scheme, or `None` for a verify-only scope.
638    pub fn into_scheme(self) -> Option<Arc<S>> {
639        self.can_sign.then_some(self.scheme)
640    }
641}
642
643impl<S: Scheme> Verifier for Scoped<S> {
644    type Subject<'a, D: Digest> = S::Subject<'a, D>;
645    type Faults = S::Faults;
646    type PublicKey = S::PublicKey;
647    type Certificate = S::Certificate;
648
649    fn verify_certificate<R, D>(
650        &self,
651        rng: &mut R,
652        subject: Self::Subject<'_, D>,
653        certificate: &Self::Certificate,
654        strategy: &impl Strategy,
655    ) -> bool
656    where
657        R: CryptoRng,
658        D: Digest,
659    {
660        self.scheme
661            .verify_certificate(rng, subject, certificate, strategy)
662    }
663
664    fn verify_certificates<'a, R, D, I>(
665        &self,
666        rng: &mut R,
667        certificates: NonEmpty<I>,
668        strategy: &impl Strategy,
669    ) -> bool
670    where
671        R: CryptoRng,
672        D: Digest,
673        I: Iterator<Item = (Self::Subject<'a, D>, &'a Self::Certificate)>,
674    {
675        self.scheme.verify_certificates(rng, certificates, strategy)
676    }
677
678    fn is_batchable() -> bool {
679        S::is_batchable()
680    }
681
682    fn max_certificate_size(participants: u32) -> usize {
683        S::max_certificate_size(participants)
684    }
685
686    fn certificate_codec_config(&self) -> <Self::Certificate as Read>::Cfg {
687        self.scheme.certificate_codec_config()
688    }
689
690    fn certificate_codec_config_unbounded() -> <Self::Certificate as Read>::Cfg {
691        S::certificate_codec_config_unbounded()
692    }
693}
694
695/// Supplies the signing scheme for a given scope.
696///
697/// This trait uses an associated `Scope` type, allowing implementations to work
698/// with any scope representation (e.g., epoch numbers, block heights, etc.).
699pub trait Provider: Clone + Send + Sync + 'static {
700    /// The scope type used to look up schemes.
701    type Scope: Clone + Send + Sync + 'static;
702    /// The signing scheme to provide.
703    type Scheme: Scheme;
704
705    /// Return a [`Scoped`] for `scope` capable of verifying certificates produced under it.
706    ///
707    /// A scheme that can verify certificates from any scope without scope-specific state should
708    /// return a verify-only [`Scoped`] (via [`Scoped::verifier`]) for every scope. A fixed group
709    /// public key that survives committee rotation is one such case. A scheme that needs
710    /// scope-specific verification state should return `None` once that state is unavailable.
711    fn scoped(&self, scope: Self::Scope) -> Option<Scoped<Self::Scheme>>;
712
713    /// Return the full signing scheme that corresponds to `scope`, if available.
714    ///
715    /// The default returns a scheme only when [`Provider::scoped`] yields a signing scope, so a
716    /// verify-only scope produces `None`. Override this when the signing scheme is available even
717    /// for scopes that [`Provider::scoped`] serves with a verify-only result.
718    fn scheme(&self, scope: Self::Scope) -> Option<Arc<Self::Scheme>> {
719        self.scoped(scope).and_then(Scoped::into_scheme)
720    }
721}
722
723/// Bitmap wrapper that tracks which participants signed a certificate.
724///
725/// Internally, it stores bits in 1-byte chunks for compact encoding.
726#[derive(Clone, Debug, PartialEq, Eq, Hash)]
727pub struct Signers {
728    bitmap: BitMap<1>,
729}
730
731impl Signers {
732    /// Returns the largest encoded size of a signer bitmap over at most `participants`
733    /// participants.
734    pub(crate) fn max_encode_size(participants: u32) -> usize {
735        BitMap::<1>::max_encode_size(u64::from(participants))
736    }
737
738    /// Builds [`Signers`] from an iterator of signer indices.
739    ///
740    /// Indices need not be sorted: some signing schemes aggregate commutatively, so ordering
741    /// is left to the caller.
742    pub fn new(
743        participants: u32,
744        signers: impl IntoIterator<Item = Participant>,
745    ) -> Result<Self, AssemblyError> {
746        let mut bitmap = BitMap::zeroes(u64::from(participants));
747        for signer in signers.into_iter() {
748            let index = u64::from(signer.get());
749            if index >= bitmap.len() {
750                return Err(AssemblyError::UnknownSigner(signer));
751            }
752            if bitmap.get(index) {
753                return Err(AssemblyError::DuplicateSigner(signer));
754            }
755            bitmap.set(index, true);
756        }
757
758        Ok(Self { bitmap })
759    }
760
761    /// Requires at least `required` signers in an already validated set.
762    pub(crate) fn require(self, required: u32) -> Result<Self, AssemblyError> {
763        let found = u32::try_from(self.count()).expect("signer count exceeds u32::MAX");
764        if found < required {
765            return Err(AssemblyError::InsufficientAttestations(required, found));
766        }
767
768        Ok(self)
769    }
770
771    /// Returns the length of the bitmap (the size of the participant set).
772    #[allow(clippy::len_without_is_empty)]
773    pub const fn len(&self) -> usize {
774        self.bitmap.len() as usize
775    }
776
777    /// Returns how many participants are marked as signers.
778    pub fn count(&self) -> usize {
779        self.bitmap.count_ones() as usize
780    }
781
782    /// Iterates over signer indices in ascending order.
783    pub fn iter(&self) -> impl Iterator<Item = Participant> + '_ {
784        self.bitmap
785            .ones_iter()
786            .map(|index| Participant::from_usize(index as usize))
787    }
788}
789
790/// Returns an upper bound on the encoded size of a certificate with a signer bitmap over at most
791/// `participants` participants and one `signature_size`-byte signature per signer.
792pub(crate) fn max_individual_certificate_size(participants: u32, signature_size: usize) -> usize {
793    Signers::max_encode_size(participants)
794        + max_items_size(Widen::widen(participants), signature_size)
795}
796
797/// Builds [`Signers`] using the participant set as the valid signer-index range.
798///
799/// # Panics
800///
801/// Panics if the participant count exceeds `u32::MAX`.
802impl<'a, P, I> TryFrom<(&'a Set<P>, I)> for Signers
803where
804    I: IntoIterator<Item = Participant>,
805{
806    type Error = AssemblyError;
807
808    fn try_from((participants, signers): (&'a Set<P>, I)) -> Result<Self, Self::Error> {
809        let total = u32::try_from(participants.len()).expect("participant count exceeds u32::MAX");
810        Self::new(total, signers)
811    }
812}
813
814impl Write for Signers {
815    fn write(&self, writer: &mut impl BufMut) {
816        self.bitmap.write(writer);
817    }
818}
819
820impl EncodeSize for Signers {
821    fn encode_size(&self) -> usize {
822        self.bitmap.encode_size()
823    }
824}
825
826impl Read for Signers {
827    type Cfg = usize;
828
829    fn read_cfg(reader: &mut impl Buf, max_participants: &usize) -> Result<Self, CodecError> {
830        let bitmap = BitMap::read_cfg(reader, &(..=*max_participants as u64).into())?;
831        // The participant count is treated as an upper bound for decoding flexibility, e.g. one
832        // might use `Scheme::certificate_codec_config_unbounded` for decoding certificates from
833        // local storage.
834        //
835        // Exact length validation **must** be enforced at verification time by the signing schemes
836        // against the actual participant set size.
837        Ok(Self { bitmap })
838    }
839}
840
841#[cfg(feature = "arbitrary")]
842impl arbitrary::Arbitrary<'_> for Signers {
843    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
844        let participants = u.arbitrary_len::<u8>()? % 10;
845        let signer_count = u.arbitrary_len::<u8>()?.min(participants);
846        let signers = (0..u32::try_from(signer_count).expect("signer count exceeds u32::MAX"))
847            .map(Participant::new)
848            .collect::<Vec<_>>();
849        Ok(Self::new(participants.try_into().unwrap(), signers)
850            .expect("indices are unique and in range"))
851    }
852}
853
854/// A scheme provider that always returns the same scheme regardless of scope.
855#[derive(Clone, Debug)]
856pub struct ConstantProvider<S: Scheme, Sc = ()> {
857    scheme: Arc<S>,
858    _scope: core::marker::PhantomData<Sc>,
859}
860
861impl<S: Scheme, Sc> ConstantProvider<S, Sc> {
862    /// Creates a new provider that always returns the given scheme.
863    pub fn new(scheme: S) -> Self {
864        Self {
865            scheme: Arc::new(scheme),
866            _scope: core::marker::PhantomData,
867        }
868    }
869}
870
871impl<S: Scheme, Sc: Clone + Send + Sync + 'static> crate::certificate::Provider
872    for ConstantProvider<S, Sc>
873{
874    type Scope = Sc;
875    type Scheme = S;
876
877    fn scoped(&self, _: Sc) -> Option<Scoped<S>> {
878        Some(Scoped::scheme(self.scheme.clone()))
879    }
880}
881
882#[cfg(feature = "mocks")]
883pub mod mocks;
884
885#[cfg(test)]
886mod tests {
887    use super::*;
888    #[cfg(feature = "bls12381")]
889    use crate::bls12381::{
890        dkg::feldman_desmedt as dkg,
891        primitives::{
892            group::Private as BlsPrivate,
893            ops::{compute_public, sign_proof_of_possession, verify_proof_of_possession},
894            sharing::Mode,
895            variant::{MinPk, MinSig, Variant},
896        },
897    };
898    use crate::{
899        Signer as _,
900        ed25519::{PrivateKey, Signature},
901        sha256::Digest as Sha256Digest,
902    };
903    use commonware_codec::{Decode, Encode, varint::MAX_U32_VARINT_SIZE};
904    use commonware_math::algebra::Random;
905    use commonware_parallel::Sequential;
906    use commonware_utils::{
907        N3f1, TryCollect, Widen, non_empty,
908        ordered::{Quorum as _, Set},
909        test_rng,
910    };
911    #[cfg(feature = "bls12381")]
912    use commonware_utils::{NZU32, ordered::BiMap, sync::Mutex};
913    #[cfg(feature = "bls12381")]
914    use core::sync::atomic::{AtomicUsize, Ordering};
915    use ed25519_fixture::{Scheme as Ed25519Scheme, TestSubject};
916
917    #[test]
918    fn test_max_individual_certificate_size() {
919        // The bound covers every signature and never decreases as the committee grows.
920        let mut previous = 0;
921        for n in [0, 1, 7, 8, 9, 127, 128] {
922            let signers = Signers::new(n, (0..n).map(Participant::new)).unwrap();
923            let signatures = vec![[0u8; Signature::SIZE]; Widen::widen(n)];
924            assert_eq!(Signers::max_encode_size(n), signers.encode().len());
925            let size = max_individual_certificate_size(n, Signature::SIZE);
926            assert_eq!(
927                size,
928                signers.encode().len() + MAX_U32_VARINT_SIZE + signatures.len() * Signature::SIZE
929            );
930            assert!(signers.encode().len() + signatures.encode().len() <= size);
931            assert!(size >= previous);
932            previous = size;
933        }
934
935        // Counts up to `u32::MAX` fit the signature length prefix.
936        let n: usize = Widen::widen(u32::MAX);
937        assert_eq!(
938            max_individual_certificate_size(u32::MAX, Signature::SIZE),
939            u64::SIZE + n.div_ceil(8) + MAX_U32_VARINT_SIZE + n * Signature::SIZE,
940        );
941    }
942
943    #[test]
944    fn test_new_signers() {
945        let signers = Signers::new(6, [0, 3, 5].map(Participant::new)).unwrap();
946        let collected: Vec<_> = signers.iter().collect();
947        assert_eq!(
948            collected,
949            vec![0, 3, 5]
950                .into_iter()
951                .map(Participant::new)
952                .collect::<Vec<_>>()
953        );
954        assert_eq!(signers.count(), 3);
955    }
956
957    #[test]
958    fn test_new_out_of_bounds() {
959        assert_eq!(
960            Signers::new(4, [0, 4].map(Participant::new)),
961            Err(AssemblyError::UnknownSigner(Participant::new(4)))
962        );
963    }
964
965    #[test]
966    fn test_new_duplicate() {
967        assert_eq!(
968            Signers::new(4, [0, 0, 1].map(Participant::new)),
969            Err(AssemblyError::DuplicateSigner(Participant::new(0)))
970        );
971    }
972
973    #[test]
974    fn test_new_not_increasing() {
975        assert!(Signers::new(4, [2, 1].map(Participant::new)).is_ok());
976    }
977
978    #[test]
979    fn test_try_from_set_and_require() {
980        let participants = Set::from_iter_dedup(0..4);
981        let signers = Signers::try_from((&participants, [0, 2, 3].map(Participant::new)))
982            .unwrap()
983            .require(3)
984            .unwrap();
985        assert_eq!(signers.count(), 3);
986        assert_eq!(
987            Signers::try_from((&participants, [0, 2].map(Participant::new)))
988                .unwrap()
989                .require(3),
990            Err(AssemblyError::InsufficientAttestations(3, 2))
991        );
992    }
993
994    #[test]
995    fn test_try_from_set_checks_signer_bounds() {
996        let participants = Set::<u8>::default();
997        let signer = Participant::new(0);
998        assert_eq!(
999            Signers::try_from((&participants, [signer])),
1000            Err(AssemblyError::UnknownSigner(signer))
1001        );
1002    }
1003
1004    #[test]
1005    fn test_codec_round_trip() {
1006        let signers = Signers::new(9, [1, 6].map(Participant::new)).unwrap();
1007        let encoded = signers.encode();
1008        let decoded = Signers::decode_cfg(encoded, &9).unwrap();
1009        assert_eq!(decoded, signers);
1010    }
1011
1012    #[test]
1013    fn test_decode_respects_participant_limit() {
1014        let signers = Signers::new(8, [0, 3, 7].map(Participant::new)).unwrap();
1015        let encoded = signers.encode();
1016        // More participants than expected should fail.
1017        assert!(Signers::decode_cfg(encoded.clone(), &2).is_err());
1018        // Exact participant bound succeeds.
1019        assert!(Signers::decode_cfg(encoded.clone(), &8).is_ok());
1020        // Less participants than expected succeeds (upper bound).
1021        assert!(Signers::decode_cfg(encoded, &10).is_ok());
1022    }
1023
1024    mod ed25519_fixture {
1025        use crate::{certificate::Subject, impl_certificate_ed25519};
1026        use commonware_utils::N3f1;
1027
1028        /// Test subject for certificate verification tests.
1029        #[derive(Copy, Clone, Debug)]
1030        pub struct TestSubject {
1031            pub message: &'static [u8],
1032        }
1033
1034        impl Subject for TestSubject {
1035            type Namespace = Vec<u8>;
1036
1037            fn namespace<'a>(&self, derived: &'a Self::Namespace) -> &'a [u8] {
1038                derived
1039            }
1040
1041            fn message(&self) -> bytes::Bytes {
1042                bytes::Bytes::from_static(self.message)
1043            }
1044        }
1045
1046        // Use the macro to generate the test scheme
1047        impl_certificate_ed25519!(TestSubject, Vec<u8>, N3f1);
1048    }
1049
1050    const NAMESPACE: &[u8] = b"test-bisect";
1051    const MESSAGE: &[u8] = b"good message";
1052    const BAD_MESSAGE: &[u8] = b"bad message";
1053
1054    fn make_certificate(
1055        schemes: &[Ed25519Scheme],
1056        message: &'static [u8],
1057    ) -> <Ed25519Scheme as Verifier>::Certificate {
1058        let attestations: Vec<_> = schemes
1059            .iter()
1060            .filter_map(|s| s.sign::<Sha256Digest>(TestSubject { message }))
1061            .collect();
1062        schemes[0]
1063            .assemble(non_empty![@attestations], &Sequential)
1064            .expect("assembly failed")
1065    }
1066
1067    fn setup_ed25519(n: u32) -> (Vec<Ed25519Scheme>, Ed25519Scheme) {
1068        let mut rng = test_rng();
1069        let private_keys: Vec<_> = (0..n).map(|_| PrivateKey::random(&mut rng)).collect();
1070        let participants: Set<crate::ed25519::PublicKey> = private_keys
1071            .iter()
1072            .map(|sk| sk.public_key())
1073            .try_collect()
1074            .unwrap();
1075        let signers: Vec<_> = private_keys
1076            .into_iter()
1077            .map(|sk| Ed25519Scheme::signer(NAMESPACE, participants.clone(), sk).unwrap())
1078            .collect();
1079        let verifier = Ed25519Scheme::verifier(NAMESPACE, participants);
1080        (signers, verifier)
1081    }
1082
1083    /// Signers report a signing membership, members report a non-signing one at their
1084    /// identity key's position, and an identity-less verifier reports none.
1085    #[test]
1086    fn membership_reports_index_and_signing() {
1087        let (signers, verifier) = setup_ed25519(4);
1088        let participants = verifier.participants().clone();
1089        let signer = signers[0].me().unwrap();
1090        let index = signer.index();
1091        let key = participants.key(index).unwrap().clone();
1092
1093        let member = Ed25519Scheme::member(NAMESPACE, participants, &key).unwrap();
1094        assert_eq!(member.me(), Some(Membership::Member(index)));
1095        assert_eq!(member.me().unwrap().index(), index);
1096        assert_eq!(member.me().unwrap().signer(), None);
1097
1098        assert_eq!(signer, Membership::Signer(index));
1099        assert_eq!(signer.signer(), Some(index));
1100
1101        assert_eq!(verifier.me(), None);
1102    }
1103
1104    #[test]
1105    fn test_bisect_empty() {
1106        let mut rng = test_rng();
1107        let (_, verifier) = setup_ed25519(4);
1108        let result =
1109            verifier.verify_certificates_bisect::<_, Sha256Digest>(&mut rng, &[], &Sequential);
1110        assert!(result.is_empty());
1111    }
1112
1113    #[test]
1114    fn test_bisect_all_valid() {
1115        let mut rng = test_rng();
1116        let (schemes, verifier) = setup_ed25519(4);
1117        let cert = make_certificate(&schemes, MESSAGE);
1118        let good = TestSubject { message: MESSAGE };
1119        let pairs: Vec<_> = (0..5).map(|_| (good, &cert)).collect();
1120        let result =
1121            verifier.verify_certificates_bisect::<_, Sha256Digest>(&mut rng, &pairs, &Sequential);
1122        assert_eq!(result, vec![true; 5]);
1123    }
1124
1125    #[test]
1126    fn test_bisect_mixed() {
1127        let mut rng = test_rng();
1128        let (schemes, verifier) = setup_ed25519(4);
1129        let cert = make_certificate(&schemes, MESSAGE);
1130        let good = TestSubject { message: MESSAGE };
1131        let bad = TestSubject {
1132            message: BAD_MESSAGE,
1133        };
1134        let pairs = vec![
1135            (good, &cert),
1136            (bad, &cert),
1137            (good, &cert),
1138            (bad, &cert),
1139            (good, &cert),
1140            (good, &cert),
1141            (bad, &cert),
1142            (bad, &cert),
1143        ];
1144        let expected = vec![true, false, true, false, true, true, false, false];
1145        let result =
1146            verifier.verify_certificates_bisect::<_, Sha256Digest>(&mut rng, &pairs, &Sequential);
1147        assert_eq!(result, expected);
1148    }
1149
1150    #[test]
1151    fn test_bisect_all_invalid() {
1152        let mut rng = test_rng();
1153        let (schemes, verifier) = setup_ed25519(4);
1154        let cert = make_certificate(&schemes, MESSAGE);
1155        let bad = TestSubject {
1156            message: BAD_MESSAGE,
1157        };
1158        let pairs: Vec<_> = (0..4).map(|_| (bad, &cert)).collect();
1159        let result =
1160            verifier.verify_certificates_bisect::<_, Sha256Digest>(&mut rng, &pairs, &Sequential);
1161        assert_eq!(result, vec![false; 4]);
1162    }
1163
1164    #[test]
1165    fn test_bisect_single_valid() {
1166        let mut rng = test_rng();
1167        let (schemes, verifier) = setup_ed25519(4);
1168        let cert = make_certificate(&schemes, MESSAGE);
1169        let pairs = vec![(TestSubject { message: MESSAGE }, &cert)];
1170        let result =
1171            verifier.verify_certificates_bisect::<_, Sha256Digest>(&mut rng, &pairs, &Sequential);
1172        assert_eq!(result, vec![true]);
1173    }
1174
1175    #[test]
1176    fn test_bisect_single_invalid() {
1177        let mut rng = test_rng();
1178        let (schemes, verifier) = setup_ed25519(4);
1179        let cert = make_certificate(&schemes, MESSAGE);
1180        let pairs = vec![(
1181            TestSubject {
1182                message: BAD_MESSAGE,
1183            },
1184            &cert,
1185        )];
1186        let result =
1187            verifier.verify_certificates_bisect::<_, Sha256Digest>(&mut rng, &pairs, &Sequential);
1188        assert_eq!(result, vec![false]);
1189    }
1190
1191    #[test]
1192    fn test_scoped_verifies_and_into_scheme() {
1193        let mut rng = test_rng();
1194        let (schemes, verifier) = setup_ed25519(4);
1195        let cert = make_certificate(&schemes, MESSAGE);
1196        let subject = TestSubject { message: MESSAGE };
1197
1198        let as_verifier = Scoped::verifier(Arc::new(verifier));
1199        let as_scheme = Scoped::scheme(Arc::new(schemes[0].clone()));
1200
1201        // Both scopes verify a valid certificate through the `Verifier` impl.
1202        assert!(as_verifier.verify_certificate::<_, Sha256Digest>(
1203            &mut rng,
1204            subject,
1205            &cert,
1206            &Sequential,
1207        ));
1208        assert!(as_scheme.verify_certificate::<_, Sha256Digest>(
1209            &mut rng,
1210            subject,
1211            &cert,
1212            &Sequential,
1213        ));
1214        let pairs = [(subject, &cert)];
1215        assert!(as_verifier.verify_certificates::<_, Sha256Digest, _>(
1216            &mut rng,
1217            non_empty![@pairs.iter().copied()],
1218            &Sequential,
1219        ));
1220        assert_eq!(
1221            <Scoped<Ed25519Scheme> as Verifier>::is_batchable(),
1222            <Ed25519Scheme as Verifier>::is_batchable(),
1223        );
1224        assert_eq!(
1225            as_verifier.certificate_codec_config(),
1226            schemes[0].certificate_codec_config(),
1227        );
1228        for participants in [0, 4, 128] {
1229            assert_eq!(
1230                <Scoped<Ed25519Scheme> as Verifier>::max_certificate_size(participants),
1231                <Ed25519Scheme as Verifier>::max_certificate_size(participants),
1232            );
1233        }
1234        let _ = <Scoped<Ed25519Scheme> as Verifier>::certificate_codec_config_unbounded();
1235
1236        // A verify-only scope never yields its scheme; a full scope always does.
1237        assert!(as_verifier.into_scheme().is_none());
1238        assert!(as_scheme.into_scheme().is_some());
1239
1240        let provider = ConstantProvider::<_, ()>::new(schemes[0].clone());
1241        assert!(provider.scoped(()).is_some());
1242        assert!(provider.scheme(()).is_some());
1243    }
1244
1245    #[cfg(feature = "bls12381")]
1246    #[allow(dead_code)]
1247    mod threshold {
1248        use super::TestSubject;
1249        use crate::impl_certificate_bls12381_threshold;
1250        use commonware_utils::N3f1;
1251
1252        impl_certificate_bls12381_threshold!(TestSubject, Vec<u8>, N3f1);
1253    }
1254
1255    #[cfg(feature = "bls12381")]
1256    #[allow(dead_code)]
1257    mod multisig {
1258        use super::TestSubject;
1259        use crate::impl_certificate_bls12381_multisig;
1260        use commonware_utils::N3f1;
1261
1262        impl_certificate_bls12381_multisig!(TestSubject, Vec<u8>, N3f1);
1263    }
1264
1265    const SUBJECT: TestSubject = TestSubject {
1266        message: b"subject",
1267    };
1268    const OTHER_SUBJECT: TestSubject = TestSubject {
1269        message: b"other-subject",
1270    };
1271
1272    #[cfg(feature = "bls12381")]
1273    const THRESHOLD_NAMESPACE: &[u8] =
1274        b"_COMMONWARE_CRYPTOGRAPHY_CERTIFICATE_THRESHOLD_OPTIMISTIC_ASSEMBLE";
1275    #[cfg(feature = "bls12381")]
1276    const MULTISIG_NAMESPACE: &[u8] =
1277        b"_COMMONWARE_CRYPTOGRAPHY_CERTIFICATE_MULTISIG_OPTIMISTIC_ASSEMBLE";
1278    #[cfg(feature = "bls12381")]
1279    const MULTISIG_POP_NAMESPACE: &[u8] =
1280        b"_COMMONWARE_CRYPTOGRAPHY_CERTIFICATE_MULTISIG_OPTIMISTIC_ASSEMBLE_POP";
1281
1282    #[cfg(feature = "bls12381")]
1283    fn threshold_signers<V: Variant>(
1284        rng: &mut impl CryptoRng,
1285        n: u32,
1286    ) -> Vec<threshold::Scheme<crate::ed25519::PublicKey, V>> {
1287        let participants: Set<crate::ed25519::PublicKey> = (0..n)
1288            .map(|_| PrivateKey::random(&mut *rng).public_key())
1289            .try_collect()
1290            .unwrap();
1291        let (polynomial, shares) =
1292            dkg::deal_anonymous::<V, N3f1>(&mut *rng, Mode::NonZeroCounter, NZU32!(n));
1293        shares
1294            .into_iter()
1295            .map(|share| {
1296                threshold::Scheme::signer(
1297                    THRESHOLD_NAMESPACE,
1298                    participants.clone(),
1299                    polynomial.clone(),
1300                    share,
1301                )
1302                .unwrap()
1303            })
1304            .collect()
1305    }
1306
1307    #[cfg(feature = "bls12381")]
1308    fn multisig_signers<V: Variant>(
1309        rng: &mut impl CryptoRng,
1310        n: u32,
1311    ) -> Vec<multisig::Scheme<crate::ed25519::PublicKey, V>> {
1312        let identity_keys: Vec<_> = (0..n).map(|_| PrivateKey::random(&mut *rng)).collect();
1313        let signing_keys: Vec<_> = (0..n).map(|_| BlsPrivate::random(&mut *rng)).collect();
1314        let signing_publics: Vec<_> = signing_keys.iter().map(compute_public::<V>).collect();
1315
1316        for (private, public) in signing_keys.iter().zip(&signing_publics) {
1317            let proof = sign_proof_of_possession::<V>(private, MULTISIG_POP_NAMESPACE);
1318            verify_proof_of_possession::<V>(public, MULTISIG_POP_NAMESPACE, &proof)
1319                .expect("proof of possession must verify");
1320        }
1321
1322        let participants: BiMap<_, _> = identity_keys
1323            .iter()
1324            .map(|key| key.public_key())
1325            .zip(signing_publics)
1326            .try_collect()
1327            .unwrap();
1328        signing_keys
1329            .into_iter()
1330            .map(|private| {
1331                multisig::Scheme::signer(MULTISIG_NAMESPACE, participants.clone(), private)
1332                    .expect("signer must be a participant")
1333            })
1334            .collect()
1335    }
1336
1337    #[cfg(feature = "bls12381")]
1338    fn threshold_success<V: Variant>() {
1339        let mut rng = test_rng();
1340        let schemes = threshold_signers::<V>(&mut rng, 5);
1341        let quorum = N3f1::quorum(schemes.len()) as usize;
1342        let attestations: Vec<_> = schemes
1343            .iter()
1344            .take(quorum)
1345            .map(|scheme| scheme.sign::<Sha256Digest>(SUBJECT).unwrap())
1346            .collect();
1347        let expected = schemes[0]
1348            .assemble(non_empty![@attestations.clone()], &Sequential)
1349            .unwrap();
1350        let prior: Vec<_> = attestations[..1]
1351            .iter()
1352            .cloned()
1353            .map(Recording::outer)
1354            .collect();
1355        let calls = Arc::new(RecordingCalls::default());
1356        let verifier = Recording {
1357            inner: schemes[0].clone(),
1358            calls: Arc::clone(&calls),
1359        };
1360
1361        let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
1362            &mut rng,
1363            SUBJECT,
1364            attestations[1..].iter().cloned().map(Recording::outer),
1365            &prior,
1366            &Sequential,
1367        );
1368        let certificate = result.ok().expect("valid quorum must certify");
1369        assert_eq!(certificate, expected);
1370        assert!(calls.attestations.lock().is_empty());
1371        assert!(schemes[0].verify_certificate::<_, Sha256Digest>(
1372            &mut rng,
1373            SUBJECT,
1374            &certificate,
1375            &Sequential,
1376        ));
1377        assert!(!schemes[0].verify_certificate::<_, Sha256Digest>(
1378            &mut rng,
1379            OTHER_SUBJECT,
1380            &certificate,
1381            &Sequential,
1382        ));
1383
1384        // Threshold recovery uses the lowest quorum of signer indices.
1385        let unused_bad = schemes[quorum].sign::<Sha256Digest>(OTHER_SUBJECT).unwrap();
1386        let pending: Vec<_> = attestations
1387            .into_iter()
1388            .chain([unused_bad])
1389            .map(Recording::outer)
1390            .collect();
1391        let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
1392            &mut rng,
1393            SUBJECT,
1394            pending,
1395            std::iter::empty(),
1396            &Sequential,
1397        );
1398        assert_eq!(result.ok(), Some(expected));
1399        assert!(calls.attestations.lock().is_empty());
1400    }
1401
1402    #[cfg(feature = "bls12381")]
1403    #[test]
1404    fn test_threshold_recovers_exact_certificate_with_verified_attestations() {
1405        threshold_success::<MinPk>();
1406        threshold_success::<MinSig>();
1407    }
1408
1409    #[cfg(feature = "bls12381")]
1410    fn multisig_optimistic_assemble_uses_one_certificate_check<V: Variant>() {
1411        let mut rng = test_rng();
1412        let schemes = multisig_signers::<V>(&mut rng, 5);
1413        let quorum = N3f1::quorum(schemes.len()) as usize;
1414        let attestations: Vec<_> = schemes
1415            .iter()
1416            .take(quorum)
1417            .map(|scheme| scheme.sign::<Sha256Digest>(SUBJECT).unwrap())
1418            .collect();
1419        let prior: Vec<_> = attestations[..1]
1420            .iter()
1421            .cloned()
1422            .map(Recording::outer)
1423            .collect();
1424        let calls = Arc::new(RecordingCalls::default());
1425        let verifier = Recording {
1426            inner: schemes[0].clone(),
1427            calls: Arc::clone(&calls),
1428        };
1429
1430        let certificate = verifier
1431            .optimistic_assemble::<_, Sha256Digest, _, _>(
1432                &mut rng,
1433                SUBJECT,
1434                attestations[1..].iter().cloned().map(Recording::outer),
1435                &prior,
1436                &Sequential,
1437            )
1438            .ok()
1439            .expect("valid mixed quorum must certify");
1440
1441        assert_eq!(calls.certificates.load(Ordering::Relaxed), 1);
1442        assert!(calls.attestations.lock().is_empty());
1443        assert!(schemes[0].verify_certificate::<_, Sha256Digest>(
1444            &mut rng,
1445            SUBJECT,
1446            &certificate,
1447            &Sequential,
1448        ));
1449        assert!(!schemes[0].verify_certificate::<_, Sha256Digest>(
1450            &mut rng,
1451            OTHER_SUBJECT,
1452            &certificate,
1453            &Sequential,
1454        ));
1455    }
1456
1457    #[cfg(feature = "bls12381")]
1458    #[test]
1459    fn test_multisig_optimistic_assemble_uses_one_certificate_check() {
1460        multisig_optimistic_assemble_uses_one_certificate_check::<MinPk>();
1461        multisig_optimistic_assemble_uses_one_certificate_check::<MinSig>();
1462    }
1463
1464    #[test]
1465    fn test_optimistic_assemble_individual_inputs() {
1466        let mut rng = test_rng();
1467        let (schemes, verifier) = setup_ed25519(5);
1468        let empty = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
1469            &mut rng,
1470            SUBJECT,
1471            Vec::new(),
1472            std::iter::empty(),
1473            &Sequential,
1474        );
1475        let verification = empty.unwrap_err();
1476        assert!(verification.verified.is_empty());
1477        assert!(verification.invalid.is_empty());
1478
1479        let pending: Vec<_> = schemes
1480            .iter()
1481            .map(|scheme| scheme.sign::<Sha256Digest>(SUBJECT).unwrap())
1482            .collect();
1483        let certificate = verifier
1484            .optimistic_assemble::<_, Sha256Digest, _, _>(
1485                &mut rng,
1486                SUBJECT,
1487                pending,
1488                std::iter::empty(),
1489                &Sequential,
1490            )
1491            .ok()
1492            .expect("valid quorum must certify");
1493        assert!(verifier.verify_certificate::<_, Sha256Digest>(
1494            &mut rng,
1495            SUBJECT,
1496            &certificate,
1497            &Sequential,
1498        ));
1499
1500        let pending: Vec<_> = schemes
1501            .iter()
1502            .enumerate()
1503            .map(|(i, scheme)| {
1504                scheme
1505                    .sign::<Sha256Digest>(if i == 0 { OTHER_SUBJECT } else { SUBJECT })
1506                    .unwrap()
1507            })
1508            .collect();
1509        let signers: Vec<_> = pending.iter().map(|a| a.signer).collect();
1510        let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
1511            &mut rng,
1512            SUBJECT,
1513            pending,
1514            std::iter::empty(),
1515            &Sequential,
1516        );
1517        let verification = result.unwrap_err();
1518        assert_eq!(verification.invalid, vec![signers[0]]);
1519        assert_eq!(
1520            verification
1521                .verified
1522                .iter()
1523                .map(|a| a.signer)
1524                .collect::<Vec<_>>(),
1525            signers[1..]
1526        );
1527        let certificate = verifier
1528            .assemble(non_empty![@verification.verified], &Sequential)
1529            .unwrap();
1530        assert!(verifier.verify_certificate::<_, Sha256Digest>(
1531            &mut rng,
1532            SUBJECT,
1533            &certificate,
1534            &Sequential,
1535        ));
1536    }
1537
1538    #[test]
1539    fn test_optimistic_assemble_individual_quorum() {
1540        let mut rng = test_rng();
1541        let (schemes, verifier) = setup_ed25519(5);
1542        let quorum = N3f1::quorum(schemes.len()) as usize;
1543        let attestations: Vec<_> = schemes
1544            .iter()
1545            .take(quorum)
1546            .map(|s| s.sign::<Sha256Digest>(SUBJECT).unwrap())
1547            .collect();
1548
1549        // Both pending-only and verified-only inputs require quorum. On failure,
1550        // the result contains only pending attestations, never retained inputs.
1551        for count in [0, quorum - 1, quorum] {
1552            for pending in [0, count] {
1553                let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
1554                    &mut rng,
1555                    SUBJECT,
1556                    attestations[..pending].iter().cloned(),
1557                    &attestations[pending..count],
1558                    &Sequential,
1559                );
1560                assert_eq!(result.is_ok(), count == quorum);
1561                match result {
1562                    Ok(certificate) => assert!(verifier.verify_certificate::<_, Sha256Digest>(
1563                        &mut rng,
1564                        SUBJECT,
1565                        &certificate,
1566                        &Sequential,
1567                    )),
1568                    Err(verification) => {
1569                        assert_eq!(verification.verified, attestations[..pending]);
1570                        assert!(verification.invalid.is_empty());
1571                    }
1572                }
1573            }
1574        }
1575    }
1576
1577    #[test]
1578    fn test_optimistic_assemble_empty_committee() {
1579        let mut rng = test_rng();
1580        let (_, verifier) = setup_ed25519(0);
1581        let (schemes, _) = setup_ed25519(1);
1582        let unknown = schemes[0].sign::<Sha256Digest>(SUBJECT).unwrap();
1583        for pending in [Vec::new(), vec![unknown]] {
1584            let expected: Vec<_> = pending.iter().map(|a| a.signer).collect();
1585            let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
1586                &mut rng,
1587                SUBJECT,
1588                pending,
1589                std::iter::empty(),
1590                &Sequential,
1591            );
1592            let verification = result.unwrap_err();
1593            assert!(verification.verified.is_empty());
1594            assert_eq!(verification.invalid, expected);
1595        }
1596    }
1597
1598    #[cfg(feature = "bls12381")]
1599    #[derive(Debug, Default)]
1600    struct RecordingCalls {
1601        attestations: Mutex<Vec<Vec<Participant>>>,
1602        assemblies: AtomicUsize,
1603        certificates: AtomicUsize,
1604    }
1605
1606    #[cfg(feature = "bls12381")]
1607    #[derive(Clone, Debug)]
1608    struct Recording<S> {
1609        inner: S,
1610        calls: Arc<RecordingCalls>,
1611    }
1612
1613    #[cfg(feature = "bls12381")]
1614    impl<S: Scheme> Recording<S> {
1615        fn inner(attestation: Attestation<Self>) -> Attestation<S> {
1616            Attestation {
1617                signer: attestation.signer,
1618                signature: attestation.signature,
1619            }
1620        }
1621
1622        fn outer(attestation: Attestation<S>) -> Attestation<Self> {
1623            Attestation {
1624                signer: attestation.signer,
1625                signature: attestation.signature,
1626            }
1627        }
1628    }
1629
1630    #[cfg(feature = "bls12381")]
1631    impl<S: Scheme> Verifier for Recording<S> {
1632        type Subject<'a, D: Digest> = S::Subject<'a, D>;
1633        type Faults = S::Faults;
1634        type PublicKey = S::PublicKey;
1635        type Certificate = S::Certificate;
1636
1637        fn verify_certificate<R, D>(
1638            &self,
1639            rng: &mut R,
1640            subject: Self::Subject<'_, D>,
1641            certificate: &Self::Certificate,
1642            strategy: &impl Strategy,
1643        ) -> bool
1644        where
1645            R: CryptoRng,
1646            D: Digest,
1647        {
1648            self.calls.certificates.fetch_add(1, Ordering::Relaxed);
1649            self.inner
1650                .verify_certificate(rng, subject, certificate, strategy)
1651        }
1652
1653        fn is_batchable() -> bool {
1654            S::is_batchable()
1655        }
1656
1657        fn max_certificate_size(participants: u32) -> usize {
1658            S::max_certificate_size(participants)
1659        }
1660
1661        fn certificate_codec_config(&self) -> <Self::Certificate as Read>::Cfg {
1662            self.inner.certificate_codec_config()
1663        }
1664
1665        fn certificate_codec_config_unbounded() -> <Self::Certificate as Read>::Cfg {
1666            S::certificate_codec_config_unbounded()
1667        }
1668    }
1669
1670    #[cfg(feature = "bls12381")]
1671    impl<S: Scheme> Scheme for Recording<S> {
1672        type Signature = S::Signature;
1673
1674        fn me(&self) -> Option<Membership> {
1675            self.inner.me()
1676        }
1677
1678        fn participants(&self) -> &Set<Self::PublicKey> {
1679            self.inner.participants()
1680        }
1681
1682        fn sign<D: Digest>(&self, subject: Self::Subject<'_, D>) -> Option<Attestation<Self>> {
1683            self.inner.sign(subject).map(Self::outer)
1684        }
1685
1686        fn verify_attestation<R, D>(
1687            &self,
1688            rng: &mut R,
1689            subject: Self::Subject<'_, D>,
1690            attestation: &Attestation<Self>,
1691            strategy: &impl Strategy,
1692        ) -> bool
1693        where
1694            R: CryptoRng,
1695            D: Digest,
1696        {
1697            self.calls
1698                .attestations
1699                .lock()
1700                .push(vec![attestation.signer]);
1701            self.inner
1702                .verify_attestation(rng, subject, &Self::inner(attestation.clone()), strategy)
1703        }
1704
1705        fn verify_attestations<R, D, I>(
1706            &self,
1707            rng: &mut R,
1708            subject: Self::Subject<'_, D>,
1709            attestations: I,
1710            strategy: &impl Strategy,
1711        ) -> Verification<Self>
1712        where
1713            R: CryptoRng,
1714            D: Digest,
1715            I: IntoIterator<Item = Attestation<Self>>,
1716            I::IntoIter: Send,
1717        {
1718            let attestations: Vec<_> = attestations.into_iter().collect();
1719            self.calls
1720                .attestations
1721                .lock()
1722                .push(attestations.iter().map(|a| a.signer).collect());
1723            let result = self.inner.verify_attestations(
1724                rng,
1725                subject,
1726                attestations.into_iter().map(Self::inner),
1727                strategy,
1728            );
1729            Verification::new(
1730                result.verified.into_iter().map(Self::outer).collect(),
1731                result.invalid,
1732            )
1733        }
1734
1735        fn optimistic_assemble<'a, R, D, I, J>(
1736            &self,
1737            rng: &mut R,
1738            subject: Self::Subject<'_, D>,
1739            pending: I,
1740            verified: J,
1741            strategy: &impl Strategy,
1742        ) -> Result<Self::Certificate, Verification<Self>>
1743        where
1744            R: CryptoRng,
1745            D: Digest,
1746            I: IntoIterator<Item = Attestation<Self>>,
1747            I::IntoIter: ExactSizeIterator + Send,
1748            J: IntoIterator<Item = &'a Attestation<Self>>,
1749            J::IntoIter: Send,
1750        {
1751            crate::certificate::optimistic_assemble::<Self, _, D, _, _>(
1752                self, rng, subject, pending, verified, strategy,
1753            )
1754        }
1755
1756        fn assemble<I>(
1757            &self,
1758            attestations: NonEmpty<I>,
1759            strategy: &impl Strategy,
1760        ) -> Result<Self::Certificate, AssemblyError>
1761        where
1762            I: Iterator<Item = Attestation<Self>> + Send,
1763        {
1764            self.calls.assemblies.fetch_add(1, Ordering::Relaxed);
1765            self.inner.assemble(
1766                non_empty![@attestations.into_iter().map(Self::inner)],
1767                strategy,
1768            )
1769        }
1770
1771        fn is_attributable() -> bool {
1772            S::is_attributable()
1773        }
1774    }
1775
1776    #[cfg(feature = "bls12381")]
1777    fn expected_halves(len: usize) -> Vec<Vec<Participant>> {
1778        let middle = len.div_ceil(2);
1779        let first = (0..middle).map(Participant::from_usize).collect();
1780        if middle == len {
1781            vec![first]
1782        } else {
1783            vec![first, (middle..len).map(Participant::from_usize).collect()]
1784        }
1785    }
1786
1787    #[cfg(feature = "bls12381")]
1788    #[test]
1789    fn test_rejected_certificate_verifies_pending_halves_directly() {
1790        for n in [1, 5, 7] {
1791            let mut rng = test_rng();
1792            let schemes = threshold_signers::<MinPk>(&mut rng, n);
1793            let calls = Arc::new(RecordingCalls::default());
1794            let verifier = Recording {
1795                inner: schemes[0].clone(),
1796                calls: Arc::clone(&calls),
1797            };
1798            let quorum = N3f1::quorum(n) as usize;
1799            let pending: Vec<_> = schemes
1800                .iter()
1801                .take(quorum)
1802                .map(|s| Recording::outer(s.sign::<Sha256Digest>(OTHER_SUBJECT).unwrap()))
1803                .collect();
1804            let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
1805                &mut rng,
1806                SUBJECT,
1807                pending,
1808                std::iter::empty(),
1809                &Sequential,
1810            );
1811            let verification = result.unwrap_err();
1812            assert!(verification.verified.is_empty());
1813            assert_eq!(
1814                verification.invalid,
1815                (0..quorum).map(Participant::from_usize).collect::<Vec<_>>()
1816            );
1817            assert_eq!(*calls.attestations.lock(), expected_halves(quorum));
1818        }
1819    }
1820
1821    #[cfg(feature = "bls12381")]
1822    #[test]
1823    fn test_malformed_assembly_falls_back_to_pending_halves() {
1824        let mut rng = test_rng();
1825        let schemes = threshold_signers::<MinPk>(&mut rng, 5);
1826        let quorum = N3f1::quorum(schemes.len()) as usize;
1827        for count in [quorum, schemes.len()] {
1828            let calls = Arc::new(RecordingCalls::default());
1829            let verifier = Recording {
1830                inner: schemes[0].clone(),
1831                calls: Arc::clone(&calls),
1832            };
1833            let mut pending: Vec<_> = schemes
1834                .iter()
1835                .take(count)
1836                .map(|s| Recording::outer(s.sign::<Sha256Digest>(SUBJECT).unwrap()))
1837                .collect();
1838            let mut malformed = Bytes::from_static(&[0]);
1839            pending[0].signature = Lazy::deferred(&mut malformed, ());
1840            let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
1841                &mut rng,
1842                SUBJECT,
1843                pending,
1844                std::iter::empty(),
1845                &Sequential,
1846            );
1847            let verification = result.unwrap_err();
1848            assert_eq!(verification.verified.len(), count - 1);
1849            assert_eq!(verification.invalid, vec![Participant::new(0)]);
1850            assert_eq!(*calls.attestations.lock(), expected_halves(count));
1851            if count > quorum {
1852                let certificate = verifier
1853                    .assemble(non_empty![@verification.verified], &Sequential)
1854                    .unwrap();
1855                assert!(verifier.verify_certificate::<_, Sha256Digest>(
1856                    &mut rng,
1857                    SUBJECT,
1858                    &certificate,
1859                    &Sequential,
1860                ));
1861            }
1862        }
1863    }
1864
1865    #[cfg(feature = "bls12381")]
1866    #[test]
1867    fn test_optimistic_assemble_retains_verified_inputs() {
1868        let mut rng = test_rng();
1869        let schemes = threshold_signers::<MinPk>(&mut rng, 5);
1870        let mut prior: Vec<_> = schemes[..2]
1871            .iter()
1872            .map(|s| Recording::outer(s.sign::<Sha256Digest>(SUBJECT).unwrap()))
1873            .collect();
1874        let accepted = Recording::outer(schemes[2].sign::<Sha256Digest>(SUBJECT).unwrap());
1875        let rejected = Recording::outer(schemes[3].sign::<Sha256Digest>(OTHER_SUBJECT).unwrap());
1876        let invalid = rejected.signer;
1877        let calls = Arc::new(RecordingCalls::default());
1878        let verifier = Recording {
1879            inner: schemes[0].clone(),
1880            calls: Arc::clone(&calls),
1881        };
1882        let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
1883            &mut rng,
1884            SUBJECT,
1885            vec![accepted.clone(), rejected],
1886            &prior,
1887            &Sequential,
1888        );
1889        let verification = result.unwrap_err();
1890        assert_eq!(verification.verified, vec![accepted.clone()]);
1891        assert_eq!(verification.invalid, vec![invalid]);
1892        assert_eq!(
1893            *calls.attestations.lock(),
1894            vec![vec![accepted.signer], vec![invalid]]
1895        );
1896
1897        prior.extend(verification.verified);
1898        calls.attestations.lock().clear();
1899        let replacement = Recording::outer(schemes[4].sign::<Sha256Digest>(SUBJECT).unwrap());
1900        let signer = replacement.signer;
1901        let verification = verifier.verify_attestations::<_, Sha256Digest, _>(
1902            &mut rng,
1903            SUBJECT,
1904            vec![replacement.clone()],
1905            &Sequential,
1906        );
1907        assert_eq!(verification.verified, vec![replacement]);
1908        assert!(verification.invalid.is_empty());
1909        assert_eq!(*calls.attestations.lock(), vec![vec![signer]]);
1910        prior.extend(verification.verified);
1911        let certificate = verifier.assemble(non_empty![@prior], &Sequential).unwrap();
1912        assert!(verifier.verify_certificate::<_, Sha256Digest>(
1913            &mut rng,
1914            SUBJECT,
1915            &certificate,
1916            &Sequential,
1917        ));
1918    }
1919
1920    #[cfg(feature = "bls12381")]
1921    #[test]
1922    fn test_optimistic_assemble_with_verified_votes() {
1923        let mut rng = test_rng();
1924        let schemes = threshold_signers::<MinPk>(&mut rng, 5);
1925        let quorum = N3f1::quorum(schemes.len()) as usize;
1926        let attestations: Vec<_> = schemes[..quorum]
1927            .iter()
1928            .map(|scheme| scheme.sign::<Sha256Digest>(SUBJECT).unwrap())
1929            .collect();
1930
1931        for pending_len in [0, 1, 2] {
1932            // Retained votes are authenticated for this configuration and subject.
1933            // The remaining votes cover empty, singleton, and evenly split inputs.
1934            let (prior, pending) = attestations.split_at(quorum - pending_len);
1935            let prior = schemes[0].verify_attestations::<_, Sha256Digest, _>(
1936                &mut rng,
1937                SUBJECT,
1938                prior.iter().cloned(),
1939                &Sequential,
1940            );
1941            assert!(prior.invalid.is_empty());
1942            let prior: Vec<_> = prior.verified.into_iter().map(Recording::outer).collect();
1943            let calls = Arc::new(RecordingCalls::default());
1944            let verifier = Recording {
1945                inner: schemes[0].clone(),
1946                calls: Arc::clone(&calls),
1947            };
1948            let certificate = verifier
1949                .optimistic_assemble::<_, Sha256Digest, _, _>(
1950                    &mut rng,
1951                    SUBJECT,
1952                    pending.iter().cloned().map(Recording::outer),
1953                    &prior,
1954                    &Sequential,
1955                )
1956                .ok()
1957                .expect("verified quorum must certify");
1958
1959            // Pending votes require certificate authentication, even when most
1960            // inputs are already verified. A verified-only quorum needs assembly alone.
1961            assert_eq!(calls.assemblies.load(Ordering::Relaxed), 1);
1962            assert_eq!(
1963                calls.certificates.load(Ordering::Relaxed),
1964                usize::from(pending_len != 0)
1965            );
1966            assert!(calls.attestations.lock().is_empty());
1967            assert!(schemes[0].verify_certificate::<_, Sha256Digest>(
1968                &mut rng,
1969                SUBJECT,
1970                &certificate,
1971                &Sequential,
1972            ));
1973        }
1974    }
1975
1976    #[cfg(feature = "bls12381")]
1977    #[test]
1978    fn test_optimistic_assemble_retries_with_replacements() {
1979        let mut rng = test_rng();
1980        let schemes = threshold_signers::<MinPk>(&mut rng, 10);
1981        let quorum = N3f1::quorum(schemes.len()) as usize;
1982
1983        for failures in [1, 2] {
1984            let calls = Arc::new(RecordingCalls::default());
1985            let verifier = Recording {
1986                inner: schemes[0].clone(),
1987                calls: Arc::clone(&calls),
1988            };
1989
1990            // Ten participants permit three faulty signers across these attempts.
1991            // A failed initial quorum retains the local vote and its valid peer votes.
1992            let own = schemes[0].verify_attestations::<_, Sha256Digest, _>(
1993                &mut rng,
1994                SUBJECT,
1995                [schemes[0].sign::<Sha256Digest>(SUBJECT).unwrap()],
1996                &Sequential,
1997            );
1998            assert!(own.invalid.is_empty());
1999            let mut prior: Vec<_> = own.verified.into_iter().map(Recording::outer).collect();
2000            let pending: Vec<_> = schemes[1..quorum]
2001                .iter()
2002                .enumerate()
2003                .map(|(index, scheme)| {
2004                    let subject = if index + 1 >= quorum - failures {
2005                        OTHER_SUBJECT
2006                    } else {
2007                        SUBJECT
2008                    };
2009                    Recording::outer(scheme.sign::<Sha256Digest>(subject).unwrap())
2010                })
2011                .collect();
2012            let result = verifier
2013                .optimistic_assemble::<_, Sha256Digest, _, _>(
2014                    &mut rng,
2015                    SUBJECT,
2016                    pending,
2017                    &prior,
2018                    &Sequential,
2019                )
2020                .unwrap_err();
2021            assert_eq!(result.invalid.len(), failures);
2022            prior.extend(result.verified);
2023            assert_eq!(prior.len(), quorum - failures);
2024
2025            // A new faulty signer is mixed with any honest replacements. The
2026            // candidate fails authentication, so fallback identifies the bad vote.
2027            let pending: Vec<_> = schemes[quorum..quorum + failures]
2028                .iter()
2029                .enumerate()
2030                .map(|(index, scheme)| {
2031                    let subject = if index + 1 == failures {
2032                        OTHER_SUBJECT
2033                    } else {
2034                        SUBJECT
2035                    };
2036                    Recording::outer(scheme.sign::<Sha256Digest>(subject).unwrap())
2037                })
2038                .collect();
2039            let signers: Vec<_> = pending.iter().map(|vote| vote.signer).collect();
2040            let invalid = *signers.last().unwrap();
2041            calls.attestations.lock().clear();
2042            calls.assemblies.store(0, Ordering::Relaxed);
2043            calls.certificates.store(0, Ordering::Relaxed);
2044            let result = verifier
2045                .optimistic_assemble::<_, Sha256Digest, _, _>(
2046                    &mut rng,
2047                    SUBJECT,
2048                    pending,
2049                    &prior,
2050                    &Sequential,
2051                )
2052                .unwrap_err();
2053            assert_eq!(result.invalid, vec![invalid]);
2054            assert_eq!(result.verified.len(), failures - 1);
2055            assert_eq!(calls.assemblies.load(Ordering::Relaxed), 1);
2056            assert_eq!(calls.certificates.load(Ordering::Relaxed), 1);
2057            assert_eq!(
2058                *calls.attestations.lock(),
2059                signers
2060                    .into_iter()
2061                    .map(|signer| vec![signer])
2062                    .collect::<Vec<_>>()
2063            );
2064            prior.extend(result.verified);
2065            assert_eq!(prior.len(), quorum - 1);
2066
2067            // The last honest replacement completes the same authenticated subject.
2068            let replacement = Recording::outer(
2069                schemes[quorum + failures]
2070                    .sign::<Sha256Digest>(SUBJECT)
2071                    .unwrap(),
2072            );
2073            calls.attestations.lock().clear();
2074            calls.assemblies.store(0, Ordering::Relaxed);
2075            calls.certificates.store(0, Ordering::Relaxed);
2076            let certificate = verifier
2077                .optimistic_assemble::<_, Sha256Digest, _, _>(
2078                    &mut rng,
2079                    SUBJECT,
2080                    [replacement],
2081                    &prior,
2082                    &Sequential,
2083                )
2084                .ok()
2085                .expect("valid replacement must certify");
2086            assert_eq!(calls.assemblies.load(Ordering::Relaxed), 1);
2087            assert_eq!(calls.certificates.load(Ordering::Relaxed), 1);
2088            assert!(calls.attestations.lock().is_empty());
2089            assert!(schemes[0].verify_certificate::<_, Sha256Digest>(
2090                &mut rng,
2091                SUBJECT,
2092                &certificate,
2093                &Sequential,
2094            ));
2095            assert!(!schemes[0].verify_certificate::<_, Sha256Digest>(
2096                &mut rng,
2097                OTHER_SUBJECT,
2098                &certificate,
2099                &Sequential,
2100            ));
2101        }
2102    }
2103
2104    #[cfg(feature = "bls12381")]
2105    #[test]
2106    fn test_optimistic_assemble_below_quorum() {
2107        let mut rng = test_rng();
2108        let schemes = threshold_signers::<MinPk>(&mut rng, 5);
2109        let pending: Vec<_> = schemes[..2]
2110            .iter()
2111            .map(|s| Recording::outer(s.sign::<Sha256Digest>(SUBJECT).unwrap()))
2112            .collect();
2113        let expected = pending.clone();
2114        let calls = Arc::new(RecordingCalls::default());
2115        let verifier = Recording {
2116            inner: schemes[0].clone(),
2117            calls: Arc::clone(&calls),
2118        };
2119        let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
2120            &mut rng,
2121            SUBJECT,
2122            pending,
2123            std::iter::empty(),
2124            &Sequential,
2125        );
2126        let verification = result.unwrap_err();
2127        assert_eq!(verification.verified, expected);
2128        assert!(verification.invalid.is_empty());
2129        assert_eq!(*calls.attestations.lock(), expected_halves(expected.len()));
2130    }
2131
2132    #[cfg(feature = "bls12381")]
2133    #[test]
2134    fn test_optimistic_assemble_unknown_pending_signer() {
2135        let mut rng = test_rng();
2136        let schemes = threshold_signers::<MinPk>(&mut rng, 5);
2137        let quorum = N3f1::quorum(schemes.len()) as usize;
2138        let mut pending: Vec<_> = schemes
2139            .iter()
2140            .take(quorum)
2141            .map(|s| Recording::outer(s.sign::<Sha256Digest>(SUBJECT).unwrap()))
2142            .collect();
2143        pending[0].signer = Participant::new(999);
2144        let calls = Arc::new(RecordingCalls::default());
2145        let verifier = Recording {
2146            inner: schemes[0].clone(),
2147            calls: Arc::clone(&calls),
2148        };
2149        let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
2150            &mut rng,
2151            SUBJECT,
2152            pending,
2153            std::iter::empty(),
2154            &Sequential,
2155        );
2156        let verification = result.unwrap_err();
2157        assert_eq!(verification.verified.len(), quorum - 1);
2158        assert_eq!(verification.invalid, vec![Participant::new(999)]);
2159        assert_eq!(
2160            *calls.attestations.lock(),
2161            vec![
2162                vec![Participant::new(999), Participant::new(1)],
2163                vec![Participant::new(2), Participant::new(3)],
2164            ]
2165        );
2166    }
2167
2168    #[cfg(feature = "arbitrary")]
2169    mod conformance {
2170        use super::{ed25519_fixture::Scheme, *};
2171        use commonware_codec::conformance::CodecConformance;
2172
2173        commonware_conformance::conformance_tests! {
2174            CodecConformance<Signers>,
2175            CodecConformance<Attestation<Scheme>>,
2176        }
2177    }
2178}