Skip to main content

commonware_cryptography/bls12381/
dkg.rs

1//! Distributed Key Generation (DKG) and Resharing protocol for the BLS12-381 curve.
2//!
3//! This module implements an interactive Distributed Key Generation (DKG) and Resharing protocol
4//! for the BLS12-381 curve. Unlike other constructions, this construction does not require encrypted
5//! shares to be publicly broadcast to complete a DKG/Reshare. Shares, instead, are sent directly
6//! between dealers and players over an encrypted channel (which can be instantiated
7//! with [commonware-p2p](https://docs.rs/commonware-p2p)).
8//!
9//! The DKG is based on the "Joint-Feldman" construction from "Secure Distributed Key
10//! Generation for Discrete-Log Based Cryptosystems" (GJKR99) and Resharing is based
11//! on the construction described in "Redistributing secret shares to new access structures
12//! and its applications" (Desmedt97).
13//!
14//! # Overview
15//!
16//! The protocol involves _dealers_ and _players_. The dealers are trying to jointly create a shared
17//! key, and then distribute it among the players. The dealers may have pre-existing shares of a key
18//! from a previous round, in which case the goal is to re-distribute that key among the players,
19//! with fresh randomness.
20//!
21//! The protocol is also designed such that an external observer can figure out whether the protocol
22//! succeeded or failed, and learn of the public outputs of the protocol. This includes
23//! the participants in the protocol, and the public polynomial committing to the key
24//! and its sharing.
25//!
26//! # Usage
27//!
28//! ## Core Types
29//!
30//! * [`Info`]: Configuration for a DKG/Reshare round, containing the dealers, players, and optional previous output
31//! * [`Output`]: The public result of a successful DKG round, containing the public polynomial and player list
32//! * [`Share`]: A player's final private share of the distributed key (from `primitives::group`)
33//! * [`Dealer`]: State machine for a dealer participating in the protocol
34//! * [`Player`]: State machine for a player receiving dealings
35//! * [`SignedDealerLog`]: A dealer's signed transcript of their interactions with players
36//!
37//! ## Message Types
38//!
39//! * [`DealerPubMsg`]: Public commitment polynomial sent from dealer to all players
40//! * [`DealerPrivMsg`]: Private dealing sent from dealer to a specific player
41//! * [`PlayerAck`]: Acknowledgement sent from player back to dealer
42//! * [`DealerLog`]: Complete log of a dealer's interactions (commitments and acks/reveals)
43//!
44//! ## Protocol Flow
45//!
46//! ### Step 1: Initialize Round
47//!
48//! Create a [`Info`] using [`Info::new`] with:
49//! - Round number (should increment sequentially, including for failed rounds)
50//! - Optional previous [`Output`] (for resharing)
51//! - List of dealers (must be >= quorum of previous round if resharing)
52//! - List of players who will receive dealings
53//!
54//! ### Step 2: Dealer Phase
55//!
56//! Each dealer calls [`Dealer::start`] which returns:
57//! - A [`Dealer`] instance for tracking state
58//! - A [`DealerPubMsg`] containing the polynomial commitment to broadcast
59//! - A vector of `(player_id, DealerPrivMsg)` pairs to send privately
60//!
61//! The [`DealerPubMsg`] contains a public polynomial commitment of degree `2f` where `f = max_faults(n)`.
62//! Each [`DealerPrivMsg`] contains a scalar evaluation of the dealer's private polynomial at the player's index.
63//!
64//! ### Step 3: Player Verification
65//!
66//! Each player creates a [`Player`] instance via [`Player::new`], then for each dealer message:
67//! - Call [`Player::dealer_message`] with the [`DealerPubMsg`] and [`DealerPrivMsg`]
68//! - If valid, this returns a [`PlayerAck`] containing a signature over `(dealer, commitment)`
69//! - The player verifies that the private dealing matches the public commitment evaluation
70//!
71//! ### Step 4: Dealer Collection
72//!
73//! Each dealer:
74//! - Calls [`Dealer::receive_player_ack`] for each acknowledgement received
75//! - After timeout, calls [`Dealer::finalize`] to produce a [`SignedDealerLog`]
76//! - The log contains the commitment and either acks or reveals for each player
77//!
78//! ### Step 5: Finalization
79//!
80//! With collected [`SignedDealerLog`]s:
81//! - Call [`SignedDealerLog::check`] to verify and extract [`DealerLog`]s
82//! - Players call [`Player::finalize`] with all logs to compute their [`Share`] and [`Output`]
83//! - Observers call [`observe`] with all logs to compute just the [`Output`]
84//!
85//! The [`Output`] contains:
86//! - The final public polynomial (sum of dealer polynomials for DKG, interpolation for reshare),
87//! - The list of dealers who distributed dealings,
88//! - The list of players who received shares,
89//! - The set of players whose shares may have been revealed,
90//! - A digest of the round's [`Info`] (including the counter, and the list of dealers and players).
91//!
92//! ## Trusted Dealing Functions
93//!
94//! As a convenience (for tests, etc.), this module also provides functions for
95//! generating shares using a trusted dealer.
96//!
97//! - [`deal`]: given a list of players, generates an [`Output`] like the DKG would,
98//! - [`deal_anonymous`]: a lower-level version that produces a polynomial directly,
99//!   and doesn't require public keys for the players.
100//!
101//! ## State
102//!
103//! The structs in this module are stateful and they assume that they exist from the
104//! start of the DKG to the end of the DKG.
105//!
106//! During restart, state should be restored by replaying all messages that
107//! dealers and players previously processed. For the dealer, it's important to use a
108//! seeded form of randomness, so that way the same messages can be generated on a second run.
109//! For the player, using [`Player::resume`] is more robust than just [`Player::new`], because it
110//! checks the integrity of the replayed messages against the publicly committed transcript (so far).
111//! This can detect some recoverable operator errors, like storage misconfiguration (where a player has publicly
112//! acknowledged a private message but has no record of it in storage).
113//!
114//! # Caveats
115//!
116//! ## Share Reveals
117//!
118//! In order to prevent malicious dealers from withholding shares from players, we
119//! require the dealers reveal the shares for which they did not receive acks.
120//!
121//! Under synchrony (as discussed below), this will only happen if either:
122//! - the dealer is malicious, not sending a share, but honestly revealing,
123//! - or, the player is malicious, not sending an ack when they should.
124//!
125//! ### Up to `f` Reveals Under Synchrony
126//!
127//! Under synchrony (where `t` is the maximum amount of time it takes for a message to be sent between any two participants),
128//! this construction will not result in more than `f` reveals from honest dealers, and none of those reveals are for honest players
129//! (`2f + 1` commitments with at most `f` players are Byzantine).
130//!
131//! To see how this is true, first consider that in any successful round there must exist `2f + 1` commitments each with at most `f`
132//! reveals. This implies that all players must have acknowledged or have access to a reveal for each of the `2f + 1` selected commitments
133//! (allowing them to derive their share). Next, consider that when the network is synchronous that all `2f + 1` honest players send
134//! acknowledgements to honest dealers before `2t`. Because `2f + 1` commitments must be chosen, at least `f + 1` commitments
135//! must be from honest dealers (where no honest player dealing is revealed...recall, a Byzantine dealer can opt to reveal any
136//! player's dealing even if they sent an acknowledgement).
137//!
138//! Even if the remaining `f` commitments are from Byzantine dealers, there will not be enough dealings to recover the derived share
139//! of any honest player (at most `f` of `2f + 1` points for a linear combination publicly revealed). Given all `2f + 1`
140//! honest players have access to their shares and it is not possible for a Byzantine player to derive any honest player's share, this claim holds.
141//!
142//! ### Up to `2f` Reveals Under Asynchrony
143//!
144//! If the network is asynchronous, Byzantine players may obtain up to `2f` revealed shares (`f` from Byzantine players
145//! and `f` from honest players).
146//!
147//! To see how this could be, consider a network where `f` honest participants are in one partition and (`f + 1` honest and
148//! `f` Byzantine participants) are in another. All `f` Byzantine players acknowledge dealings from the `f + 1` honest dealers.
149//! Participants in the second partition will complete a round and all the reveals will belong to the same set of `f`
150//! honest players (that are in the first partition). A colluding Byzantine adversary will then have access to their acknowledged `f`
151//! shares and the revealed `f` shares. If the Byzantine adversary reveals all of their (still private) shares at this time, each of the
152//! `f + 1` honest players that were in the second partition will be able to derive the shared secret without collusion (using their private share
153//! and the `2f` revealed shares). **It will not be possible for any external observer (or a Byzantine adversary), however, to recover the shared secret.**
154//!
155//! While not entirely revealed, a secret with more than `f` revealed shares may no longer be safe for some applications (like when used to
156//! form threshold certificates for consensus). Consider an equivocating leader (one of the `f` Byzantine players) that sends one block `B_1` to `f`
157//! honest players and another block `B_2` to `f + 1` other honest players. Normally, it would only be possible to create one quorum of `2f + 1` (for `B_2`),
158//! however, with `h` other shares revealed another quorum of `2f + h` can be formed for `B_1`.
159//!
160//! #### Future Work: Dropping the Synchrony Assumption for `f` Bounded Reveals?
161//!
162//! It is possible to design a DKG/Resharing scheme that maintains a shared secret where at least `f + 1` honest players
163//! must participate to recover the shared secret that doesn't require a synchrony assumption (`2f + 1` threshold
164//! where at most `f` players are Byzantine). However, known constructions that satisfy this requirement require both
165//! broadcasting encrypted dealings publicly and employing Zero-Knowledge Proofs (ZKPs) to attest that encrypted dealings
166//! were generated correctly ([Groth21](https://eprint.iacr.org/2021/339), [Kate23](https://eprint.iacr.org/2023/451)).
167//!
168//! As of January 2025, these constructions are still considered novel (2-3 years in production), require stronger
169//! cryptographic assumptions, don't scale to hundreds of participants (unless dealers have powerful hardware), and provide
170//! observers the opportunity to brute force decrypt shares (even if honest players are online).
171//!
172//! ## Handling Complaints
173//!
174//! This crate does not provide an integrated mechanism for tracking complaints from players (of malicious dealers). However, it is
175//! possible to implement your own mechanism and to manually disqualify dealers from a given round in the arbiter. This decision was made
176//! because the mechanism for communicating commitments/shares/acknowledgements is highly dependent on the context in which this
177//! construction is used.
178//!
179//! In practice:
180//! - [`Player::dealer_message`] returns `None` for invalid messages (implicit complaint)
181//! - [`Dealer::receive_player_ack`] validates acknowledgements
182//! - Other custom mechanisms can exclude dealers before calling [`observe`] or [`Player::finalize`],
183//!   to enforce other rules for "misbehavior" beyond what the DKG does already.
184//!
185//! ## Non-Uniform Distribution
186//!
187//! The Joint-Feldman DKG protocol does not guarantee a uniformly random secret key is generated. An adversary
188//! can introduce `O(lg N)` bits of bias into the key with `O(poly(N))` amount of computation. For uses
189//! like signing, threshold encryption, where the security of the scheme reduces to that of
190//! the underlying assumption that cryptographic constructions using the curve are secure (i.e.
191//! that the Discrete Logarithm Problem, or stronger variants, are hard), then this caveat does
192//! not affect the security of the scheme. This must be taken into account when integrating this
193//! component into more esoteric schemes.
194//!
195//! This choice was explicitly made, because the best known protocols guaranteeing a uniform output
196//! require an extra round of broadcast ([GJKR02](https://www.researchgate.net/publication/2558744_Revisiting_the_Distributed_Key_Generation_for_Discrete-Log_Based_Cryptosystems),
197//! [BK25](https://eprint.iacr.org/2025/819)).
198//!
199//! # Example
200//!
201//! ```
202//! use commonware_cryptography::bls12381::{
203//!     dkg::{Dealer, Info, Logs, Player, SignedDealerLog, observe},
204//!     primitives::{variant::MinSig, sharing::Mode},
205//! };
206//! use commonware_cryptography::{ed25519, Signer};
207//! use commonware_math::algebra::Random;
208//! use commonware_utils::{ordered::Set, TryCollect, N3f1};
209//! use std::collections::BTreeMap;
210//! use rand::SeedableRng;
211//! use rand_chacha::ChaCha8Rng;
212//!
213//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
214//! let mut rng = ChaCha8Rng::seed_from_u64(42);
215//!
216//! // Generate 4 Ed25519 private keys for participants
217//! let mut private_keys = Vec::new();
218//! for _ in 0..4 {
219//!     let private_key = ed25519::PrivateKey::random(&mut rng);
220//!     private_keys.push(private_key);
221//! }
222//!
223//! // All 4 participants are both dealers and players in initial DKG
224//! let dealer_set: Set<ed25519::PublicKey> = private_keys.iter()
225//!     .map(|k| k.public_key())
226//!     .try_collect()?;
227//! let player_set = dealer_set.clone();
228//!
229//! // Step 1: Create round info for initial DKG
230//! let info = Info::<MinSig, ed25519::PublicKey>::new::<N3f1>(
231//!     b"application-namespace",
232//!     0,                        // round number
233//!     None,                     // no previous output (initial DKG)
234//!     Mode::default(),   // sharing mode
235//!     dealer_set.clone(),       // dealers
236//!     player_set.clone(),       // players
237//! )?;
238//!
239//! // Step 2: Initialize players
240//! let mut players = BTreeMap::new();
241//! for private_key in &private_keys {
242//!     let player = Player::<MinSig, ed25519::PrivateKey>::new(
243//!         info.clone(),
244//!         private_key.clone(),
245//!     )?;
246//!     players.insert(private_key.public_key(), player);
247//! }
248//!
249//! // Step 3: Run dealer protocol for each participant
250//! let mut logs = Logs::<MinSig, ed25519::PublicKey, N3f1>::new(info.clone());
251//! for dealer_priv in &private_keys {
252//!     // Each dealer generates messages for all players
253//!     let (mut dealer, pub_msg, priv_msgs) = Dealer::start::<N3f1>(
254//!         &mut rng,
255//!         info.clone(),
256//!         dealer_priv.clone(),
257//!         None,  // no previous share for initial DKG
258//!     )?;
259//!
260//!     // Distribute messages to players and collect acknowledgements
261//!     for (player_pk, priv_msg) in priv_msgs {
262//!         if let Some(player) = players.get_mut(&player_pk) {
263//!             if let Some(ack) = player.dealer_message::<N3f1>(
264//!                 dealer_priv.public_key(),
265//!                 pub_msg.clone(),
266//!                 priv_msg,
267//!             ) {
268//!                 dealer.receive_player_ack(player_pk, ack)?;
269//!             }
270//!         }
271//!     }
272//!
273//!     // Finalize dealer and verify log
274//!     let signed_log = dealer.finalize::<N3f1>();
275//!     if let Some((dealer_pk, log)) = signed_log.check(&info) {
276//!         logs.record(dealer_pk, log);
277//!     }
278//! }
279//!
280//! // Step 4: Players finalize to get their shares
281//! let mut player_shares = BTreeMap::new();
282//! for (player_pk, player) in players {
283//!     let (output, share) = player.finalize::<N3f1, ed25519::Batch>(
284//!       &mut rng,
285//!       logs.clone(),
286//!       &commonware_parallel::Sequential,
287//!     )?;
288//!     println!("Player {:?} got share at index {}", player_pk, share.index);
289//!     player_shares.insert(player_pk, share);
290//! }
291//!
292//! // Step 5: Observer can also compute the public output
293//! let observer_output = observe::<MinSig, ed25519::PublicKey, N3f1, ed25519::Batch>(
294//!     &mut rng,
295//!     logs,
296//!     &commonware_parallel::Sequential,
297//! )?;
298//! println!("DKG completed with threshold {}", observer_output.quorum::<N3f1>());
299//! # Ok(())
300//! # }
301//! ```
302//!
303//! For a complete example with resharing, see [commonware-reshare](https://docs.rs/commonware-reshare).
304
305use super::primitives::group::{Private, Share};
306use crate::{
307    bls12381::primitives::{
308        group::Scalar,
309        sharing::{Mode, ModeVersion, Sharing},
310        variant::Variant,
311    },
312    transcript::{Summary, Transcript},
313    BatchVerifier, PublicKey, Secret, Signer,
314};
315use commonware_codec::{Encode, EncodeSize, RangeCfg, Read, ReadExt, Write};
316use commonware_math::{
317    algebra::{Additive, CryptoGroup, Random, Ring as _},
318    poly::{Interpolator, Poly},
319};
320use commonware_parallel::{Sequential, Strategy};
321#[cfg(feature = "arbitrary")]
322use commonware_utils::N3f1;
323use commonware_utils::{
324    ordered::{Map, Quorum, Set},
325    Faults, Participant, TryCollect, NZU32,
326};
327use core::num::NonZeroU32;
328use rand_core::CryptoRngCore;
329use std::{borrow::Cow, collections::BTreeMap, marker::PhantomData};
330use thiserror::Error;
331
332const NAMESPACE: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG";
333const SIG_ACK: &[u8] = b"ack";
334const SIG_LOG: &[u8] = b"log";
335const NOISE_PRE_VERIFY: &[u8] = b"pre_verify";
336
337/// The error type for the DKG protocol.
338///
339/// The only error which can happen through no fault of your own is
340/// [`Error::DkgFailed`].
341///
342/// [`Error::MissingPlayerDealing`] happens through mistakes or faults when the state
343/// of a player is restored after a crash.
344///
345/// The other errors are due to issues with configuration or misuse.
346#[derive(Debug, Error)]
347pub enum Error {
348    #[error("missing dealer's share from the previous round")]
349    MissingDealerShare,
350    #[error("player is not present in the list of players")]
351    UnknownPlayer,
352    #[error("dealer is not present in the previous list of players")]
353    UnknownDealer(String),
354    #[error("invalid number of dealers: {0}")]
355    NumDealers(usize),
356    #[error("invalid number of players: {0}")]
357    NumPlayers(usize),
358    #[error("dkg failed for some reason")]
359    DkgFailed,
360    #[error("logs are bound to a different dkg round")]
361    MismatchedLogs,
362    /// The player's state is missing a dealing it should have.
363    ///
364    /// This error is emitted when the player is missing dealings that it should
365    /// otherwise have based on the flow of the protocol. This can only happen if
366    /// the code in this module is used in a stateful way, restoring the
367    /// state of the player from saved information. If this state is corrupted
368    /// on disk, or missing, then this error can happen.
369    #[error("missing player's dealing")]
370    MissingPlayerDealing,
371}
372
373/// The output of a successful DKG.
374#[derive(Debug, Clone, PartialEq, Eq)]
375pub struct Output<V: Variant, P> {
376    summary: Summary,
377    public: Sharing<V>,
378    dealers: Set<P>,
379    players: Set<P>,
380    revealed: Set<P>,
381}
382
383impl<V: Variant, P: Ord> Output<V, P> {
384    fn share_commitment(&self, player: &P) -> Option<V::Public> {
385        self.public.partial_public(self.players.index(player)?).ok()
386    }
387
388    /// Return the quorum, i.e. the number of players needed to reconstruct the key.
389    pub fn quorum<M: Faults>(&self) -> u32 {
390        self.players.quorum::<M>()
391    }
392
393    /// Get the public polynomial associated with this output.
394    ///
395    /// This is useful for verifying partial signatures, with [crate::bls12381::primitives::ops::threshold::verify_message].
396    pub const fn public(&self) -> &Sharing<V> {
397        &self.public
398    }
399
400    /// Return the dealers who were selected in this round of the DKG.
401    pub const fn dealers(&self) -> &Set<P> {
402        &self.dealers
403    }
404
405    /// Return the players who participated in this round of the DKG, and should have shares.
406    pub const fn players(&self) -> &Set<P> {
407        &self.players
408    }
409
410    /// Return the set of players whose shares may have been revealed.
411    ///
412    /// These are players who had more than `max_faults` reveals.
413    pub const fn revealed(&self) -> &Set<P> {
414        &self.revealed
415    }
416}
417
418impl<V: Variant, P: PublicKey> EncodeSize for Output<V, P> {
419    fn encode_size(&self) -> usize {
420        self.summary.encode_size()
421            + self.public.encode_size()
422            + self.dealers.encode_size()
423            + self.players.encode_size()
424            + self.revealed.encode_size()
425    }
426}
427
428impl<V: Variant, P: PublicKey> Write for Output<V, P> {
429    fn write(&self, buf: &mut impl bytes::BufMut) {
430        self.summary.write(buf);
431        self.public.write(buf);
432        self.dealers.write(buf);
433        self.players.write(buf);
434        self.revealed.write(buf);
435    }
436}
437
438impl<V: Variant, P: PublicKey> Read for Output<V, P> {
439    type Cfg = (NonZeroU32, ModeVersion);
440
441    fn read_cfg(
442        buf: &mut impl bytes::Buf,
443        (max_participants, max_supported_mode): &Self::Cfg,
444    ) -> Result<Self, commonware_codec::Error> {
445        let max_participants_usize = max_participants.get() as usize;
446        Ok(Self {
447            summary: ReadExt::read(buf)?,
448            public: Read::read_cfg(buf, &(*max_participants, *max_supported_mode))?,
449            dealers: Read::read_cfg(buf, &(RangeCfg::new(1..=max_participants_usize), ()))?, // at least one dealer must be part of a dealing
450            players: Read::read_cfg(buf, &(RangeCfg::new(1..=max_participants_usize), ()))?, // at least one player must be part of a dealing
451            revealed: Read::read_cfg(buf, &(RangeCfg::new(0..=max_participants_usize), ()))?, // there may not be any reveals
452        })
453    }
454}
455
456#[cfg(feature = "arbitrary")]
457impl<P: PublicKey, V: Variant> arbitrary::Arbitrary<'_> for Output<V, P>
458where
459    P: for<'a> arbitrary::Arbitrary<'a> + Ord,
460    V::Public: for<'a> arbitrary::Arbitrary<'a>,
461{
462    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
463        let summary = u.arbitrary()?;
464        let public: Sharing<V> = u.arbitrary()?;
465        let total = public.total().get() as usize;
466
467        let num_dealers = u.int_in_range(1..=total * 2)?;
468        let dealers = Set::try_from(
469            u.arbitrary_iter::<P>()?
470                .take(num_dealers)
471                .collect::<Result<Vec<_>, _>>()?,
472        )
473        .map_err(|_| arbitrary::Error::IncorrectFormat)?;
474
475        let players = Set::try_from(
476            u.arbitrary_iter::<P>()?
477                .take(total)
478                .collect::<Result<Vec<_>, _>>()?,
479        )
480        .map_err(|_| arbitrary::Error::IncorrectFormat)?;
481
482        let max_revealed = N3f1::max_faults(total) as usize;
483        let revealed = Set::from_iter_dedup(
484            players
485                .iter()
486                .filter(|_| u.arbitrary::<bool>().unwrap_or(false))
487                .take(max_revealed)
488                .cloned(),
489        );
490
491        Ok(Self {
492            summary,
493            public,
494            dealers,
495            players,
496            revealed,
497        })
498    }
499}
500
501/// Information about the current round of the DKG.
502///
503/// This is used to bind signatures to the current round, and to provide the
504/// information that dealers, players, and observers need to perform their actions.
505#[derive(Debug, Clone)]
506pub struct Info<V: Variant, P: PublicKey> {
507    summary: Summary,
508    round: u64,
509    previous: Option<Output<V, P>>,
510    mode: Mode,
511    dealers: Set<P>,
512    players: Set<P>,
513}
514
515impl<V: Variant, P: PublicKey> PartialEq for Info<V, P> {
516    fn eq(&self, other: &Self) -> bool {
517        self.summary == other.summary
518    }
519}
520
521impl<V: Variant, P: PublicKey> Info<V, P> {
522    /// Figure out what the dealer share should be.
523    ///
524    /// If there's no previous round, we need a random value, hence `rng`.
525    ///
526    /// However, if there is a previous round, we expect a share, hence `Result`.
527    fn unwrap_or_random_share(
528        &self,
529        mut rng: impl CryptoRngCore,
530        share: Option<Scalar>,
531    ) -> Result<Scalar, Error> {
532        let out = match (self.previous.as_ref(), share) {
533            (None, None) => Scalar::random(&mut rng),
534            (_, Some(x)) => x,
535            (Some(_), None) => return Err(Error::MissingDealerShare),
536        };
537        Ok(out)
538    }
539
540    const fn num_players(&self) -> NonZeroU32 {
541        // Will not panic because we check that the number of players is non-empty in `new`
542        NZU32!(self.players.len() as u32)
543    }
544
545    fn degree<M: Faults>(&self) -> u32 {
546        self.players.quorum::<M>().saturating_sub(1)
547    }
548
549    fn required_commitments<M: Faults>(&self) -> u32 {
550        let dealer_quorum = self.dealers.quorum::<M>();
551        let prev_quorum = self
552            .previous
553            .as_ref()
554            .map(Output::quorum::<M>)
555            .unwrap_or(u32::MIN);
556        dealer_quorum.max(prev_quorum)
557    }
558
559    fn max_reveals<M: Faults>(&self) -> u32 {
560        self.players.max_faults::<M>()
561    }
562
563    fn player_index(&self, player: &P) -> Result<Participant, Error> {
564        self.players.index(player).ok_or(Error::UnknownPlayer)
565    }
566
567    fn dealer_index(&self, dealer: &P) -> Result<Participant, Error> {
568        self.dealers
569            .index(dealer)
570            .ok_or(Error::UnknownDealer(format!("{dealer:?}")))
571    }
572
573    fn player_scalar(&self, player: &P) -> Result<Scalar, Error> {
574        Ok(self
575            .mode
576            .scalar(self.num_players(), self.player_index(player)?)
577            .expect("player index should be < num_players"))
578    }
579
580    #[must_use]
581    fn check_dealer_pub_msg<M: Faults>(&self, dealer: &P, pub_msg: &DealerPubMsg<V>) -> bool {
582        if self.degree::<M>() != pub_msg.commitment.degree_exact() {
583            return false;
584        }
585        if let Some(previous) = self.previous.as_ref() {
586            let Some(share_commitment) = previous.share_commitment(dealer) else {
587                return false;
588            };
589            if *pub_msg.commitment.constant() != share_commitment {
590                return false;
591            }
592        }
593        true
594    }
595
596    #[must_use]
597    fn check_dealer_priv_msg(
598        &self,
599        player: &P,
600        pub_msg: &DealerPubMsg<V>,
601        priv_msg: &DealerPrivMsg,
602    ) -> bool {
603        let Ok(scalar) = self.player_scalar(player) else {
604            return false;
605        };
606        let expected = pub_msg.commitment.eval_msm(&scalar, &Sequential);
607        priv_msg
608            .share
609            .expose(|share| expected == V::Public::generator() * share)
610    }
611
612    #[must_use]
613    fn check_dealer_log<M: Faults, B: BatchVerifier<PublicKey = P>>(
614        &self,
615        rng: &mut impl CryptoRngCore,
616        strategy: &impl Strategy,
617        round_transcript: &Transcript,
618        dealer: &P,
619        log: &DealerLog<V, P>,
620    ) -> bool {
621        if self.dealer_index(dealer).is_err() {
622            return false;
623        }
624        if !self.check_dealer_pub_msg::<M>(dealer, &log.pub_msg) {
625            return false;
626        }
627        let Some(results_iter) = log.zip_players(&self.players) else {
628            return false;
629        };
630        let ack_summary = transcript_for_ack(round_transcript, dealer, &log.pub_msg).summarize();
631        let mut ack_batch = B::new();
632        let mut reveal_count = 0;
633        let max_reveals = self.max_reveals::<M>();
634        let mut reveal_eval_points = Vec::new();
635        let mut reveal_sum = Scalar::zero();
636        for (player, result) in results_iter {
637            match result {
638                AckOrReveal::Ack(ack) => {
639                    if !ack_summary.add_to_batch(&mut ack_batch, player, &ack.sig) {
640                        return false;
641                    }
642                }
643                AckOrReveal::Reveal(priv_msg) => {
644                    reveal_count += 1;
645                    if reveal_count > max_reveals {
646                        return false;
647                    }
648                    let Ok(player_scalar) = self.player_scalar(player) else {
649                        return false;
650                    };
651                    let coeff = if reveal_count == 1 {
652                        Scalar::one()
653                    } else {
654                        Scalar::random(&mut *rng)
655                    };
656                    reveal_eval_points.push((coeff.clone(), player_scalar));
657                    priv_msg
658                        .share
659                        .expose(|share| reveal_sum += &(coeff * share));
660                }
661            }
662        }
663        if !ack_batch.verify(&mut *rng) {
664            return false;
665        }
666        let lhs = log.pub_msg.commitment.lin_comb_eval(
667            reveal_eval_points
668                .into_iter()
669                .map(|(coeff, point)| (coeff, Cow::Owned(point))),
670            strategy,
671        );
672        lhs == V::Public::generator() * &reveal_sum
673    }
674}
675
676impl<V: Variant, P: PublicKey> Info<V, P> {
677    /// Create a new [`Info`].
678    ///
679    /// `namespace` must be provided to isolate different applications
680    /// performing DKGs from each other.
681    /// `round` should be a counter, always incrementing, even for failed DKGs.
682    /// `previous` should be the result of the previous successful DKG.
683    /// `dealers` should be the list of public keys for the dealers. This MUST
684    /// be a subset of the previous round's players.
685    /// `players` should be the list of public keys for the players.
686    pub fn new<M: Faults>(
687        namespace: &[u8],
688        round: u64,
689        previous: Option<Output<V, P>>,
690        mode: Mode,
691        dealers: Set<P>,
692        players: Set<P>,
693    ) -> Result<Self, Error> {
694        let participant_range = 1..u32::MAX as usize;
695        if !participant_range.contains(&dealers.len()) {
696            return Err(Error::NumDealers(dealers.len()));
697        }
698        if !participant_range.contains(&players.len()) {
699            return Err(Error::NumPlayers(players.len()));
700        }
701        if let Some(previous) = previous.as_ref() {
702            if let Some(unknown) = dealers
703                .iter()
704                .find(|d| previous.players.position(d).is_none())
705            {
706                return Err(Error::UnknownDealer(format!("{unknown:?}")));
707            }
708            if dealers.len() < previous.quorum::<M>() as usize {
709                return Err(Error::NumDealers(dealers.len()));
710            }
711        }
712        let summary = {
713            let mut transcript = Transcript::new(NAMESPACE);
714            transcript
715                .commit(namespace)
716                .commit(round.encode())
717                .commit(previous.encode())
718                .commit(dealers.encode())
719                .commit(players.encode());
720            // We want backwards compatibility with the default mode, which wasn't
721            // committed. The absence of the mode is thus treated as an implicit
722            // default in the transcript, so this is sound.
723            if mode != Mode::default() {
724                transcript.commit([mode as u8].as_slice());
725            }
726            transcript.summarize()
727        };
728        Ok(Self {
729            summary,
730            round,
731            previous,
732            mode,
733            dealers,
734            players,
735        })
736    }
737
738    /// Return the round number for this round.
739    ///
740    /// Round numbers should increase sequentially.
741    pub const fn round(&self) -> u64 {
742        self.round
743    }
744}
745
746#[derive(Clone, Debug)]
747pub struct DealerPubMsg<V: Variant> {
748    commitment: Poly<V::Public>,
749}
750
751impl<V: Variant> PartialEq for DealerPubMsg<V> {
752    fn eq(&self, other: &Self) -> bool {
753        self.commitment == other.commitment
754    }
755}
756
757impl<V: Variant> Eq for DealerPubMsg<V> {}
758
759impl<V: Variant> EncodeSize for DealerPubMsg<V> {
760    fn encode_size(&self) -> usize {
761        self.commitment.encode_size()
762    }
763}
764
765impl<V: Variant> Write for DealerPubMsg<V> {
766    fn write(&self, buf: &mut impl bytes::BufMut) {
767        self.commitment.write(buf);
768    }
769}
770
771impl<V: Variant> Read for DealerPubMsg<V> {
772    type Cfg = NonZeroU32;
773
774    fn read_cfg(
775        buf: &mut impl bytes::Buf,
776        &max_size: &Self::Cfg,
777    ) -> Result<Self, commonware_codec::Error> {
778        Ok(Self {
779            commitment: Read::read_cfg(buf, &(RangeCfg::from(NZU32!(1)..=max_size), ()))?,
780        })
781    }
782}
783
784#[cfg(feature = "arbitrary")]
785impl<V: Variant> arbitrary::Arbitrary<'_> for DealerPubMsg<V>
786where
787    V::Public: for<'a> arbitrary::Arbitrary<'a>,
788{
789    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
790        let commitment = u.arbitrary()?;
791        Ok(Self { commitment })
792    }
793}
794
795#[derive(Clone, Debug, PartialEq, Eq)]
796pub struct DealerPrivMsg {
797    share: Secret<Scalar>,
798}
799
800impl DealerPrivMsg {
801    /// Creates a new `DealerPrivMsg` with the given share.
802    pub const fn new(share: Scalar) -> Self {
803        Self {
804            share: Secret::new(share),
805        }
806    }
807}
808
809impl EncodeSize for DealerPrivMsg {
810    fn encode_size(&self) -> usize {
811        self.share.expose(|share| share.encode_size())
812    }
813}
814
815impl Write for DealerPrivMsg {
816    fn write(&self, buf: &mut impl bytes::BufMut) {
817        self.share.expose(|share| share.write(buf));
818    }
819}
820
821impl Read for DealerPrivMsg {
822    type Cfg = ();
823
824    fn read_cfg(
825        buf: &mut impl bytes::Buf,
826        _cfg: &Self::Cfg,
827    ) -> Result<Self, commonware_codec::Error> {
828        Ok(Self::new(ReadExt::read(buf)?))
829    }
830}
831
832#[cfg(feature = "arbitrary")]
833impl arbitrary::Arbitrary<'_> for DealerPrivMsg {
834    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
835        Ok(Self::new(u.arbitrary()?))
836    }
837}
838
839#[derive(Clone, Debug)]
840pub struct PlayerAck<P: PublicKey> {
841    sig: P::Signature,
842}
843
844impl<P: PublicKey> PartialEq for PlayerAck<P> {
845    fn eq(&self, other: &Self) -> bool {
846        self.sig == other.sig
847    }
848}
849
850impl<P: PublicKey> EncodeSize for PlayerAck<P> {
851    fn encode_size(&self) -> usize {
852        self.sig.encode_size()
853    }
854}
855
856impl<P: PublicKey> Write for PlayerAck<P> {
857    fn write(&self, buf: &mut impl bytes::BufMut) {
858        self.sig.write(buf);
859    }
860}
861
862impl<P: PublicKey> Read for PlayerAck<P> {
863    type Cfg = ();
864
865    fn read_cfg(
866        buf: &mut impl bytes::Buf,
867        _cfg: &Self::Cfg,
868    ) -> Result<Self, commonware_codec::Error> {
869        Ok(Self {
870            sig: ReadExt::read(buf)?,
871        })
872    }
873}
874
875#[cfg(feature = "arbitrary")]
876impl<P: PublicKey> arbitrary::Arbitrary<'_> for PlayerAck<P>
877where
878    P::Signature: for<'a> arbitrary::Arbitrary<'a>,
879{
880    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
881        let sig = u.arbitrary()?;
882        Ok(Self { sig })
883    }
884}
885
886#[derive(Clone, PartialEq)]
887enum AckOrReveal<P: PublicKey> {
888    Ack(PlayerAck<P>),
889    Reveal(DealerPrivMsg),
890}
891
892impl<P: PublicKey> AckOrReveal<P> {
893    const fn is_reveal(&self) -> bool {
894        matches!(*self, Self::Reveal(_))
895    }
896}
897
898impl<P: PublicKey> std::fmt::Debug for AckOrReveal<P> {
899    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
900        match self {
901            Self::Ack(x) => write!(f, "Ack({x:?})"),
902            Self::Reveal(_) => write!(f, "Reveal(REDACTED)"),
903        }
904    }
905}
906
907impl<P: PublicKey> EncodeSize for AckOrReveal<P> {
908    fn encode_size(&self) -> usize {
909        1 + match self {
910            Self::Ack(x) => x.encode_size(),
911            Self::Reveal(x) => x.encode_size(),
912        }
913    }
914}
915
916impl<P: PublicKey> Write for AckOrReveal<P> {
917    fn write(&self, buf: &mut impl bytes::BufMut) {
918        match self {
919            Self::Ack(x) => {
920                0u8.write(buf);
921                x.write(buf);
922            }
923            Self::Reveal(x) => {
924                1u8.write(buf);
925                x.write(buf);
926            }
927        }
928    }
929}
930
931impl<P: PublicKey> Read for AckOrReveal<P> {
932    type Cfg = ();
933
934    fn read_cfg(
935        buf: &mut impl bytes::Buf,
936        _cfg: &Self::Cfg,
937    ) -> Result<Self, commonware_codec::Error> {
938        let tag = u8::read(buf)?;
939        match tag {
940            0 => Ok(Self::Ack(ReadExt::read(buf)?)),
941            1 => Ok(Self::Reveal(ReadExt::read(buf)?)),
942            x => Err(commonware_codec::Error::InvalidEnum(x)),
943        }
944    }
945}
946
947#[cfg(feature = "arbitrary")]
948impl<P: PublicKey> arbitrary::Arbitrary<'_> for AckOrReveal<P>
949where
950    P: for<'a> arbitrary::Arbitrary<'a>,
951    P::Signature: for<'a> arbitrary::Arbitrary<'a>,
952{
953    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
954        let choice = u.int_in_range(0..=1)?;
955        match choice {
956            0 => {
957                let ack = u.arbitrary()?;
958                Ok(Self::Ack(ack))
959            }
960            1 => {
961                let reveal = u.arbitrary()?;
962                Ok(Self::Reveal(reveal))
963            }
964            _ => unreachable!(),
965        }
966    }
967}
968
969#[derive(Clone, Debug)]
970enum DealerResult<P: PublicKey> {
971    Ok(Map<P, AckOrReveal<P>>),
972    TooManyReveals,
973}
974
975impl<P: PublicKey> PartialEq for DealerResult<P> {
976    fn eq(&self, other: &Self) -> bool {
977        match (self, other) {
978            (Self::Ok(x), Self::Ok(y)) => x == y,
979            (Self::TooManyReveals, Self::TooManyReveals) => true,
980            _ => false,
981        }
982    }
983}
984
985impl<P: PublicKey> EncodeSize for DealerResult<P> {
986    fn encode_size(&self) -> usize {
987        1 + match self {
988            Self::Ok(r) => r.encode_size(),
989            Self::TooManyReveals => 0,
990        }
991    }
992}
993
994impl<P: PublicKey> Write for DealerResult<P> {
995    fn write(&self, buf: &mut impl bytes::BufMut) {
996        match self {
997            Self::Ok(r) => {
998                0u8.write(buf);
999                r.write(buf);
1000            }
1001            Self::TooManyReveals => {
1002                1u8.write(buf);
1003            }
1004        }
1005    }
1006}
1007
1008impl<P: PublicKey> Read for DealerResult<P> {
1009    type Cfg = NonZeroU32;
1010
1011    fn read_cfg(
1012        buf: &mut impl bytes::Buf,
1013        &max_players: &Self::Cfg,
1014    ) -> Result<Self, commonware_codec::Error> {
1015        let tag = u8::read(buf)?;
1016        match tag {
1017            0 => Ok(Self::Ok(Read::read_cfg(
1018                buf,
1019                &(RangeCfg::from(0..=max_players.get() as usize), (), ()),
1020            )?)),
1021            1 => Ok(Self::TooManyReveals),
1022            x => Err(commonware_codec::Error::InvalidEnum(x)),
1023        }
1024    }
1025}
1026
1027#[cfg(feature = "arbitrary")]
1028impl<P: PublicKey> arbitrary::Arbitrary<'_> for DealerResult<P>
1029where
1030    P: for<'a> arbitrary::Arbitrary<'a>,
1031    P::Signature: for<'a> arbitrary::Arbitrary<'a>,
1032{
1033    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1034        let choice = u.int_in_range(0..=1)?;
1035        match choice {
1036            0 => {
1037                use commonware_utils::TryFromIterator;
1038                use std::collections::HashMap;
1039
1040                let base: HashMap<P, AckOrReveal<P>> = u.arbitrary()?;
1041                let map =
1042                    Map::try_from_iter(base).map_err(|_| arbitrary::Error::IncorrectFormat)?;
1043
1044                Ok(Self::Ok(map))
1045            }
1046            1 => Ok(Self::TooManyReveals),
1047            _ => unreachable!(),
1048        }
1049    }
1050}
1051
1052#[derive(Clone, Debug)]
1053pub struct DealerLog<V: Variant, P: PublicKey> {
1054    pub_msg: DealerPubMsg<V>,
1055    results: DealerResult<P>,
1056}
1057
1058impl<V: Variant, P: PublicKey> PartialEq for DealerLog<V, P> {
1059    fn eq(&self, other: &Self) -> bool {
1060        self.pub_msg == other.pub_msg && self.results == other.results
1061    }
1062}
1063
1064impl<V: Variant, P: PublicKey> EncodeSize for DealerLog<V, P> {
1065    fn encode_size(&self) -> usize {
1066        self.pub_msg.encode_size() + self.results.encode_size()
1067    }
1068}
1069
1070impl<V: Variant, P: PublicKey> Write for DealerLog<V, P> {
1071    fn write(&self, buf: &mut impl bytes::BufMut) {
1072        self.pub_msg.write(buf);
1073        self.results.write(buf);
1074    }
1075}
1076
1077impl<V: Variant, P: PublicKey> Read for DealerLog<V, P> {
1078    type Cfg = NonZeroU32;
1079
1080    fn read_cfg(
1081        buf: &mut impl bytes::Buf,
1082        cfg: &Self::Cfg,
1083    ) -> Result<Self, commonware_codec::Error> {
1084        Ok(Self {
1085            pub_msg: Read::read_cfg(buf, cfg)?,
1086            results: Read::read_cfg(buf, cfg)?,
1087        })
1088    }
1089}
1090
1091impl<V: Variant, P: PublicKey> DealerLog<V, P> {
1092    fn get_ack(&self, player: &P) -> Option<&PlayerAck<P>> {
1093        let DealerResult::Ok(results) = &self.results else {
1094            return None;
1095        };
1096        match results.get_value(player) {
1097            Some(AckOrReveal::Ack(ack)) => Some(ack),
1098            _ => None,
1099        }
1100    }
1101
1102    fn get_reveal(&self, player: &P) -> Option<&DealerPrivMsg> {
1103        let DealerResult::Ok(results) = &self.results else {
1104            return None;
1105        };
1106        match results.get_value(player) {
1107            Some(AckOrReveal::Reveal(priv_msg)) => Some(priv_msg),
1108            _ => None,
1109        }
1110    }
1111
1112    fn zip_players<'a, 'b>(
1113        &'a self,
1114        players: &'b Set<P>,
1115    ) -> Option<impl Iterator<Item = (&'b P, &'a AckOrReveal<P>)>> {
1116        match &self.results {
1117            DealerResult::TooManyReveals => None,
1118            DealerResult::Ok(results) => {
1119                // We don't check this on deserialization.
1120                if results.keys() != players {
1121                    return None;
1122                }
1123                Some(players.iter().zip(results.values().iter()))
1124            }
1125        }
1126    }
1127
1128    /// Return a [`DealerLogSummary`] of the results in this log.
1129    ///
1130    /// This can be useful for observing the progress of the DKG.
1131    pub fn summary(&self) -> DealerLogSummary<P> {
1132        match &self.results {
1133            DealerResult::TooManyReveals => DealerLogSummary::TooManyReveals,
1134            DealerResult::Ok(map) => {
1135                let (reveals, acks): (Vec<_>, Vec<_>) =
1136                    map.iter_pairs().partition(|(_, a_r)| a_r.is_reveal());
1137                DealerLogSummary::Ok {
1138                    acks: acks
1139                        .into_iter()
1140                        .map(|(p, _)| p.clone())
1141                        .try_collect()
1142                        .expect("map keys are deduped"),
1143                    reveals: reveals
1144                        .into_iter()
1145                        .map(|(p, _)| p.clone())
1146                        .try_collect()
1147                        .expect("map keys are deduped"),
1148                }
1149            }
1150        }
1151    }
1152}
1153
1154/// Information about the reveals and acks in a [`DealerLog`].
1155// This exists to have a public interface we're happy maintaining, not leaking
1156// internal details about various things.
1157#[derive(Clone, Debug)]
1158pub enum DealerLogSummary<P> {
1159    /// The dealer is refusing to post any information, because they would have
1160    /// too many reveals otherwise.
1161    TooManyReveals,
1162    /// The dealer has some players who acked, and some players who didn't, that it's revealing.
1163    Ok { acks: Set<P>, reveals: Set<P> },
1164}
1165
1166#[cfg(feature = "arbitrary")]
1167impl<V: Variant, P: PublicKey> arbitrary::Arbitrary<'_> for DealerLog<V, P>
1168where
1169    P: for<'a> arbitrary::Arbitrary<'a>,
1170    V::Public: for<'a> arbitrary::Arbitrary<'a>,
1171    P::Signature: for<'a> arbitrary::Arbitrary<'a>,
1172{
1173    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1174        let pub_msg = u.arbitrary()?;
1175        let results = u.arbitrary()?;
1176        Ok(Self { pub_msg, results })
1177    }
1178}
1179
1180/// A [`DealerLog`], but identified to and signed by a dealer.
1181///
1182/// The [`SignedDealerLog::check`] method allows extracting a public key (the dealer)
1183/// and a [`DealerLog`] from this struct.
1184///
1185/// This avoids having to trust some other party or process for knowing that a
1186/// dealer actually produced a log.
1187#[derive(Clone, Debug)]
1188pub struct SignedDealerLog<V: Variant, S: Signer> {
1189    dealer: S::PublicKey,
1190    log: DealerLog<V, S::PublicKey>,
1191    sig: S::Signature,
1192}
1193
1194impl<V: Variant, S: Signer> PartialEq for SignedDealerLog<V, S> {
1195    fn eq(&self, other: &Self) -> bool {
1196        self.dealer == other.dealer && self.log == other.log && self.sig == other.sig
1197    }
1198}
1199
1200impl<V: Variant, S: Signer> SignedDealerLog<V, S> {
1201    fn sign(sk: &S, info: &Info<V, S::PublicKey>, log: DealerLog<V, S::PublicKey>) -> Self {
1202        let sig = transcript_for_log(info, &log).sign(sk);
1203        Self {
1204            dealer: sk.public_key(),
1205            log,
1206            sig,
1207        }
1208    }
1209
1210    /// Check this log for a particular round.
1211    ///
1212    /// This will produce the public key of the dealer that signed this log,
1213    /// and the underlying log that they signed.
1214    ///
1215    /// This will return [`Option::None`] if the check fails.
1216    #[allow(clippy::type_complexity)]
1217    pub fn check(
1218        self,
1219        info: &Info<V, S::PublicKey>,
1220    ) -> Option<(S::PublicKey, DealerLog<V, S::PublicKey>)> {
1221        if !transcript_for_log(info, &self.log).verify(&self.dealer, &self.sig) {
1222            return None;
1223        }
1224        Some((self.dealer, self.log))
1225    }
1226}
1227
1228impl<V: Variant, S: Signer> EncodeSize for SignedDealerLog<V, S> {
1229    fn encode_size(&self) -> usize {
1230        self.dealer.encode_size() + self.log.encode_size() + self.sig.encode_size()
1231    }
1232}
1233
1234impl<V: Variant, S: Signer> Write for SignedDealerLog<V, S> {
1235    fn write(&self, buf: &mut impl bytes::BufMut) {
1236        self.dealer.write(buf);
1237        self.log.write(buf);
1238        self.sig.write(buf);
1239    }
1240}
1241
1242impl<V: Variant, S: Signer> Read for SignedDealerLog<V, S> {
1243    type Cfg = NonZeroU32;
1244
1245    fn read_cfg(
1246        buf: &mut impl bytes::Buf,
1247        cfg: &Self::Cfg,
1248    ) -> Result<Self, commonware_codec::Error> {
1249        Ok(Self {
1250            dealer: ReadExt::read(buf)?,
1251            log: Read::read_cfg(buf, cfg)?,
1252            sig: ReadExt::read(buf)?,
1253        })
1254    }
1255}
1256
1257#[cfg(feature = "arbitrary")]
1258impl<V: Variant, S: Signer> arbitrary::Arbitrary<'_> for SignedDealerLog<V, S>
1259where
1260    S::PublicKey: for<'a> arbitrary::Arbitrary<'a>,
1261    V::Public: for<'a> arbitrary::Arbitrary<'a>,
1262    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
1263{
1264    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1265        let dealer = u.arbitrary()?;
1266        let log = u.arbitrary()?;
1267        let sig = u.arbitrary()?;
1268        Ok(Self { dealer, log, sig })
1269    }
1270}
1271
1272fn transcript_for_round<V: Variant, P: PublicKey>(info: &Info<V, P>) -> Transcript {
1273    Transcript::resume(info.summary)
1274}
1275
1276fn transcript_for_ack<V: Variant, P: PublicKey>(
1277    transcript: &Transcript,
1278    dealer: &P,
1279    pub_msg: &DealerPubMsg<V>,
1280) -> Transcript {
1281    let mut out = transcript.fork(SIG_ACK);
1282    out.commit(dealer.encode());
1283    out.commit(pub_msg.encode());
1284    out
1285}
1286
1287fn transcript_for_log<V: Variant, P: PublicKey>(
1288    info: &Info<V, P>,
1289    log: &DealerLog<V, P>,
1290) -> Transcript {
1291    let mut out = transcript_for_round(info).fork(SIG_LOG);
1292    out.commit(log.encode());
1293    out
1294}
1295
1296/// Accumulates dealer logs for a DKG round and caches verification results so
1297/// `pre_verify` work can be reused until a dealer's log is replaced.
1298#[derive(Clone)]
1299pub struct Logs<V: Variant, P: PublicKey, M: Faults> {
1300    info: Info<V, P>,
1301    logs: BTreeMap<P, DealerLog<V, P>>,
1302    known: BTreeMap<P, bool>,
1303    phantom_m: PhantomData<M>,
1304}
1305
1306// Keep the selected logs paired with the bound round info without repeating
1307// the tuple shape at each `select` call site.
1308type SelectedLogs<V, P> = (Info<V, P>, Map<P, DealerLog<V, P>>);
1309
1310impl<V: Variant, P: PublicKey, M: Faults> Logs<V, P, M> {
1311    /// Create a log set bound to a particular DKG round.
1312    pub fn new(info: Info<V, P>) -> Self {
1313        Self {
1314            info,
1315            logs: Default::default(),
1316            known: Default::default(),
1317            phantom_m: Default::default(),
1318        }
1319    }
1320
1321    fn check_dealers<B: BatchVerifier<PublicKey = P>>(
1322        rng: &mut impl CryptoRngCore,
1323        info: &Info<V, P>,
1324        strategy: &impl Strategy,
1325        transcript: &Transcript,
1326        dealers: &[(&P, &DealerLog<V, P>)],
1327    ) -> Vec<(P, bool)> {
1328        let checks: Vec<_> = dealers
1329            .iter()
1330            .map(|&(dealer, log)| {
1331                let seed = Summary::random(&mut *rng);
1332                ((*dealer).clone(), log, seed)
1333            })
1334            .collect();
1335
1336        // This uses signature batch verification only for a particular dealer's
1337        // signatures. We could batch across all dealers, but in practice this starts
1338        // performing worse than just using parallelism with at least 4 threads,
1339        // and also introduces a slow path if any dealer has a bad sig. This slow
1340        // path can easily be exercised by an adversary.
1341        strategy.map_collect_vec(checks, |(dealer, log, seed)| {
1342            let mut local_rng = Transcript::resume(seed).noise(NOISE_PRE_VERIFY);
1343            let valid =
1344                info.check_dealer_log::<M, B>(&mut local_rng, strategy, transcript, &dealer, log);
1345            (dealer, valid)
1346        })
1347    }
1348
1349    /// Record the log for a particular dealer.
1350    ///
1351    /// Return `true` if the dealer was already present in the log, in which
1352    /// case its log will be replaced.
1353    pub fn record(&mut self, dealer: P, log: DealerLog<V, P>) -> bool {
1354        self.known.remove(&dealer);
1355        self.logs.insert(dealer, log).is_some()
1356    }
1357
1358    /// Verify the logs that we've received so far.
1359    ///
1360    /// This makes finalization faster, by doing some of
1361    /// the verification work now.
1362    ///
1363    /// This method can amortize work over a batch of items. It's more efficient
1364    /// to call it after several [`Self::record`], rather than after
1365    /// each call.
1366    pub fn pre_verify<B: BatchVerifier<PublicKey = P>>(
1367        &mut self,
1368        rng: &mut impl CryptoRngCore,
1369        strategy: &impl Strategy,
1370    ) {
1371        let required_commitments = self.info.required_commitments::<M>() as usize;
1372        let transcript = transcript_for_round(&self.info);
1373
1374        // Create a pending batch, which we try and optimistically size as small as
1375        // possible, to avoid verifying more dealers than we need, if they're all
1376        // honest.
1377        let mut need = required_commitments;
1378        let mut pending = Vec::new();
1379        let mut iter = self.logs.iter();
1380        while need > 0 {
1381            let Some((dealer, log)) = iter.next() else {
1382                break;
1383            };
1384            match self.known.get(dealer) {
1385                Some(true) => need -= 1,
1386                Some(false) => {}
1387                None => {
1388                    need -= 1;
1389                    pending.push((dealer, log));
1390                }
1391            }
1392        }
1393
1394        // Verify the batch and update the known valid dealers.
1395        let pending_results =
1396            Self::check_dealers::<B>(rng, &self.info, strategy, &transcript, &pending);
1397        let mut all_pending_valid = true;
1398        for (dealer, is_valid) in pending_results {
1399            self.known.insert(dealer, is_valid);
1400            all_pending_valid &= is_valid;
1401        }
1402        if all_pending_valid {
1403            return;
1404        }
1405
1406        // We could jump back to the start of the function to recalculate the minimal pending set,
1407        // hoping that they would all be valid again. However, in this case, we're
1408        // dealing with some dealers that are malicious, and they might be trying to
1409        // slow down verification as much as possible by making us waste our time with
1410        // undue optimism. We instead adopt a pessimistic approach, assuming the
1411        // worst: that we might need to check all of the remaining dealers
1412        // to find the honest ones we need.
1413        let remaining: Vec<_> = iter
1414            .filter(|(dealer, _)| !self.known.contains_key(*dealer))
1415            .collect();
1416        let results = Self::check_dealers::<B>(rng, &self.info, strategy, &transcript, &remaining);
1417        for (dealer, is_valid) in results {
1418            self.known.insert(dealer, is_valid);
1419        }
1420    }
1421
1422    /// Given the logs we've received, determine which dealer logs to use, if any.
1423    ///
1424    /// This might return an error if there are not enough good logs that we can use.
1425    fn select<B: BatchVerifier<PublicKey = P>>(
1426        mut self,
1427        rng: &mut impl CryptoRngCore,
1428        strategy: &impl Strategy,
1429    ) -> Result<SelectedLogs<V, P>, Error> {
1430        self.pre_verify::<B>(rng, strategy);
1431        let required_commitments = self.info.required_commitments::<M>() as usize;
1432        let out: Map<_, _> = self
1433            .logs
1434            .into_iter()
1435            .filter(|(dealer, _)| matches!(self.known.get(dealer), Some(true)))
1436            .take(required_commitments)
1437            .try_collect()
1438            .expect("dealers should be unique");
1439        if out.len() < required_commitments {
1440            return Err(Error::DkgFailed);
1441        }
1442        Ok((self.info, out))
1443    }
1444}
1445
1446pub struct Dealer<V: Variant, S: Signer> {
1447    me: S,
1448    info: Info<V, S::PublicKey>,
1449    pub_msg: DealerPubMsg<V>,
1450    results: Map<S::PublicKey, AckOrReveal<S::PublicKey>>,
1451    transcript: Transcript,
1452}
1453
1454impl<V: Variant, S: Signer> Dealer<V, S> {
1455    /// Create a [`Dealer`].
1456    ///
1457    /// This needs randomness, to generate a dealing.
1458    ///
1459    /// We also need the dealer's private key, in order to produce the [`SignedDealerLog`].
1460    ///
1461    /// If we're doing a reshare, the dealer should have a share from the previous round.
1462    ///
1463    /// This will produce the [`Dealer`], a [`DealerPubMsg`] to send to every player,
1464    /// and a list of [`DealerPrivMsg`]s, along with which players those need to
1465    /// be sent to.
1466    ///
1467    /// The public message can be sent in the clear, but it's important that players
1468    /// know which dealer sent what public message. You MUST ensure that dealers
1469    /// cannot impersonate each-other when sending this message.
1470    ///
1471    /// The private message MUST be sent encrypted (or, in some other way, privately)
1472    /// to the target player. Similarly, that player MUST be convinced that this dealer
1473    /// sent it that message, without any possibility of impersonation. A simple way
1474    /// to provide both guarantees is through an authenticated channel, e.g. via
1475    /// [crate::handshake], or [commonware-p2p](https://docs.rs/commonware-p2p/latest/commonware_p2p/).
1476    #[allow(clippy::type_complexity)]
1477    pub fn start<M: Faults>(
1478        mut rng: impl CryptoRngCore,
1479        info: Info<V, S::PublicKey>,
1480        me: S,
1481        share: Option<Share>,
1482    ) -> Result<(Self, DealerPubMsg<V>, Vec<(S::PublicKey, DealerPrivMsg)>), Error> {
1483        // Check that this dealer is defined in the round.
1484        info.dealer_index(&me.public_key())?;
1485        let share = info.unwrap_or_random_share(
1486            &mut rng,
1487            // We are extracting the private scalar from `Secret` protection because
1488            // `Poly::new_with_constant` requires an owned value. The extracted scalar is
1489            // scoped to this function and will be zeroized on drop (i.e. the secret is
1490            // only exposed for the duration of this function).
1491            share.map(|x| x.private.expose_unwrap()),
1492        )?;
1493        let my_poly = Poly::new_with_constant(&mut rng, info.degree::<M>(), share);
1494        let priv_msgs = info
1495            .players
1496            .iter()
1497            .map(|pk| {
1498                (
1499                    pk.clone(),
1500                    DealerPrivMsg::new(my_poly.eval_msm(
1501                        &info.player_scalar(pk).expect("player should exist"),
1502                        &Sequential,
1503                    )),
1504                )
1505            })
1506            .collect::<Vec<_>>();
1507        let results: Map<_, _> = priv_msgs
1508            .clone()
1509            .into_iter()
1510            .map(|(pk, priv_msg)| (pk, AckOrReveal::Reveal(priv_msg)))
1511            .try_collect()
1512            .expect("players are unique");
1513        let commitment = Poly::commit(my_poly);
1514        let pub_msg = DealerPubMsg { commitment };
1515        let transcript = {
1516            let t = transcript_for_round(&info);
1517            transcript_for_ack(&t, &me.public_key(), &pub_msg)
1518        };
1519        let this = Self {
1520            me,
1521            info,
1522            pub_msg: pub_msg.clone(),
1523            results,
1524            transcript,
1525        };
1526        Ok((this, pub_msg, priv_msgs))
1527    }
1528
1529    /// Process an acknowledgement from a player.
1530    ///
1531    /// Acknowledgements should really only be processed once per player,
1532    /// but this method is idempotent nonetheless.
1533    pub fn receive_player_ack(
1534        &mut self,
1535        player: S::PublicKey,
1536        ack: PlayerAck<S::PublicKey>,
1537    ) -> Result<(), Error> {
1538        let res_mut = self
1539            .results
1540            .get_value_mut(&player)
1541            .ok_or(Error::UnknownPlayer)?;
1542        if self.transcript.verify(&player, &ack.sig) {
1543            *res_mut = AckOrReveal::Ack(ack);
1544        }
1545        Ok(())
1546    }
1547
1548    /// Finalize the dealer, producing a signed log.
1549    ///
1550    /// This should be called at the point where no more acks will be processed.
1551    pub fn finalize<M: Faults>(self) -> SignedDealerLog<V, S> {
1552        let reveals = self
1553            .results
1554            .values()
1555            .iter()
1556            .filter(|x| x.is_reveal())
1557            .count() as u32;
1558        // Omit results if there are too many reveals.
1559        let results = if reveals > self.info.max_reveals::<M>() {
1560            DealerResult::TooManyReveals
1561        } else {
1562            DealerResult::Ok(self.results)
1563        };
1564        let log = DealerLog {
1565            pub_msg: self.pub_msg,
1566            results,
1567        };
1568        SignedDealerLog::sign(&self.me, &self.info, log)
1569    }
1570}
1571
1572struct ObserveInner<V: Variant, P: PublicKey> {
1573    output: Output<V, P>,
1574    weights: Option<Interpolator<P, Scalar>>,
1575}
1576
1577impl<V: Variant, P: PublicKey> ObserveInner<V, P> {
1578    fn reckon<M: Faults>(
1579        info: Info<V, P>,
1580        selected: Map<P, DealerLog<V, P>>,
1581        strategy: &impl Strategy,
1582    ) -> Result<Self, Error> {
1583        // Track players with too many reveals
1584        let max_faults = info.players.max_faults::<M>();
1585        let mut reveal_counts: BTreeMap<P, u32> = BTreeMap::new();
1586        let mut revealed = Vec::new();
1587        for log in selected.values() {
1588            let Some(iter) = log.zip_players(&info.players) else {
1589                continue;
1590            };
1591            for (player, result) in iter {
1592                if !result.is_reveal() {
1593                    continue;
1594                }
1595                let count = reveal_counts.entry(player.clone()).or_insert(0);
1596                *count += 1;
1597                if *count == max_faults + 1 {
1598                    revealed.push(player.clone());
1599                }
1600            }
1601        }
1602        let revealed: Set<P> = revealed
1603            .into_iter()
1604            .try_collect()
1605            .expect("players are unique");
1606
1607        // Extract dealers before consuming selected
1608        let dealers: Set<P> = selected
1609            .keys()
1610            .iter()
1611            .cloned()
1612            .try_collect()
1613            .expect("selected dealers are unique");
1614
1615        // Recover the public polynomial
1616        let (public, weights) = if let Some(previous) = info.previous.as_ref() {
1617            let weights = previous
1618                .public()
1619                .mode()
1620                .subset_interpolator(previous.players(), selected.keys())
1621                .expect("the result of select should produce a valid subset");
1622            let commitments = selected
1623                .into_iter()
1624                .map(|(dealer, log)| (dealer, log.pub_msg.commitment))
1625                .try_collect::<Map<_, _>>()
1626                .expect("Map should have unique keys");
1627            let public = weights
1628                .interpolate(&commitments, strategy)
1629                .expect("select checks that enough points have been provided");
1630            if previous.public().public() != public.constant() {
1631                return Err(Error::DkgFailed);
1632            }
1633            (public, Some(weights))
1634        } else {
1635            let mut public = Poly::zero();
1636            for log in selected.values() {
1637                public += &log.pub_msg.commitment;
1638            }
1639            (public, None)
1640        };
1641        let n = info.players.len() as u32;
1642        let output = Output {
1643            summary: info.summary,
1644            public: Sharing::new(info.mode, NZU32!(n), public),
1645            dealers,
1646            players: info.players,
1647            revealed,
1648        };
1649        Ok(Self { output, weights })
1650    }
1651}
1652
1653/// Observe the result of a DKG, using the public results.
1654///
1655/// The log mapping dealers to their log is the shared piece of information
1656/// that the participants (players, observers) of the DKG must all agree on.
1657///
1658/// From this log, we can (potentially, as the DKG can fail) compute the public output.
1659///
1660/// This will only ever return [`Error::DkgFailed`].
1661pub fn observe<V: Variant, P: PublicKey, M: Faults, B: BatchVerifier<PublicKey = P>>(
1662    rng: &mut impl CryptoRngCore,
1663    logs: Logs<V, P, M>,
1664    strategy: &impl Strategy,
1665) -> Result<Output<V, P>, Error> {
1666    let (info, selected) = logs.select::<B>(rng, strategy)?;
1667    ObserveInner::<V, P>::reckon::<M>(info, selected, strategy).map(|x| x.output)
1668}
1669
1670/// Represents a player in the DKG / reshare process.
1671///
1672/// The player is attempting to get a share of the key.
1673///
1674/// They need not have participated in prior rounds.
1675pub struct Player<V: Variant, S: Signer> {
1676    me: S,
1677    me_pub: S::PublicKey,
1678    info: Info<V, S::PublicKey>,
1679    index: Participant,
1680    transcript: Transcript,
1681    view: BTreeMap<S::PublicKey, (DealerPubMsg<V>, DealerPrivMsg)>,
1682}
1683
1684impl<V: Variant, S: Signer> Player<V, S> {
1685    /// Create a new [`Player`].
1686    ///
1687    /// We need the player's private key in order to sign messages.
1688    pub fn new(info: Info<V, S::PublicKey>, me: S) -> Result<Self, Error> {
1689        let me_pub = me.public_key();
1690        Ok(Self {
1691            index: info.player_index(&me_pub)?,
1692            me,
1693            me_pub,
1694            transcript: transcript_for_round(&info),
1695            info,
1696            view: BTreeMap::new(),
1697        })
1698    }
1699
1700    /// Resume a [`Player`], given some existing public state.
1701    ///
1702    /// This is equivalent to calling [`Self::new`] and then [`Self::dealer_message`]
1703    /// with the appropriate messages, but includes extra safeguards to detect
1704    /// missing / corrupted state.
1705    ///
1706    /// It's imperative that the `logs` passed in have been verified. This is done
1707    /// naturally when converting from a [`SignedDealerLog`] to a [`DealerLog`],
1708    /// but this function, like [`Player::finalize`], assumes that this check has
1709    /// been done.
1710    ///
1711    /// All messages the player should have received must be passed into this method,
1712    /// and if any messages which should be present based on this player's actions
1713    /// in the log are missing, this method will return [`Error::MissingPlayerDealing`].
1714    ///
1715    /// For example, if a particular private message containing a share is not
1716    /// present in `msgs`, but we've already acknowledged it, and this has been
1717    /// included in a public log, then this method will fail.
1718    /// This method cannot catch all cases where state has been corrupted. In
1719    /// particular, if a dealer has not posted their log publicly yet, but has
1720    /// already received an ack, then this method cannot help in that case,
1721    /// but the issue still remains.
1722    ///
1723    /// The returned map contains the acknowledgements generated while replaying
1724    /// `msgs`, keyed by dealer.
1725    #[allow(clippy::type_complexity)]
1726    pub fn resume<M: Faults>(
1727        info: Info<V, S::PublicKey>,
1728        me: S,
1729        logs: &BTreeMap<S::PublicKey, DealerLog<V, S::PublicKey>>,
1730        msgs: impl IntoIterator<Item = (S::PublicKey, DealerPubMsg<V>, DealerPrivMsg)>,
1731    ) -> Result<(Self, BTreeMap<S::PublicKey, PlayerAck<S::PublicKey>>), Error> {
1732        // Record all acks we've emitted (by dealer).
1733        let mut this = Self::new(info, me)?;
1734        let mut acks = BTreeMap::new();
1735        for (dealer, pub_msg, priv_msg) in msgs {
1736            if let Some(ack) = this.dealer_message::<M>(dealer.clone(), pub_msg, priv_msg) {
1737                acks.insert(dealer, ack);
1738            }
1739        }
1740
1741        // Have we emitted any valid acks, publicly recorded, for which we do
1742        // not have the private message?
1743        if logs.iter().any(|(dealer, log)| {
1744            let Some(ack) = log.get_ack(&this.me_pub) else {
1745                return false;
1746            };
1747            // Only trust this ack if the signature is valid for this round.
1748            transcript_for_ack(&this.transcript, dealer, &log.pub_msg)
1749                .verify(&this.me_pub, &ack.sig)
1750                && !acks.contains_key(dealer)
1751        }) {
1752            // If so, we have a problem, because we're missing a dealing that we're
1753            // supposed to have, and that we publicly committed to having.
1754            return Err(Error::MissingPlayerDealing);
1755        }
1756
1757        Ok((this, acks))
1758    }
1759
1760    /// Process a message from a dealer.
1761    ///
1762    /// It's important that nobody can impersonate the dealer, and that the
1763    /// private message was not exposed to anyone else. A convenient way to
1764    /// provide this is by using an authenticated channel, e.g. via
1765    /// [crate::handshake], or [commonware-p2p](https://docs.rs/commonware-p2p/latest/commonware_p2p/).
1766    pub fn dealer_message<M: Faults>(
1767        &mut self,
1768        dealer: S::PublicKey,
1769        pub_msg: DealerPubMsg<V>,
1770        priv_msg: DealerPrivMsg,
1771    ) -> Option<PlayerAck<S::PublicKey>> {
1772        if self.view.contains_key(&dealer) {
1773            return None;
1774        }
1775        self.info.dealer_index(&dealer).ok()?;
1776        if !self.info.check_dealer_pub_msg::<M>(&dealer, &pub_msg) {
1777            return None;
1778        }
1779        if !self
1780            .info
1781            .check_dealer_priv_msg(&self.me_pub, &pub_msg, &priv_msg)
1782        {
1783            return None;
1784        }
1785        let sig = transcript_for_ack(&self.transcript, &dealer, &pub_msg).sign(&self.me);
1786        self.view.insert(dealer, (pub_msg, priv_msg));
1787        Some(PlayerAck { sig })
1788    }
1789
1790    /// Finalize the player, producing an output, and a share.
1791    ///
1792    /// This should agree with [`observe`], in terms of `Ok` vs `Err` (with one exception)
1793    /// and the public output, so long as the logs agree. It's crucial that the players
1794    /// come to agreement, in some way, on exactly which logs they need to use
1795    /// for finalize.
1796    ///
1797    /// The exception is that if this function returns [`Error::MissingPlayerDealing`],
1798    /// then [`observe`] will return `Ok`, because this error indicates that this
1799    /// player's state has been corrupted, but the DKG has otherwise succeeded.
1800    /// However, this player's share is not recoverable without external intervention.
1801    ///
1802    /// Otherwise, this function returns [`Error::DkgFailed`] if the DKG fails, or
1803    /// [`Error::MismatchedLogs`] if `logs` are bound to a different DKG round.
1804    pub fn finalize<M: Faults, B: BatchVerifier<PublicKey = S::PublicKey>>(
1805        self,
1806        rng: &mut impl CryptoRngCore,
1807        logs: Logs<V, S::PublicKey, M>,
1808        strategy: &impl Strategy,
1809    ) -> Result<(Output<V, S::PublicKey>, Share), Error> {
1810        // `Logs::select` verifies ack signatures, so any ack present in `selected`
1811        // is trustworthy for this round/dealer/pub_msg transcript.
1812        // If there's a log that contains an ack of ours, but no corresponding view,
1813        // then we're missing a dealing.
1814        if logs.info != self.info {
1815            return Err(Error::MismatchedLogs);
1816        }
1817        let (_, selected) = logs.select::<B>(rng, strategy)?;
1818        if selected
1819            .iter_pairs()
1820            .any(|(d, l)| l.get_ack(&self.me_pub).is_some() && !self.view.contains_key(d))
1821        {
1822            return Err(Error::MissingPlayerDealing);
1823        }
1824
1825        // We are extracting the private scalars from `Secret` protection
1826        // because interpolation/summation needs owned scalars for polynomial
1827        // arithmetic. The extracted scalars are scoped to this function and
1828        // will be zeroized on drop (i.e. the secrets are only exposed for the
1829        // duration of this function).
1830        let dealings = selected
1831            .iter_pairs()
1832            .map(|(dealer, log)| {
1833                let share = self
1834                    .view
1835                    .get(dealer)
1836                    .map(|(_, priv_msg)| priv_msg.share.clone().expose_unwrap())
1837                    .unwrap_or_else(|| {
1838                        log.get_reveal(&self.me_pub).map_or_else(
1839                            || {
1840                                unreachable!(
1841                                    "Logs::select didn't check dealer reveal, or we're not a player?"
1842                                )
1843                            },
1844                            |priv_msg| priv_msg.share.clone().expose_unwrap(),
1845                        )
1846                    });
1847                (dealer.clone(), share)
1848            })
1849            .try_collect::<Map<_, _>>()
1850            .expect("Logs::select produces at most one entry per dealer");
1851        let ObserveInner { output, weights } =
1852            ObserveInner::<V, S::PublicKey>::reckon::<M>(self.info, selected, strategy)?;
1853        let private = weights.map_or_else(
1854            || {
1855                let mut out = <Scalar as Additive>::zero();
1856                for s in dealings.values() {
1857                    out += s;
1858                }
1859                out
1860            },
1861            |weights| {
1862                weights
1863                    .interpolate(&dealings, strategy)
1864                    .expect("Logs::select ensures that we can recover")
1865            },
1866        );
1867        let share = Share::new(self.index, Private::new(private));
1868        Ok((output, share))
1869    }
1870}
1871
1872/// The result of dealing shares to players.
1873pub type DealResult<V, P> = Result<(Output<V, P>, Map<P, Share>), Error>;
1874
1875/// Simply distribute shares at random, instead of performing a distributed protocol.
1876pub fn deal<V: Variant, P: Clone + Ord, M: Faults>(
1877    mut rng: impl CryptoRngCore,
1878    mode: Mode,
1879    players: Set<P>,
1880) -> DealResult<V, P> {
1881    if players.is_empty() {
1882        return Err(Error::NumPlayers(0));
1883    }
1884    let n = NZU32!(players.len() as u32);
1885    let t = players.quorum::<M>();
1886    let private = Poly::new(&mut rng, t - 1);
1887    let shares: Map<_, _> = players
1888        .iter()
1889        .enumerate()
1890        .map(|(i, p)| {
1891            let participant = Participant::from_usize(i);
1892            let eval = private.eval_msm(
1893                &mode
1894                    .scalar(n, participant)
1895                    .expect("player index should be valid"),
1896                &Sequential,
1897            );
1898            let share = Share::new(participant, Private::new(eval));
1899            (p.clone(), share)
1900        })
1901        .try_collect()
1902        .expect("players are unique");
1903    let output = Output {
1904        summary: Summary::random(&mut rng),
1905        public: Sharing::new(mode, n, Poly::commit(private)),
1906        dealers: players.clone(),
1907        players,
1908        revealed: Set::default(),
1909    };
1910    Ok((output, shares))
1911}
1912
1913/// Like [`deal`], but without linking the result to specific public keys.
1914///
1915/// This can be more convenient for testing, where you don't want to go through
1916/// the trouble of generating signing keys. The downside is that the result isn't
1917/// compatible with subsequent DKGs, which need an [`Output`].
1918pub fn deal_anonymous<V: Variant, M: Faults>(
1919    rng: impl CryptoRngCore,
1920    mode: Mode,
1921    n: NonZeroU32,
1922) -> (Sharing<V>, Vec<Share>) {
1923    let players = (0..n.get()).try_collect().unwrap();
1924    let (output, shares) = deal::<V, _, M>(rng, mode, players).unwrap();
1925    (output.public().clone(), shares.values().to_vec())
1926}
1927
1928#[cfg(any(feature = "arbitrary", test))]
1929mod test_plan {
1930    use super::*;
1931    use crate::{
1932        bls12381::primitives::{
1933            ops::{self, threshold},
1934            variant::Variant,
1935        },
1936        ed25519, PublicKey,
1937    };
1938    use anyhow::anyhow;
1939    use bytes::BytesMut;
1940    use commonware_utils::{Faults, N3f1, TryCollect};
1941    use core::num::NonZeroI32;
1942    use rand::{rngs::StdRng, SeedableRng as _};
1943    use std::collections::BTreeSet;
1944
1945    /// Apply a mask to some bytes, returning whether or not a modification happened
1946    fn apply_mask(bytes: &mut BytesMut, mask: &[u8]) -> bool {
1947        let mut modified = false;
1948        for (l, &r) in bytes.iter_mut().zip(mask.iter()) {
1949            modified |= r != 0;
1950            *l ^= r;
1951        }
1952        modified
1953    }
1954
1955    #[derive(Clone, Default, Debug)]
1956    pub struct Masks {
1957        pub info_summary: Vec<u8>,
1958        pub dealer: Vec<u8>,
1959        pub pub_msg: Vec<u8>,
1960        pub log: Vec<u8>,
1961    }
1962
1963    impl Masks {
1964        fn modifies_player_ack(&self) -> bool {
1965            self.info_summary.iter().any(|&b| b != 0)
1966                || self.dealer.iter().any(|&b| b != 0)
1967                || self.pub_msg.iter().any(|&b| b != 0)
1968        }
1969
1970        fn transcript_for_round<V: Variant, P: PublicKey>(
1971            &self,
1972            info: &Info<V, P>,
1973        ) -> anyhow::Result<(bool, Transcript)> {
1974            let mut summary_bs = info.summary.encode_mut();
1975            let modified = apply_mask(&mut summary_bs, &self.info_summary);
1976            let summary = Summary::read(&mut summary_bs)?;
1977            Ok((modified, Transcript::resume(summary)))
1978        }
1979
1980        fn transcript_for_player_ack<V: Variant, P: PublicKey>(
1981            &self,
1982            info: &Info<V, P>,
1983            dealer: &P,
1984            pub_msg: &DealerPubMsg<V>,
1985        ) -> anyhow::Result<(bool, Transcript)> {
1986            let (mut modified, transcript) = self.transcript_for_round(info)?;
1987            let mut transcript = transcript.fork(SIG_ACK);
1988
1989            let mut dealer_bs = dealer.encode_mut();
1990            modified |= apply_mask(&mut dealer_bs, &self.dealer);
1991            transcript.commit(&mut dealer_bs);
1992
1993            let mut pub_msg_bs = pub_msg.encode_mut();
1994            modified |= apply_mask(&mut pub_msg_bs, &self.pub_msg);
1995            transcript.commit(&mut pub_msg_bs);
1996
1997            Ok((modified, transcript))
1998        }
1999
2000        fn transcript_for_signed_dealer_log<V: Variant, P: PublicKey>(
2001            &self,
2002            info: &Info<V, P>,
2003            log: &DealerLog<V, P>,
2004        ) -> anyhow::Result<(bool, Transcript)> {
2005            let (mut modified, transcript) = self.transcript_for_round(info)?;
2006            let mut transcript = transcript.fork(SIG_LOG);
2007
2008            let mut log_bs = log.encode_mut();
2009            modified |= apply_mask(&mut log_bs, &self.log);
2010            transcript.commit(&mut log_bs);
2011
2012            Ok((modified, transcript))
2013        }
2014    }
2015
2016    /// A round in the DKG test plan.
2017    #[derive(Debug, Default)]
2018    pub struct Round {
2019        dealers: Vec<u32>,
2020        players: Vec<u32>,
2021        crash_resume_players: BTreeSet<(u32, u32)>,
2022        resume_missing_dealer_msg_fails: BTreeSet<(u32, u32)>,
2023        finalize_missing_dealer_msg_fails: BTreeSet<u32>,
2024        no_acks: BTreeSet<(u32, u32)>,
2025        bad_shares: BTreeSet<(u32, u32)>,
2026        bad_player_sigs: BTreeMap<(u32, u32), Masks>,
2027        bad_reveals: BTreeSet<(u32, u32)>,
2028        bad_dealer_sigs: BTreeMap<u32, Masks>,
2029        replace_shares: BTreeSet<u32>,
2030        shift_degrees: BTreeMap<u32, NonZeroI32>,
2031    }
2032
2033    impl Round {
2034        pub fn new(dealers: Vec<u32>, players: Vec<u32>) -> Self {
2035            Self {
2036                dealers,
2037                players,
2038                ..Default::default()
2039            }
2040        }
2041
2042        pub fn no_ack(mut self, dealer: u32, player: u32) -> Self {
2043            self.no_acks.insert((dealer, player));
2044            self
2045        }
2046
2047        pub fn crash_resume_player(mut self, after_dealer: u32, player: u32) -> Self {
2048            self.crash_resume_players.insert((after_dealer, player));
2049            self
2050        }
2051
2052        pub fn resume_missing_dealer_msg_fails(
2053            mut self,
2054            after_dealer: u32,
2055            missing_dealer: u32,
2056        ) -> Self {
2057            self.resume_missing_dealer_msg_fails
2058                .insert((after_dealer, missing_dealer));
2059            self
2060        }
2061
2062        pub fn finalize_missing_dealer_msg_fails(mut self, player: u32) -> Self {
2063            self.finalize_missing_dealer_msg_fails.insert(player);
2064            self
2065        }
2066
2067        pub fn bad_share(mut self, dealer: u32, player: u32) -> Self {
2068            self.bad_shares.insert((dealer, player));
2069            self
2070        }
2071
2072        pub fn bad_player_sig(mut self, dealer: u32, player: u32, masks: Masks) -> Self {
2073            self.bad_player_sigs.insert((dealer, player), masks);
2074            self
2075        }
2076
2077        pub fn bad_reveal(mut self, dealer: u32, player: u32) -> Self {
2078            self.bad_reveals.insert((dealer, player));
2079            self
2080        }
2081
2082        pub fn bad_dealer_sig(mut self, dealer: u32, masks: Masks) -> Self {
2083            self.bad_dealer_sigs.insert(dealer, masks);
2084            self
2085        }
2086
2087        pub fn replace_share(mut self, dealer: u32) -> Self {
2088            self.replace_shares.insert(dealer);
2089            self
2090        }
2091
2092        pub fn shift_degree(mut self, dealer: u32, shift: NonZeroI32) -> Self {
2093            self.shift_degrees.insert(dealer, shift);
2094            self
2095        }
2096
2097        /// Validate that this round is well-formed given the number of participants
2098        /// and the previous successful round's players.
2099        pub fn validate(
2100            &self,
2101            num_participants: u32,
2102            previous_players: Option<&[u32]>,
2103        ) -> anyhow::Result<()> {
2104            if self.dealers.is_empty() {
2105                return Err(anyhow!("dealers is empty"));
2106            }
2107            if self.players.is_empty() {
2108                return Err(anyhow!("players is empty"));
2109            }
2110            // Check dealer/player ranges
2111            for &d in &self.dealers {
2112                if d >= num_participants {
2113                    return Err(anyhow!("dealer {d} out of range [1, {num_participants}]"));
2114                }
2115            }
2116            for &p in &self.players {
2117                if p >= num_participants {
2118                    return Err(anyhow!("player {p} out of range [1, {num_participants}]"));
2119                }
2120            }
2121            // Crash/resume checkpoints must reference in-round dealers/players.
2122            for &(after_dealer, player) in &self.crash_resume_players {
2123                if !self.dealers.contains(&after_dealer) {
2124                    return Err(anyhow!("crash_resume dealer {after_dealer} not in round"));
2125                }
2126                if !self.players.contains(&player) {
2127                    return Err(anyhow!("crash_resume player {player} not in round"));
2128                }
2129            }
2130            let dealer_positions: BTreeMap<u32, usize> = self
2131                .dealers
2132                .iter()
2133                .enumerate()
2134                .map(|(idx, &dealer)| (dealer, idx))
2135                .collect();
2136            let previous_successful_round = previous_players.is_some();
2137            for &(after_dealer, missing_dealer) in &self.resume_missing_dealer_msg_fails {
2138                if !self.dealers.contains(&after_dealer) {
2139                    return Err(anyhow!("resume_missing dealer {after_dealer} not in round"));
2140                }
2141                if !self.dealers.contains(&missing_dealer) {
2142                    return Err(anyhow!(
2143                        "resume_missing missing_dealer {missing_dealer} not in round"
2144                    ));
2145                }
2146                let after_pos = dealer_positions[&after_dealer];
2147                let missing_pos = dealer_positions[&missing_dealer];
2148                if missing_pos > after_pos {
2149                    return Err(anyhow!(
2150                        "resume_missing missing_dealer {missing_dealer} appears after {after_dealer}"
2151                    ));
2152                }
2153                if self.bad(previous_successful_round, missing_dealer) {
2154                    return Err(anyhow!(
2155                        "resume_missing_dealer_msg_fails requires dealer {missing_dealer} to be good"
2156                    ));
2157                }
2158                let any_valid_ack = self.players.iter().any(|&player| {
2159                    let ack_corrupted = self.no_acks.contains(&(missing_dealer, player))
2160                        || self.bad_shares.contains(&(missing_dealer, player))
2161                        || self
2162                            .bad_player_sigs
2163                            .get(&(missing_dealer, player))
2164                            .is_some_and(Masks::modifies_player_ack);
2165                    !ack_corrupted
2166                });
2167                if !any_valid_ack {
2168                    return Err(anyhow!(
2169                        "resume_missing_dealer_msg_fails requires dealer {missing_dealer} to ack at least one player"
2170                    ));
2171                }
2172            }
2173            for &player in &self.finalize_missing_dealer_msg_fails {
2174                if !self.players.contains(&player) {
2175                    return Err(anyhow!("finalize_missing player {player} not in round"));
2176                }
2177            }
2178
2179            // If there's a previous round, check dealer constraints
2180            if let Some(prev_players) = previous_players {
2181                // Every dealer must have been a player in the previous round
2182                for &d in &self.dealers {
2183                    if !prev_players.contains(&d) {
2184                        return Err(anyhow!("dealer {d} was not a player in previous round"));
2185                    }
2186                }
2187                // Must have >= quorum(prev_players) dealers
2188                let required = N3f1::quorum(prev_players.len());
2189                if (self.dealers.len() as u32) < required {
2190                    return Err(anyhow!(
2191                        "not enough dealers: have {}, need {} (quorum of {} previous players)",
2192                        self.dealers.len(),
2193                        required,
2194                        prev_players.len()
2195                    ));
2196                }
2197            }
2198
2199            Ok(())
2200        }
2201
2202        fn bad(&self, previous_successful_round: bool, dealer: u32) -> bool {
2203            if self.replace_shares.contains(&dealer) && previous_successful_round {
2204                return true;
2205            }
2206            if let Some(shift) = self.shift_degrees.get(&dealer) {
2207                let degree = N3f1::quorum(self.players.len()) as i32 - 1;
2208                // We shift the degree, but saturate at 0, so it's possible
2209                // that the shift isn't actually doing anything.
2210                //
2211                // This is effectively the same as checking degree == 0 && shift < 0,
2212                // but matches what ends up happening a bit better.
2213                if (degree + shift.get()).max(0) != degree {
2214                    return true;
2215                }
2216            }
2217            if self.bad_reveals.iter().any(|&(d, _)| d == dealer) {
2218                return true;
2219            }
2220            let revealed_players = self
2221                .bad_shares
2222                .iter()
2223                .copied()
2224                .chain(self.no_acks.iter().copied())
2225                .filter_map(|(d, p)| if d == dealer { Some(p) } else { None })
2226                .collect::<BTreeSet<_>>();
2227            revealed_players.len() as u32 > N3f1::max_faults(self.players.len())
2228        }
2229
2230        /// Determine if this round is expected to fail.
2231        fn expect_failure(&self, previous_successful_round: Option<u32>) -> bool {
2232            let good_dealer_count = self
2233                .dealers
2234                .iter()
2235                .filter(|&&d| !self.bad(previous_successful_round.is_some(), d))
2236                .count();
2237            let required = previous_successful_round
2238                .map(N3f1::quorum)
2239                .unwrap_or_default()
2240                .max(N3f1::quorum(self.dealers.len())) as usize;
2241            good_dealer_count < required
2242        }
2243    }
2244
2245    /// A DKG test plan consisting of multiple rounds.
2246    #[derive(Debug)]
2247    pub struct Plan {
2248        num_participants: NonZeroU32,
2249        rounds: Vec<Round>,
2250    }
2251
2252    impl Plan {
2253        pub const fn new(num_participants: NonZeroU32) -> Self {
2254            Self {
2255                num_participants,
2256                rounds: Vec::new(),
2257            }
2258        }
2259
2260        pub fn with(mut self, round: Round) -> Self {
2261            self.rounds.push(round);
2262            self
2263        }
2264
2265        /// Validate the entire plan.
2266        pub(crate) fn validate(&self) -> anyhow::Result<()> {
2267            let mut last_successful_players: Option<Vec<u32>> = None;
2268
2269            for round in &self.rounds {
2270                round.validate(
2271                    self.num_participants.get(),
2272                    last_successful_players.as_deref(),
2273                )?;
2274
2275                // If this round is expected to succeed, update last_successful_players
2276                if !round.expect_failure(last_successful_players.as_ref().map(|x| x.len() as u32)) {
2277                    last_successful_players = Some(round.players.clone());
2278                }
2279            }
2280            Ok(())
2281        }
2282
2283        /// Run the test plan with a given seed.
2284        pub fn run<V: Variant>(self, seed: u64) -> anyhow::Result<()> {
2285            self.validate()?;
2286
2287            let mut rng = StdRng::seed_from_u64(seed);
2288
2289            // Generate keys for all participants (1-indexed to num_participants)
2290            let keys = (0..self.num_participants.get())
2291                .map(|_| ed25519::PrivateKey::random(&mut rng))
2292                .collect::<Vec<_>>();
2293
2294            // Precompute mapping from public key to key index to avoid confusion
2295            // between key indices and positions in sorted Sets.
2296            let pk_to_key_idx: BTreeMap<ed25519::PublicKey, u32> = keys
2297                .iter()
2298                .enumerate()
2299                .map(|(i, k)| (k.public_key(), i as u32))
2300                .collect();
2301
2302            // The max_read_size needs to account for shifted polynomial degrees.
2303            // Find the maximum positive shift across all rounds.
2304            let max_shift = self
2305                .rounds
2306                .iter()
2307                .flat_map(|r| r.shift_degrees.values())
2308                .map(|s| s.get())
2309                .max()
2310                .unwrap_or(0)
2311                .max(0) as u32;
2312            let max_read_size =
2313                NonZeroU32::new(self.num_participants.get() + max_shift).expect("non-zero");
2314
2315            let mut previous_output: Option<Output<V, ed25519::PublicKey>> = None;
2316            let mut shares: BTreeMap<ed25519::PublicKey, Share> = BTreeMap::new();
2317            let mut threshold_public_key: Option<V::Public> = None;
2318
2319            for (i_round, round) in self.rounds.into_iter().enumerate() {
2320                let previous_successful_round =
2321                    previous_output.as_ref().map(|o| o.players.len() as u32);
2322
2323                let dealer_set = round
2324                    .dealers
2325                    .iter()
2326                    .map(|&i| keys[i as usize].public_key())
2327                    .try_collect::<Set<_>>()
2328                    .unwrap();
2329                let player_set: Set<ed25519::PublicKey> = round
2330                    .players
2331                    .iter()
2332                    .map(|&i| keys[i as usize].public_key())
2333                    .try_collect()
2334                    .unwrap();
2335
2336                // Create round info
2337                let info = Info::new::<N3f1>(
2338                    b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
2339                    i_round as u64,
2340                    previous_output.clone(),
2341                    Default::default(),
2342                    dealer_set.clone(),
2343                    player_set.clone(),
2344                )?;
2345
2346                let mut players: Map<_, _> = round
2347                    .players
2348                    .iter()
2349                    .map(|&i| {
2350                        let sk = keys[i as usize].clone();
2351                        let pk = sk.public_key();
2352                        let player = Player::new(info.clone(), sk)?;
2353                        Ok((pk, player))
2354                    })
2355                    .collect::<anyhow::Result<Vec<_>>>()?
2356                    .try_into()
2357                    .unwrap();
2358                let mut acked_dealings: BTreeMap<
2359                    ed25519::PublicKey,
2360                    Vec<(ed25519::PublicKey, DealerPubMsg<V>, DealerPrivMsg)>,
2361                > = player_set
2362                    .iter()
2363                    .cloned()
2364                    .map(|pk| (pk, Vec::new()))
2365                    .collect();
2366                let mut crash_resume_by_dealer: BTreeMap<u32, Vec<u32>> = BTreeMap::new();
2367                for &(after_dealer, player) in &round.crash_resume_players {
2368                    crash_resume_by_dealer
2369                        .entry(after_dealer)
2370                        .or_default()
2371                        .push(player);
2372                }
2373                let mut resume_missing_msg_by_dealer: BTreeMap<u32, Vec<u32>> = BTreeMap::new();
2374                for &(after_dealer, missing_dealer) in &round.resume_missing_dealer_msg_fails {
2375                    resume_missing_msg_by_dealer
2376                        .entry(after_dealer)
2377                        .or_default()
2378                        .push(missing_dealer);
2379                }
2380
2381                // Run dealer protocol
2382                let mut dealer_logs = BTreeMap::new();
2383                for &i_dealer in &round.dealers {
2384                    let sk = keys[i_dealer as usize].clone();
2385                    let pk = sk.public_key();
2386                    let share = match (shares.get(&pk), round.replace_shares.contains(&i_dealer)) {
2387                        (None, _) => None,
2388                        (Some(s), false) => Some(s.clone()),
2389                        (Some(_), true) => Some(Share::new(
2390                            Participant::new(i_dealer),
2391                            Private::random(&mut rng),
2392                        )),
2393                    };
2394
2395                    // Start dealer (with potential modifications)
2396                    let (mut dealer, pub_msg, mut priv_msgs) =
2397                        if let Some(shift) = round.shift_degrees.get(&i_dealer) {
2398                            // Create dealer with shifted degree
2399                            let degree = u32::try_from(info.degree::<N3f1>() as i32 + shift.get())
2400                                .unwrap_or_default();
2401
2402                            // Manually create the dealer with adjusted polynomial
2403                            let share = info
2404                                .unwrap_or_random_share(
2405                                    &mut rng,
2406                                    share.map(|s| s.private.expose_unwrap()),
2407                                )
2408                                .expect("Failed to generate dealer share");
2409
2410                            let my_poly = Poly::new_with_constant(&mut rng, degree, share);
2411                            let priv_msgs = info
2412                                .players
2413                                .iter()
2414                                .map(|pk| {
2415                                    (
2416                                        pk.clone(),
2417                                        DealerPrivMsg::new(my_poly.eval_msm(
2418                                            &info.player_scalar(pk).expect("player should exist"),
2419                                            &Sequential,
2420                                        )),
2421                                    )
2422                                })
2423                                .collect::<Vec<_>>();
2424                            let results: Map<_, _> = priv_msgs
2425                                .iter()
2426                                .map(|(pk, pm)| (pk.clone(), AckOrReveal::Reveal(pm.clone())))
2427                                .try_collect()
2428                                .unwrap();
2429                            let commitment = Poly::commit(my_poly);
2430                            let pub_msg = DealerPubMsg { commitment };
2431                            let transcript = {
2432                                let t = transcript_for_round(&info);
2433                                transcript_for_ack(&t, &pk, &pub_msg)
2434                            };
2435                            let dealer = Dealer {
2436                                me: sk.clone(),
2437                                info: info.clone(),
2438                                pub_msg: pub_msg.clone(),
2439                                results,
2440                                transcript,
2441                            };
2442                            (dealer, pub_msg, priv_msgs)
2443                        } else {
2444                            Dealer::start::<N3f1>(&mut rng, info.clone(), sk.clone(), share)?
2445                        };
2446
2447                    // Apply BadShare perturbations
2448                    for (player, priv_msg) in &mut priv_msgs {
2449                        let player_key_idx = pk_to_key_idx[player];
2450                        if round.bad_shares.contains(&(i_dealer, player_key_idx)) {
2451                            *priv_msg = DealerPrivMsg::new(Scalar::random(&mut rng));
2452                        }
2453                    }
2454                    assert_eq!(priv_msgs.len(), players.len());
2455
2456                    // Process player acks
2457                    let mut num_reveals = players.len() as u32;
2458                    for (player_pk, priv_msg) in priv_msgs {
2459                        // Check priv msg encoding.
2460                        assert_eq!(priv_msg, ReadExt::read(&mut priv_msg.encode())?);
2461
2462                        let i_player = players
2463                            .index(&player_pk)
2464                            .ok_or_else(|| anyhow!("unknown player: {:?}", &player_pk))?;
2465                        let player_key_idx = pk_to_key_idx[&player_pk];
2466                        let player = &mut players.values_mut()[usize::from(i_player)];
2467                        let persisted = priv_msg.clone();
2468
2469                        let ack =
2470                            player.dealer_message::<N3f1>(pk.clone(), pub_msg.clone(), priv_msg);
2471                        assert_eq!(ack, ReadExt::read(&mut ack.encode())?);
2472                        if let Some(ack) = ack {
2473                            acked_dealings
2474                                .get_mut(&player_pk)
2475                                .expect("player should be present")
2476                                .push((pk.clone(), pub_msg.clone(), persisted));
2477                            let masks = round
2478                                .bad_player_sigs
2479                                .get(&(i_dealer, player_key_idx))
2480                                .cloned()
2481                                .unwrap_or_default();
2482                            let (modified, transcript) =
2483                                masks.transcript_for_player_ack(&info, &pk, &pub_msg)?;
2484                            assert_eq!(transcript.verify(&player_pk, &ack.sig), !modified);
2485
2486                            // Skip receiving ack if NoAck perturbation
2487                            if !round.no_acks.contains(&(i_dealer, player_key_idx)) {
2488                                dealer.receive_player_ack(player_pk, ack)?;
2489                                num_reveals -= 1;
2490                            }
2491                        } else {
2492                            assert!(
2493                                round.bad_shares.contains(&(i_dealer, player_key_idx))
2494                                    || round.bad(previous_successful_round.is_some(), i_dealer)
2495                            );
2496                        }
2497                    }
2498
2499                    // Finalize dealer
2500                    let signed_log = dealer.finalize::<N3f1>();
2501                    assert_eq!(
2502                        signed_log,
2503                        Read::read_cfg(&mut signed_log.encode(), &max_read_size)?
2504                    );
2505
2506                    // Check for BadDealerSig
2507                    let masks = round
2508                        .bad_dealer_sigs
2509                        .get(&i_dealer)
2510                        .cloned()
2511                        .unwrap_or_default();
2512                    let (modified, transcript) =
2513                        masks.transcript_for_signed_dealer_log(&info, &signed_log.log)?;
2514                    assert_eq!(transcript.verify(&pk, &signed_log.sig), !modified);
2515                    let (found_pk, mut log) = signed_log
2516                        .check(&info)
2517                        .ok_or_else(|| anyhow!("signed log should verify"))?;
2518                    assert_eq!(pk, found_pk);
2519                    // Apply BadReveal perturbations
2520                    match &mut log.results {
2521                        DealerResult::TooManyReveals => {
2522                            assert!(num_reveals > info.max_reveals::<N3f1>());
2523                        }
2524                        DealerResult::Ok(results) => {
2525                            assert_eq!(results.len(), players.len());
2526                            for &i_player in &round.players {
2527                                if !round.bad_reveals.contains(&(i_dealer, i_player)) {
2528                                    continue;
2529                                }
2530                                let player_pk = keys[i_player as usize].public_key();
2531                                *results
2532                                    .get_value_mut(&player_pk)
2533                                    .ok_or_else(|| anyhow!("unknown player: {:?}", &player_pk))? =
2534                                    AckOrReveal::Reveal(DealerPrivMsg::new(Scalar::random(
2535                                        &mut rng,
2536                                    )));
2537                            }
2538                        }
2539                    }
2540                    dealer_logs.insert(pk, log);
2541
2542                    // For selected checkpoints, omit a good dealer's private message and
2543                    // ensure resume reports corruption. Do not mutate player state.
2544                    for &missing_dealer in resume_missing_msg_by_dealer
2545                        .get(&i_dealer)
2546                        .into_iter()
2547                        .flatten()
2548                    {
2549                        assert!(
2550                            !round.bad(previous_successful_round.is_some(), missing_dealer),
2551                            "resume_missing_dealer_msg_fails requires dealer {missing_dealer} to be good"
2552                        );
2553                        let missing_pk = keys[missing_dealer as usize].public_key();
2554                        let missing_log = dealer_logs
2555                            .get(&missing_pk)
2556                            .unwrap_or_else(|| panic!("missing dealer log for {:?}", &missing_pk));
2557                        for &i_player in &round.players {
2558                            let player_pk = keys[i_player as usize].public_key();
2559                            let was_acked = missing_log.get_ack(&player_pk).is_some();
2560
2561                            let replay = acked_dealings
2562                                .get(&player_pk)
2563                                .cloned()
2564                                .expect("player should be present");
2565                            let replay_without = replay
2566                                .into_iter()
2567                                .filter(|(dealer, _, _)| dealer != &missing_pk);
2568                            let player_sk = keys[i_player as usize].clone();
2569                            let resumed = Player::resume::<N3f1>(
2570                                info.clone(),
2571                                player_sk,
2572                                &dealer_logs,
2573                                replay_without,
2574                            );
2575                            if was_acked {
2576                                assert!(
2577                                    matches!(resumed, Err(Error::MissingPlayerDealing)),
2578                                    "resume without dealer {missing_dealer} message should report MissingPlayerDealing for player {i_player}"
2579                                );
2580                            } else {
2581                                assert!(
2582                                    resumed.is_ok(),
2583                                    "resume without dealer {missing_dealer} message should succeed for unacked player {i_player}"
2584                                );
2585                            }
2586                        }
2587                    }
2588
2589                    // Crash/resume selected players after this dealer has finalized.
2590                    for &i_player in crash_resume_by_dealer.get(&i_dealer).into_iter().flatten() {
2591                        let player_pk = keys[i_player as usize].public_key();
2592                        let player_sk = keys[i_player as usize].clone();
2593                        let replay = acked_dealings
2594                            .get(&player_pk)
2595                            .cloned()
2596                            .expect("player should be present");
2597                        let (resumed, _) =
2598                            Player::resume::<N3f1>(info.clone(), player_sk, &dealer_logs, replay)
2599                                .expect("player resume perturbation should succeed");
2600                        *players
2601                            .get_value_mut(&player_pk)
2602                            .expect("player should be present") = resumed;
2603                    }
2604                }
2605
2606                // Make sure that bad dealers are not selected.
2607                let mut logs = Logs::<_, _, N3f1>::new(info.clone());
2608                for (dealer, log) in &dealer_logs {
2609                    logs.record(dealer.clone(), log.clone());
2610                }
2611                let selection = logs.clone().select::<ed25519::Batch>(&mut rng, &Sequential);
2612                if let Ok(ref selection) = selection {
2613                    let good_pks = selection
2614                        .1
2615                        .iter_pairs()
2616                        .map(|(pk, _)| pk.clone())
2617                        .collect::<BTreeSet<_>>();
2618                    for &i_dealer in &round.dealers {
2619                        if round.bad(previous_successful_round.is_some(), i_dealer) {
2620                            assert!(!good_pks.contains(&keys[i_dealer as usize].public_key()));
2621                        }
2622                    }
2623                }
2624                // Run observer
2625                let observe_result =
2626                    observe::<_, _, N3f1, ed25519::Batch>(&mut rng, logs.clone(), &Sequential);
2627                if round.expect_failure(previous_successful_round) {
2628                    assert!(
2629                        observe_result.is_err(),
2630                        "Round {i_round} should have failed but succeeded",
2631                    );
2632                    continue;
2633                }
2634                let observer_output = observe_result?;
2635                let selection = selection.expect("select should succeed if observe succeeded");
2636
2637                // Compute expected dealers: good dealers up to required_commitments
2638                // The select function iterates dealer_logs (BTreeMap) in public key order
2639                let required_commitments = info.required_commitments::<N3f1>() as usize;
2640                let expected_dealers: Set<ed25519::PublicKey> = dealer_set
2641                    .iter()
2642                    .filter(|pk| {
2643                        let i = keys.iter().position(|k| &k.public_key() == *pk).unwrap() as u32;
2644                        !round.bad(previous_successful_round.is_some(), i)
2645                    })
2646                    .take(required_commitments)
2647                    .cloned()
2648                    .try_collect()
2649                    .expect("dealers are unique");
2650                let expected_dealer_indices: BTreeSet<u32> = expected_dealers
2651                    .iter()
2652                    .filter_map(|pk| {
2653                        keys.iter()
2654                            .position(|k| &k.public_key() == pk)
2655                            .map(|i| i as u32)
2656                    })
2657                    .collect();
2658                assert_eq!(
2659                    observer_output.dealers(),
2660                    &expected_dealers,
2661                    "Output dealers should match expected good dealers"
2662                );
2663
2664                // Map selected dealers to their key indices (for later use)
2665                let selected_dealers: BTreeSet<u32> = selection
2666                    .1
2667                    .keys()
2668                    .iter()
2669                    .filter_map(|pk| {
2670                        keys.iter()
2671                            .position(|k| &k.public_key() == pk)
2672                            .map(|i| i as u32)
2673                    })
2674                    .collect();
2675                assert_eq!(
2676                    selected_dealers, expected_dealer_indices,
2677                    "Selection should match expected dealers"
2678                );
2679                let selected_players: Set<ed25519::PublicKey> = round
2680                    .players
2681                    .iter()
2682                    .map(|&i| keys[i as usize].public_key())
2683                    .try_collect()
2684                    .expect("players are unique");
2685                for &i_player in &round.finalize_missing_dealer_msg_fails {
2686                    let player_pk = keys[i_player as usize].public_key();
2687                    let player_sk = keys[i_player as usize].clone();
2688                    let mut tested = 0u32;
2689                    for &dealer_idx in &selected_dealers {
2690                        if round.bad(previous_successful_round.is_some(), dealer_idx) {
2691                            continue;
2692                        }
2693                        let dealer_pk = keys[dealer_idx as usize].public_key();
2694                        let dealer_log = dealer_logs
2695                            .get(&dealer_pk)
2696                            .unwrap_or_else(|| panic!("missing dealer log for {:?}", &dealer_pk));
2697                        if dealer_log.get_ack(&player_pk).is_none() {
2698                            continue;
2699                        }
2700                        let replay = acked_dealings
2701                            .get(&player_pk)
2702                            .cloned()
2703                            .expect("player should be present");
2704                        let replay_without = replay
2705                            .into_iter()
2706                            .filter(|(dealer, _, _)| dealer != &dealer_pk);
2707                        let resume_logs: BTreeMap<_, _> = dealer_logs
2708                            .iter()
2709                            .filter(|(dealer, _)| *dealer != &dealer_pk)
2710                            .map(|(dealer, log)| (dealer.clone(), log.clone()))
2711                            .collect();
2712                        let (resumed, _) = Player::resume::<N3f1>(
2713                            info.clone(),
2714                            player_sk.clone(),
2715                            &resume_logs,
2716                            replay_without,
2717                        )
2718                        .expect("resume should succeed with stale logs");
2719                        let finalize_res = resumed.finalize::<N3f1, ed25519::Batch>(
2720                            &mut rng,
2721                            logs.clone(),
2722                            &Sequential,
2723                        );
2724                        assert!(
2725                            matches!(finalize_res, Err(Error::MissingPlayerDealing)),
2726                            "finalize without dealer {dealer_idx} message should return MissingPlayerDealing for player {i_player}"
2727                        );
2728                        tested += 1;
2729                    }
2730                    assert!(
2731                        tested > 0,
2732                        "finalize_missing_dealer_msg_fails for player {i_player} tested no dealers"
2733                    );
2734                }
2735
2736                // Compute expected reveals
2737                //
2738                // Note: We use union of no_acks and bad_shares since each (dealer, player) pair
2739                // results in at most one reveal in the protocol, regardless of whether the player
2740                // didn't ack, got a bad share, or both.
2741                let mut expected_reveals: BTreeMap<ed25519::PublicKey, u32> = BTreeMap::new();
2742                for &(dealer_idx, player_key_idx) in round.no_acks.union(&round.bad_shares) {
2743                    if !selected_dealers.contains(&dealer_idx) {
2744                        continue;
2745                    }
2746                    let pk = keys[player_key_idx as usize].public_key();
2747                    if selected_players.position(&pk).is_none() {
2748                        continue;
2749                    }
2750                    *expected_reveals.entry(pk).or_insert(0) += 1;
2751                }
2752
2753                // Verify each player's revealed status
2754                let max_faults = selected_players.max_faults::<N3f1>();
2755                for player in player_set.iter() {
2756                    let expected = expected_reveals.get(player).copied().unwrap_or(0) > max_faults;
2757                    let actual = observer_output.revealed().position(player).is_some();
2758                    assert_eq!(expected, actual, "Unexpected outcome for player {player:?} (expected={expected}, actual={actual})");
2759                }
2760
2761                // Finalize each player
2762                for (player_pk, player) in players.into_iter() {
2763                    let (player_output, share) = player
2764                        .finalize::<N3f1, ed25519::Batch>(&mut rng, logs.clone(), &Sequential)
2765                        .expect("Player finalize should succeed");
2766
2767                    assert_eq!(
2768                        player_output, observer_output,
2769                        "Player output should match observer output"
2770                    );
2771
2772                    // Verify share matches public polynomial
2773                    let expected_public = observer_output
2774                        .public
2775                        .partial_public(share.index)
2776                        .expect("share index should be valid");
2777                    let actual_public = share.public::<V>();
2778                    assert_eq!(
2779                        expected_public, actual_public,
2780                        "Share should match public polynomial"
2781                    );
2782
2783                    shares.insert(player_pk.clone(), share);
2784                }
2785
2786                // Initialize or verify threshold public key
2787                let current_public = *observer_output.public().public();
2788                match threshold_public_key {
2789                    None => threshold_public_key = Some(current_public),
2790                    Some(tpk) => {
2791                        assert_eq!(
2792                            tpk, current_public,
2793                            "Public key should remain constant across reshares"
2794                        );
2795                    }
2796                }
2797
2798                // Generate and verify threshold signature
2799                let test_message = format!("test message round {i_round}").into_bytes();
2800                let namespace = b"test";
2801
2802                let mut partial_sigs = Vec::new();
2803                for &i_player in &round.players {
2804                    let share = &shares[&keys[i_player as usize].public_key()];
2805                    let partial_sig = threshold::sign_message::<V>(share, namespace, &test_message);
2806
2807                    threshold::verify_message::<V>(
2808                        &observer_output.public,
2809                        namespace,
2810                        &test_message,
2811                        &partial_sig,
2812                    )
2813                    .expect("Partial signature verification should succeed");
2814
2815                    partial_sigs.push(partial_sig);
2816                }
2817
2818                let threshold = observer_output.quorum::<N3f1>();
2819                let threshold_sig = threshold::recover::<V, _, N3f1>(
2820                    &observer_output.public,
2821                    &partial_sigs[0..threshold as usize],
2822                    &Sequential,
2823                )
2824                .expect("Should recover threshold signature");
2825
2826                // Verify against the saved public key
2827                ops::verify_message::<V>(
2828                    threshold_public_key.as_ref().unwrap(),
2829                    namespace,
2830                    &test_message,
2831                    &threshold_sig,
2832                )
2833                .expect("Threshold signature verification should succeed");
2834
2835                // Update state for next round
2836                previous_output = Some(observer_output);
2837            }
2838            Ok(())
2839        }
2840    }
2841
2842    #[cfg(feature = "arbitrary")]
2843    mod impl_arbitrary {
2844        use super::*;
2845        use arbitrary::{Arbitrary, Unstructured};
2846        use core::ops::ControlFlow;
2847
2848        const MAX_NUM_PARTICIPANTS: u32 = 20;
2849        const MAX_ROUNDS: u32 = 10;
2850
2851        fn arbitrary_masks<'a>(u: &mut Unstructured<'a>) -> arbitrary::Result<Masks> {
2852            Ok(Masks {
2853                info_summary: Arbitrary::arbitrary(u)?,
2854                dealer: Arbitrary::arbitrary(u)?,
2855                pub_msg: Arbitrary::arbitrary(u)?,
2856                log: Arbitrary::arbitrary(u)?,
2857            })
2858        }
2859
2860        /// Pick at most `num` elements at random from `data`, returning them.
2861        ///
2862        /// This needs mutable access to perform a shuffle.
2863        ///
2864        fn pick<'a, T>(
2865            u: &mut Unstructured<'a>,
2866            num: usize,
2867            mut data: Vec<T>,
2868        ) -> arbitrary::Result<Vec<T>> {
2869            let len = data.len();
2870            let num = num.min(len);
2871            // Invariant: 0..start is a random subset of data.
2872            for start in 0..num {
2873                data.swap(start, u.int_in_range(start..=len - 1)?);
2874            }
2875            data.truncate(num);
2876            Ok(data)
2877        }
2878
2879        fn arbitrary_round<'a>(
2880            u: &mut Unstructured<'a>,
2881            num_participants: u32,
2882            last_successful_players: Option<&Set<u32>>,
2883        ) -> arbitrary::Result<Round> {
2884            let dealers = if let Some(players) = last_successful_players {
2885                let to_pick = u.int_in_range(players.quorum::<N3f1>() as usize..=players.len())?;
2886                pick(u, to_pick, players.into_iter().copied().collect())?
2887            } else {
2888                let to_pick = u.int_in_range(1..=num_participants as usize)?;
2889                pick(u, to_pick, (0..num_participants).collect())?
2890            };
2891            let players = {
2892                let to_pick = u.int_in_range(1..=num_participants as usize)?;
2893                pick(u, to_pick, (0..num_participants).collect())?
2894            };
2895            let pairs = dealers
2896                .iter()
2897                .flat_map(|d| players.iter().map(|p| (*d, *p)))
2898                .collect::<Vec<_>>();
2899            let pick_pair_set = |u: &mut Unstructured<'a>| {
2900                let num = u.int_in_range(0..=pairs.len())?;
2901                if num == 0 {
2902                    return Ok(BTreeSet::new());
2903                }
2904                Ok(pick(u, num, pairs.clone())?.into_iter().collect())
2905            };
2906            let pick_dealer_set = |u: &mut Unstructured<'a>| {
2907                let num = u.int_in_range(0..=dealers.len())?;
2908                if num == 0 {
2909                    return Ok(BTreeSet::new());
2910                }
2911                Ok(pick(u, num, dealers.clone())?.into_iter().collect())
2912            };
2913            let round = Round {
2914                crash_resume_players: BTreeSet::new(),
2915                resume_missing_dealer_msg_fails: BTreeSet::new(),
2916                finalize_missing_dealer_msg_fails: BTreeSet::new(),
2917                no_acks: pick_pair_set(u)?,
2918                bad_shares: pick_pair_set(u)?,
2919                bad_player_sigs: {
2920                    let indices = pick_pair_set(u)?;
2921                    indices
2922                        .into_iter()
2923                        .map(|k| Ok((k, arbitrary_masks(u)?)))
2924                        .collect::<arbitrary::Result<_>>()?
2925                },
2926                bad_reveals: pick_pair_set(u)?,
2927                bad_dealer_sigs: {
2928                    let indices = pick_dealer_set(u)?;
2929                    indices
2930                        .into_iter()
2931                        .map(|k| Ok((k, arbitrary_masks(u)?)))
2932                        .collect::<arbitrary::Result<_>>()?
2933                },
2934                replace_shares: pick_dealer_set(u)?,
2935                shift_degrees: {
2936                    let indices = pick_dealer_set(u)?;
2937                    indices
2938                        .into_iter()
2939                        .map(|k| {
2940                            let expected = N3f1::quorum(players.len()) as i32 - 1;
2941                            let shift = u.int_in_range(1..=expected.max(1))?;
2942                            let shift = if bool::arbitrary(u)? { -shift } else { shift };
2943                            Ok((k, NonZeroI32::new(shift).expect("checked to not be zero")))
2944                        })
2945                        .collect::<arbitrary::Result<_>>()?
2946                },
2947                dealers,
2948                players,
2949            };
2950            Ok(round)
2951        }
2952
2953        impl<'a> Arbitrary<'a> for Plan {
2954            fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
2955                let num_participants = u.int_in_range(1..=MAX_NUM_PARTICIPANTS)?;
2956                let mut rounds = Vec::new();
2957                let mut last_successful_players: Option<Set<u32>> = None;
2958                u.arbitrary_loop(None, Some(MAX_ROUNDS), |u| {
2959                    let round =
2960                        arbitrary_round(u, num_participants, last_successful_players.as_ref())?;
2961                    if !round
2962                        .expect_failure(last_successful_players.as_ref().map(|x| x.len() as u32))
2963                    {
2964                        last_successful_players =
2965                            Some(round.players.iter().copied().try_collect().unwrap());
2966                    }
2967                    rounds.push(round);
2968                    Ok(ControlFlow::Continue(()))
2969                })?;
2970                let plan = Self {
2971                    num_participants: NZU32!(num_participants),
2972                    rounds,
2973                };
2974                plan.validate()
2975                    .map_err(|_| arbitrary::Error::IncorrectFormat)?;
2976                Ok(plan)
2977            }
2978        }
2979    }
2980}
2981
2982#[cfg(feature = "arbitrary")]
2983pub use test_plan::Plan as FuzzPlan;
2984
2985#[cfg(test)]
2986mod test {
2987    use super::{test_plan::*, *};
2988    use crate::{bls12381::primitives::variant::MinPk, ed25519};
2989    use anyhow::anyhow;
2990    use arbitrary::{Arbitrary, Unstructured};
2991    use commonware_invariants::minifuzz;
2992    use commonware_math::algebra::Random;
2993    use commonware_utils::{test_rng, test_rng_seeded, Faults, N3f1};
2994    use core::num::NonZeroI32;
2995
2996    const PRE_VERIFY_DEALERS: usize = 8;
2997    type PreVerifyLog = DealerLog<MinPk, ed25519::PublicKey>;
2998    type PreVerifyLogs = Logs<MinPk, ed25519::PublicKey, QuorumTwo>;
2999
3000    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
3001    struct QuorumTwo;
3002
3003    impl Faults for QuorumTwo {
3004        fn max_faults(n: impl num_traits::ToPrimitive) -> u32 {
3005            let n = n
3006                .to_u32()
3007                .expect("n must be a non-negative integer that fits in u32");
3008            assert!(n >= 2, "n must be at least 2");
3009            n - 2
3010        }
3011    }
3012
3013    struct PreVerifyDealer {
3014        key: ed25519::PublicKey,
3015        valid: PreVerifyLog,
3016        invalid: PreVerifyLog,
3017    }
3018
3019    struct PreVerifyFixture {
3020        info: Info<MinPk, ed25519::PublicKey>,
3021        wrong_info: Info<MinPk, ed25519::PublicKey>,
3022        dealers: Vec<PreVerifyDealer>,
3023    }
3024
3025    impl PreVerifyFixture {
3026        fn new() -> Self {
3027            fn pre_verify_test_keys() -> Vec<ed25519::PrivateKey> {
3028                (0..PRE_VERIFY_DEALERS as u64)
3029                    .map(ed25519::PrivateKey::from_seed)
3030                    .collect()
3031            }
3032
3033            fn pre_verify_test_info(
3034                keys: &[ed25519::PrivateKey],
3035                round: u64,
3036            ) -> Info<MinPk, ed25519::PublicKey> {
3037                let dealers: Set<_> = keys
3038                    .iter()
3039                    .map(|sk| sk.public_key())
3040                    .try_collect()
3041                    .expect("dealers must be unique");
3042                Info::<MinPk, _>::new::<QuorumTwo>(
3043                    b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3044                    round,
3045                    None,
3046                    Default::default(),
3047                    dealers.clone(),
3048                    dealers,
3049                )
3050                .expect("info must be valid")
3051            }
3052
3053            fn generate_dealer_log(
3054                info: &Info<MinPk, ed25519::PublicKey>,
3055                keys: &[ed25519::PrivateKey],
3056                dealer_index: usize,
3057                seed: u64,
3058            ) -> DealerLog<MinPk, ed25519::PublicKey> {
3059                let mut players: BTreeMap<_, _> = keys
3060                    .iter()
3061                    .cloned()
3062                    .map(|sk| {
3063                        let pk = sk.public_key();
3064                        (
3065                            pk,
3066                            Player::<MinPk, _>::new(info.clone(), sk)
3067                                .expect("player initialization must succeed"),
3068                        )
3069                    })
3070                    .collect();
3071
3072                let dealer_sk = keys[dealer_index].clone();
3073                let dealer_pk = dealer_sk.public_key();
3074                let mut rng = test_rng_seeded(seed);
3075                let (mut dealer, pub_msg, priv_msgs) =
3076                    Dealer::start::<QuorumTwo>(&mut rng, info.clone(), dealer_sk, None)
3077                        .expect("dealer initialization must succeed");
3078                for (player_pk, priv_msg) in priv_msgs {
3079                    let ack = players
3080                        .get_mut(&player_pk)
3081                        .expect("player should exist")
3082                        .dealer_message::<QuorumTwo>(dealer_pk.clone(), pub_msg.clone(), priv_msg)
3083                        .expect("dealer message must succeed");
3084                    dealer
3085                        .receive_player_ack(player_pk, ack)
3086                        .expect("ack handling must succeed");
3087                }
3088                dealer
3089                    .finalize::<QuorumTwo>()
3090                    .check(info)
3091                    .expect("signed dealer log must verify against its own info")
3092                    .1
3093            }
3094
3095            let keys = pre_verify_test_keys();
3096            let info = pre_verify_test_info(&keys, 0);
3097            let wrong_info = pre_verify_test_info(&keys, 1);
3098            let mut logs_by_key: BTreeMap<_, _> = keys
3099                .iter()
3100                .enumerate()
3101                .map(|(dealer_index, sk)| {
3102                    let key = sk.public_key();
3103                    let seed = dealer_index as u64;
3104                    let valid = generate_dealer_log(&info, &keys, dealer_index, seed);
3105                    let invalid = generate_dealer_log(&wrong_info, &keys, dealer_index, seed);
3106                    assert_eq!(
3107                        valid.pub_msg, invalid.pub_msg,
3108                        "wrong-info log generation should only change transcript-bound signatures"
3109                    );
3110                    (key, (valid, invalid))
3111                })
3112                .collect();
3113            let dealers = info
3114                .dealers
3115                .iter()
3116                .cloned()
3117                .map(|key| {
3118                    let (valid, invalid) = logs_by_key
3119                        .remove(&key)
3120                        .expect("fixture should include every dealer");
3121                    PreVerifyDealer {
3122                        key,
3123                        valid,
3124                        invalid,
3125                    }
3126                })
3127                .collect();
3128            Self {
3129                info,
3130                wrong_info,
3131                dealers,
3132            }
3133        }
3134
3135        fn required_commitments(&self) -> usize {
3136            self.info.required_commitments::<QuorumTwo>() as usize
3137        }
3138
3139        fn expected(&self, valid: &[bool]) -> Set<ed25519::PublicKey> {
3140            assert_eq!(
3141                valid.len(),
3142                self.dealers.len(),
3143                "fixture size should match case"
3144            );
3145            self.dealers
3146                .iter()
3147                .zip(valid.iter().copied())
3148                .filter(|(_, is_valid)| *is_valid)
3149                .take(self.required_commitments())
3150                .map(|(dealer, _)| dealer.key.clone())
3151                .try_collect()
3152                .expect("dealers must be unique")
3153        }
3154
3155        fn record(&self, logs: &mut PreVerifyLogs, dealer_index: usize, is_valid: bool) {
3156            let dealer = &self.dealers[dealer_index];
3157            let log = if is_valid {
3158                dealer.valid.clone()
3159            } else {
3160                dealer.invalid.clone()
3161            };
3162            logs.record(dealer.key.clone(), log);
3163        }
3164
3165        fn logs_for(
3166            &self,
3167            info: &Info<MinPk, ed25519::PublicKey>,
3168            valid: &[bool],
3169        ) -> PreVerifyLogs {
3170            assert_eq!(
3171                valid.len(),
3172                self.dealers.len(),
3173                "fixture size should match case"
3174            );
3175            let mut logs = PreVerifyLogs::new(info.clone());
3176            for (dealer_index, &is_valid) in valid.iter().enumerate() {
3177                self.record(&mut logs, dealer_index, is_valid);
3178            }
3179            logs
3180        }
3181    }
3182
3183    #[derive(Debug)]
3184    struct IncrementalPreVerifyCase {
3185        valid: [bool; PRE_VERIFY_DEALERS],
3186        batches: Vec<Vec<usize>>,
3187    }
3188
3189    impl<'a> Arbitrary<'a> for IncrementalPreVerifyCase {
3190        fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
3191            let mut valid = [false; PRE_VERIFY_DEALERS];
3192            for is_valid in &mut valid {
3193                *is_valid = u.arbitrary()?;
3194            }
3195
3196            let mut order: Vec<_> = (0..valid.len()).collect();
3197            for index in 0..valid.len() {
3198                let last = order.len() - 1;
3199                order.swap(index, u.int_in_range(index..=last)?);
3200            }
3201
3202            let mut batches = Vec::new();
3203            let mut start = 0;
3204            while start < order.len() {
3205                let batch_size = u.int_in_range(1..=order.len() - start)?;
3206                batches.push(order[start..start + batch_size].to_vec());
3207                start += batch_size;
3208            }
3209
3210            Ok(Self { valid, batches })
3211        }
3212    }
3213
3214    impl IncrementalPreVerifyCase {
3215        fn run(self, fixture: &PreVerifyFixture) -> arbitrary::Result<()> {
3216            let required_commitments = fixture.required_commitments();
3217            let expected = fixture.expected(&self.valid);
3218            let fresh = fixture.logs_for(&fixture.info, &self.valid);
3219
3220            let mut incremental = PreVerifyLogs::new(fixture.info.clone());
3221            let mut incremental_rng = test_rng();
3222            for batch in &self.batches {
3223                for &dealer_index in batch {
3224                    fixture.record(&mut incremental, dealer_index, self.valid[dealer_index]);
3225                }
3226                incremental.pre_verify::<ed25519::Batch>(&mut incremental_rng, &Sequential);
3227            }
3228
3229            let mut fresh_rng = test_rng();
3230            let fresh_selected = fresh
3231                .select::<ed25519::Batch>(&mut fresh_rng, &Sequential)
3232                .map(|(_, selection)| selection.keys().clone());
3233            let incremental_selected = incremental
3234                .select::<ed25519::Batch>(&mut incremental_rng, &Sequential)
3235                .map(|(_, selection)| selection.keys().clone());
3236
3237            match &fresh_selected {
3238                Ok(selected) => assert_eq!(
3239                    selected, &expected,
3240                    "all-at-once selection disagreed with validity mask: {:?}",
3241                    self
3242                ),
3243                Err(_) => assert!(
3244                    expected.len() < required_commitments,
3245                    "all-at-once selection failed despite quorum-sized expected set: {:?}",
3246                    self
3247                ),
3248            }
3249
3250            match (fresh_selected, incremental_selected) {
3251                (Err(_), Err(_)) => {}
3252                (Ok(fresh_selected), Ok(incremental_selected)) => assert_eq!(
3253                    incremental_selected, fresh_selected,
3254                    "incremental selection disagreed with all-at-once selection: {:?}",
3255                    self
3256                ),
3257                (Ok(fresh_selected), Err(err)) => panic!(
3258                    "incremental selection failed with {err:?} but all-at-once selected {fresh_selected:?}: {self:?}"
3259                ),
3260                (Err(err), Ok(incremental_selected)) => panic!(
3261                    "incremental selection returned {incremental_selected:?} but all-at-once failed with {err:?}: {self:?}"
3262                ),
3263            }
3264
3265            Ok(())
3266        }
3267    }
3268
3269    #[test]
3270    fn incremental_pre_verify_preserves_dealer_order() {
3271        let fixture = PreVerifyFixture::new();
3272        minifuzz::test(move |u| u.arbitrary::<IncrementalPreVerifyCase>()?.run(&fixture));
3273    }
3274
3275    #[test]
3276    fn logs_are_bound_to_constructor_info() {
3277        let fixture = PreVerifyFixture::new();
3278        let mut logs = fixture.logs_for(&fixture.info, &[false; PRE_VERIFY_DEALERS]);
3279        let mut wrong_logs = fixture.logs_for(&fixture.wrong_info, &[false; PRE_VERIFY_DEALERS]);
3280        let mut rng = test_rng();
3281
3282        logs.pre_verify::<ed25519::Batch>(&mut rng, &Sequential);
3283        wrong_logs.pre_verify::<ed25519::Batch>(&mut rng, &Sequential);
3284        assert!(
3285            wrong_logs
3286                .select::<ed25519::Batch>(&mut rng, &Sequential)
3287                .is_ok(),
3288            "control check: logs should verify when bound to the round they were created for"
3289        );
3290        assert!(
3291            matches!(
3292                logs.select::<ed25519::Batch>(&mut rng, &Sequential),
3293                Err(Error::DkgFailed)
3294            ),
3295            "logs bound to a different round must reject these transcript-bound signatures"
3296        );
3297    }
3298
3299    #[test]
3300    fn finalize_rejects_logs_bound_to_different_round() {
3301        let fixture = PreVerifyFixture::new();
3302        let player =
3303            Player::<MinPk, _>::new(fixture.info.clone(), ed25519::PrivateKey::from_seed(0))
3304                .expect("player initialization must succeed");
3305        let wrong_logs = fixture.logs_for(&fixture.wrong_info, &[false; PRE_VERIFY_DEALERS]);
3306
3307        let result =
3308            player.finalize::<QuorumTwo, ed25519::Batch>(&mut test_rng(), wrong_logs, &Sequential);
3309
3310        assert!(
3311            matches!(result, Err(Error::MismatchedLogs)),
3312            "finalize should reject logs bound to a different round"
3313        );
3314    }
3315
3316    #[test]
3317    fn single_round() -> anyhow::Result<()> {
3318        Plan::new(NZU32!(4))
3319            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
3320            .run::<MinPk>(0)
3321    }
3322
3323    #[test]
3324    fn multiple_rounds() -> anyhow::Result<()> {
3325        Plan::new(NZU32!(4))
3326            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
3327            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
3328            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
3329            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
3330            .run::<MinPk>(0)
3331    }
3332
3333    #[test]
3334    fn player_crash_resume_after_dealer() -> anyhow::Result<()> {
3335        Plan::new(NZU32!(4))
3336            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).crash_resume_player(1, 2))
3337            .run::<MinPk>(0)
3338    }
3339
3340    #[test]
3341    fn resume_missing_good_dealer_message_fails_after_checkpoint() -> anyhow::Result<()> {
3342        Plan::new(NZU32!(4))
3343            .with(
3344                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
3345                    .resume_missing_dealer_msg_fails(2, 1),
3346            )
3347            .run::<MinPk>(0)
3348    }
3349
3350    #[test]
3351    fn resume_missing_good_dealer_message_skips_unacked_players() -> anyhow::Result<()> {
3352        Plan::new(NZU32!(4))
3353            .with(
3354                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
3355                    .no_ack(1, 0)
3356                    .resume_missing_dealer_msg_fails(2, 1),
3357            )
3358            .run::<MinPk>(0)
3359    }
3360
3361    #[test]
3362    fn finalize_fails_after_resume_without_good_dealer_message() -> anyhow::Result<()> {
3363        Plan::new(NZU32!(4))
3364            .with(
3365                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
3366                    .no_ack(0, 1)
3367                    .finalize_missing_dealer_msg_fails(0),
3368            )
3369            .run::<MinPk>(0)
3370    }
3371
3372    #[test]
3373    fn invalid_checkpoint_configs_fail_validation() {
3374        assert!(Plan::new(NZU32!(4))
3375            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).crash_resume_player(4, 2))
3376            .validate()
3377            .is_err());
3378        assert!(Plan::new(NZU32!(4))
3379            .with(
3380                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
3381                    .resume_missing_dealer_msg_fails(1, 2),
3382            )
3383            .validate()
3384            .is_err());
3385        assert!(Plan::new(NZU32!(4))
3386            .with(
3387                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
3388                    .bad_reveal(1, 0)
3389                    .resume_missing_dealer_msg_fails(2, 1),
3390            )
3391            .validate()
3392            .is_err());
3393    }
3394
3395    #[test]
3396    fn changing_committee() -> anyhow::Result<()> {
3397        Plan::new(NonZeroU32::new(5).unwrap())
3398            .with(Round::new(vec![0, 1, 2], vec![1, 2, 3]))
3399            .with(Round::new(vec![1, 2, 3], vec![2, 3, 4]))
3400            .with(Round::new(vec![2, 3, 4], vec![3, 4, 0]))
3401            .with(Round::new(vec![3, 4, 0], vec![4, 0, 1]))
3402            .run::<MinPk>(0)
3403    }
3404
3405    #[test]
3406    fn missing_ack() -> anyhow::Result<()> {
3407        // With 4 players, max_faults = 1, so 1 missing ack per dealer is OK
3408        Plan::new(NonZeroU32::new(4).unwrap())
3409            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 0))
3410            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 1))
3411            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 2))
3412            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 3))
3413            .run::<MinPk>(0)
3414    }
3415
3416    #[test]
3417    fn increasing_decreasing_committee() -> anyhow::Result<()> {
3418        Plan::new(NonZeroU32::new(5).unwrap())
3419            .with(Round::new(vec![0, 1], vec![0, 1, 2]))
3420            .with(Round::new(vec![0, 1, 2], vec![0, 1, 2, 3]))
3421            .with(Round::new(vec![0, 1, 2], vec![0, 1]))
3422            .with(Round::new(vec![0, 1], vec![0, 1, 2, 3, 4]))
3423            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1]))
3424            .run::<MinPk>(0)
3425    }
3426
3427    #[test]
3428    fn bad_reveal_fails() -> anyhow::Result<()> {
3429        Plan::new(NonZeroU32::new(4).unwrap())
3430            .with(Round::new(vec![0], vec![0, 1, 2, 3]).bad_reveal(0, 1))
3431            .run::<MinPk>(0)
3432    }
3433
3434    #[test]
3435    fn bad_share() -> anyhow::Result<()> {
3436        Plan::new(NonZeroU32::new(4).unwrap())
3437            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).bad_share(0, 1))
3438            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).bad_share(0, 2))
3439            .run::<MinPk>(0)
3440    }
3441
3442    #[test]
3443    fn shift_degree_fails() -> anyhow::Result<()> {
3444        Plan::new(NonZeroU32::new(4).unwrap())
3445            .with(Round::new(vec![0], vec![0, 1, 2, 3]).shift_degree(
3446                0,
3447                NonZeroI32::new(1).ok_or_else(|| anyhow!("invalid NZI32"))?,
3448            ))
3449            .run::<MinPk>(0)
3450    }
3451
3452    #[test]
3453    fn replace_share_fails() -> anyhow::Result<()> {
3454        Plan::new(NonZeroU32::new(4).unwrap())
3455            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
3456            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).replace_share(0))
3457            .run::<MinPk>(0)
3458    }
3459
3460    #[test]
3461    fn too_many_reveals_dealer() -> anyhow::Result<()> {
3462        Plan::new(NonZeroU32::new(4).unwrap())
3463            .with(
3464                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
3465                    .no_ack(0, 0)
3466                    .no_ack(0, 1),
3467            )
3468            .run::<MinPk>(0)
3469    }
3470
3471    #[test]
3472    fn too_many_reveals_player() -> anyhow::Result<()> {
3473        Plan::new(NonZeroU32::new(4).unwrap())
3474            .with(
3475                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
3476                    .no_ack(0, 0)
3477                    .no_ack(1, 0)
3478                    .no_ack(3, 0),
3479            )
3480            .run::<MinPk>(0)
3481    }
3482
3483    #[test]
3484    fn bad_sigs() -> anyhow::Result<()> {
3485        Plan::new(NonZeroU32::new(4).unwrap())
3486            .with(
3487                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
3488                    .bad_dealer_sig(
3489                        0,
3490                        Masks {
3491                            log: vec![0xFF; 8],
3492                            ..Default::default()
3493                        },
3494                    )
3495                    .bad_player_sig(
3496                        0,
3497                        1,
3498                        Masks {
3499                            pub_msg: vec![0xFF; 8],
3500                            ..Default::default()
3501                        },
3502                    ),
3503            )
3504            .run::<MinPk>(0)
3505    }
3506
3507    #[test]
3508    fn issue_2745_regression() -> anyhow::Result<()> {
3509        Plan::new(NonZeroU32::new(6).unwrap())
3510            .with(
3511                Round::new(vec![0], vec![5, 1, 3, 0, 4])
3512                    .no_ack(0, 5)
3513                    .bad_share(0, 5),
3514            )
3515            .with(Round::new(vec![0, 1, 3, 4], vec![0]))
3516            .with(Round::new(vec![0], vec![0]))
3517            .run::<MinPk>(0)
3518    }
3519
3520    #[test]
3521    fn signed_dealer_log_commitment() -> Result<(), Error> {
3522        let sk = ed25519::PrivateKey::from_seed(0);
3523        let pk = sk.public_key();
3524        let info = Info::<MinPk, _>::new::<N3f1>(
3525            b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3526            0,
3527            None,
3528            Default::default(),
3529            vec![sk.public_key()].try_into().unwrap(),
3530            vec![sk.public_key()].try_into().unwrap(),
3531        )?;
3532        let mut log0 = {
3533            let (dealer, _, _) =
3534                Dealer::start::<N3f1>(&mut test_rng(), info.clone(), sk.clone(), None)?;
3535            dealer.finalize::<N3f1>()
3536        };
3537        let mut log1 = {
3538            let (mut dealer, pub_msg, priv_msgs) =
3539                Dealer::start::<N3f1>(&mut test_rng(), info.clone(), sk.clone(), None)?;
3540            let mut player = Player::new(info.clone(), sk)?;
3541            let ack = player
3542                .dealer_message::<N3f1>(pk.clone(), pub_msg, priv_msgs[0].1.clone())
3543                .unwrap();
3544            dealer.receive_player_ack(pk, ack)?;
3545            dealer.finalize::<N3f1>()
3546        };
3547        std::mem::swap(&mut log0.log, &mut log1.log);
3548        assert!(log0.check(&info).is_none());
3549        assert!(log1.check(&info).is_none());
3550
3551        Ok(())
3552    }
3553
3554    #[test]
3555    fn info_with_different_mode_is_not_equal() -> Result<(), Error> {
3556        let sk = ed25519::PrivateKey::from_seed(0);
3557        let pk = sk.public_key();
3558        let dealers: Set<ed25519::PublicKey> = vec![pk.clone()].try_into().unwrap();
3559        let players: Set<ed25519::PublicKey> = vec![pk].try_into().unwrap();
3560
3561        let default_mode_info = Info::<MinPk, _>::new::<N3f1>(
3562            b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3563            0,
3564            None,
3565            Mode::default(),
3566            dealers.clone(),
3567            players.clone(),
3568        )?;
3569        let roots_of_unity_mode_info = Info::<MinPk, _>::new::<N3f1>(
3570            b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3571            0,
3572            None,
3573            Mode::RootsOfUnity,
3574            dealers,
3575            players,
3576        )?;
3577
3578        assert_ne!(default_mode_info, roots_of_unity_mode_info);
3579        Ok(())
3580    }
3581
3582    #[test]
3583    fn resume_ignores_invalid_logged_ack_signature() -> Result<(), Error> {
3584        let dealer_sk = ed25519::PrivateKey::from_seed(11);
3585        let dealer_pk = dealer_sk.public_key();
3586        let player_sk = ed25519::PrivateKey::from_seed(22);
3587        let player_pk = player_sk.public_key();
3588        let dealers: Set<ed25519::PublicKey> = vec![dealer_pk.clone()].try_into().unwrap();
3589        let players: Set<ed25519::PublicKey> = vec![player_pk.clone()].try_into().unwrap();
3590        let info = Info::<MinPk, _>::new::<N3f1>(
3591            b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3592            0,
3593            None,
3594            Default::default(),
3595            dealers.clone(),
3596            players.clone(),
3597        )?;
3598        let wrong_round_info = Info::<MinPk, _>::new::<N3f1>(
3599            b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3600            1,
3601            None,
3602            Default::default(),
3603            dealers,
3604            players,
3605        )?;
3606        let (_, pub_msg, _) =
3607            Dealer::start::<N3f1>(&mut test_rng(), info.clone(), dealer_sk, None)?;
3608        let bad_ack = PlayerAck {
3609            sig: transcript_for_ack(
3610                &transcript_for_round(&wrong_round_info),
3611                &dealer_pk,
3612                &pub_msg,
3613            )
3614            .sign(&player_sk),
3615        };
3616        let results: Map<_, _> = vec![(player_pk, AckOrReveal::Ack(bad_ack))]
3617            .into_iter()
3618            .try_collect()
3619            .unwrap();
3620        let mut logs = BTreeMap::new();
3621        logs.insert(
3622            dealer_pk,
3623            DealerLog {
3624                pub_msg,
3625                results: DealerResult::Ok(results),
3626            },
3627        );
3628
3629        let resumed = Player::resume::<N3f1>(info, player_sk, &logs, []);
3630        assert!(resumed.is_ok());
3631        let (_, acks) = resumed.unwrap();
3632        assert!(acks.is_empty());
3633
3634        Ok(())
3635    }
3636
3637    #[test]
3638    fn test_dealer_priv_msg_redacted() {
3639        let mut rng = test_rng();
3640        let msg = DealerPrivMsg::new(Scalar::random(&mut rng));
3641        let debug = format!("{:?}", msg);
3642        assert!(debug.contains("REDACTED"));
3643    }
3644
3645    #[cfg(feature = "arbitrary")]
3646    mod conformance {
3647        use super::*;
3648        use commonware_codec::conformance::CodecConformance;
3649
3650        commonware_conformance::conformance_tests! {
3651            CodecConformance<Output<MinPk, ed25519::PublicKey>>,
3652            CodecConformance<DealerPubMsg<MinPk>>,
3653            CodecConformance<DealerPrivMsg>,
3654            CodecConformance<PlayerAck<ed25519::PublicKey>>,
3655            CodecConformance<AckOrReveal<ed25519::PublicKey>>,
3656            CodecConformance<DealerResult<ed25519::PublicKey>>,
3657            CodecConformance<DealerLog<MinPk, ed25519::PublicKey>>,
3658            CodecConformance<SignedDealerLog<MinPk, ed25519::PrivateKey>>,
3659        }
3660    }
3661}