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, Player, Info, 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 dealer_logs = BTreeMap::new();
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//!         dealer_logs.insert(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>(
284//!       dealer_logs.clone(),
285//!       &commonware_parallel::Sequential,
286//!     )?;
287//!     println!("Player {:?} got share at index {}", player_pk, share.index);
288//!     player_shares.insert(player_pk, share);
289//! }
290//!
291//! // Step 5: Observer can also compute the public output
292//! let observer_output = observe::<MinSig, ed25519::PublicKey, N3f1>(
293//!     info,
294//!     dealer_logs,
295//!     &commonware_parallel::Sequential,
296//! )?;
297//! println!("DKG completed with threshold {}", observer_output.quorum::<N3f1>());
298//! # Ok(())
299//! # }
300//! ```
301//!
302//! For a complete example with resharing, see [commonware-reshare](https://docs.rs/commonware-reshare).
303
304use super::primitives::group::{Private, Share};
305use crate::{
306    bls12381::primitives::{
307        group::Scalar,
308        sharing::{Mode, ModeVersion, Sharing},
309        variant::Variant,
310    },
311    transcript::{Summary, Transcript},
312    PublicKey, Secret, Signer,
313};
314use commonware_codec::{Encode, EncodeSize, RangeCfg, Read, ReadExt, Write};
315use commonware_math::{
316    algebra::{Additive, CryptoGroup, Random},
317    poly::{Interpolator, Poly},
318};
319use commonware_parallel::{Sequential, Strategy};
320#[cfg(feature = "arbitrary")]
321use commonware_utils::N3f1;
322use commonware_utils::{
323    ordered::{Map, Quorum, Set},
324    Faults, Participant, TryCollect, NZU32,
325};
326use core::num::NonZeroU32;
327use rand_core::CryptoRngCore;
328use std::collections::BTreeMap;
329use thiserror::Error;
330
331const NAMESPACE: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG";
332const SIG_ACK: &[u8] = b"ack";
333const SIG_LOG: &[u8] = b"log";
334
335/// The error type for the DKG protocol.
336///
337/// The only error which can happen through no fault of your own is
338/// [`Error::DkgFailed`].
339///
340/// [`Error::MissingPlayerDealing`] happens through mistakes or faults when the state
341/// of a player is restored after a crash.
342///
343/// The other errors are due to issues with configuration.
344#[derive(Debug, Error)]
345pub enum Error {
346    #[error("missing dealer's share from the previous round")]
347    MissingDealerShare,
348    #[error("player is not present in the list of players")]
349    UnknownPlayer,
350    #[error("dealer is not present in the previous list of players")]
351    UnknownDealer(String),
352    #[error("invalid number of dealers: {0}")]
353    NumDealers(usize),
354    #[error("invalid number of players: {0}")]
355    NumPlayers(usize),
356    #[error("dkg failed for some reason")]
357    DkgFailed,
358    /// The player's state is missing a dealing it should have.
359    ///
360    /// This error is emitted when the player is missing dealings that it should
361    /// otherwise have based on the flow of the protocol. This can only happen if
362    /// the code in this module is used in a stateful way, restoring the
363    /// state of the player from saved information. If this state is corrupted
364    /// on disk, or missing, then this error can happen.
365    #[error("missing player's dealing")]
366    MissingPlayerDealing,
367}
368
369/// The output of a successful DKG.
370#[derive(Debug, Clone, PartialEq, Eq)]
371pub struct Output<V: Variant, P> {
372    summary: Summary,
373    public: Sharing<V>,
374    dealers: Set<P>,
375    players: Set<P>,
376    revealed: Set<P>,
377}
378
379impl<V: Variant, P: Ord> Output<V, P> {
380    fn share_commitment(&self, player: &P) -> Option<V::Public> {
381        self.public.partial_public(self.players.index(player)?).ok()
382    }
383
384    /// Return the quorum, i.e. the number of players needed to reconstruct the key.
385    pub fn quorum<M: Faults>(&self) -> u32 {
386        self.players.quorum::<M>()
387    }
388
389    /// Get the public polynomial associated with this output.
390    ///
391    /// This is useful for verifying partial signatures, with [crate::bls12381::primitives::ops::threshold::verify_message].
392    pub const fn public(&self) -> &Sharing<V> {
393        &self.public
394    }
395
396    /// Return the dealers who were selected in this round of the DKG.
397    pub const fn dealers(&self) -> &Set<P> {
398        &self.dealers
399    }
400
401    /// Return the players who participated in this round of the DKG, and should have shares.
402    pub const fn players(&self) -> &Set<P> {
403        &self.players
404    }
405
406    /// Return the set of players whose shares may have been revealed.
407    ///
408    /// These are players who had more than `max_faults` reveals.
409    pub const fn revealed(&self) -> &Set<P> {
410        &self.revealed
411    }
412}
413
414impl<V: Variant, P: PublicKey> EncodeSize for Output<V, P> {
415    fn encode_size(&self) -> usize {
416        self.summary.encode_size()
417            + self.public.encode_size()
418            + self.dealers.encode_size()
419            + self.players.encode_size()
420            + self.revealed.encode_size()
421    }
422}
423
424impl<V: Variant, P: PublicKey> Write for Output<V, P> {
425    fn write(&self, buf: &mut impl bytes::BufMut) {
426        self.summary.write(buf);
427        self.public.write(buf);
428        self.dealers.write(buf);
429        self.players.write(buf);
430        self.revealed.write(buf);
431    }
432}
433
434impl<V: Variant, P: PublicKey> Read for Output<V, P> {
435    type Cfg = (NonZeroU32, ModeVersion);
436
437    fn read_cfg(
438        buf: &mut impl bytes::Buf,
439        (max_participants, max_supported_mode): &Self::Cfg,
440    ) -> Result<Self, commonware_codec::Error> {
441        let max_participants_usize = max_participants.get() as usize;
442        Ok(Self {
443            summary: ReadExt::read(buf)?,
444            public: Read::read_cfg(buf, &(*max_participants, *max_supported_mode))?,
445            dealers: Read::read_cfg(buf, &(RangeCfg::new(1..=max_participants_usize), ()))?, // at least one dealer must be part of a dealing
446            players: Read::read_cfg(buf, &(RangeCfg::new(1..=max_participants_usize), ()))?, // at least one player must be part of a dealing
447            revealed: Read::read_cfg(buf, &(RangeCfg::new(0..=max_participants_usize), ()))?, // there may not be any reveals
448        })
449    }
450}
451
452#[cfg(feature = "arbitrary")]
453impl<P: PublicKey, V: Variant> arbitrary::Arbitrary<'_> for Output<V, P>
454where
455    P: for<'a> arbitrary::Arbitrary<'a> + Ord,
456    V::Public: for<'a> arbitrary::Arbitrary<'a>,
457{
458    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
459        let summary = u.arbitrary()?;
460        let public: Sharing<V> = u.arbitrary()?;
461        let total = public.total().get() as usize;
462
463        let num_dealers = u.int_in_range(1..=total * 2)?;
464        let dealers = Set::try_from(
465            u.arbitrary_iter::<P>()?
466                .take(num_dealers)
467                .collect::<Result<Vec<_>, _>>()?,
468        )
469        .map_err(|_| arbitrary::Error::IncorrectFormat)?;
470
471        let players = Set::try_from(
472            u.arbitrary_iter::<P>()?
473                .take(total)
474                .collect::<Result<Vec<_>, _>>()?,
475        )
476        .map_err(|_| arbitrary::Error::IncorrectFormat)?;
477
478        let max_revealed = N3f1::max_faults(total) as usize;
479        let revealed = Set::from_iter_dedup(
480            players
481                .iter()
482                .filter(|_| u.arbitrary::<bool>().unwrap_or(false))
483                .take(max_revealed)
484                .cloned(),
485        );
486
487        Ok(Self {
488            summary,
489            public,
490            dealers,
491            players,
492            revealed,
493        })
494    }
495}
496
497/// Information about the current round of the DKG.
498///
499/// This is used to bind signatures to the current round, and to provide the
500/// information that dealers, players, and observers need to perform their actions.
501#[derive(Debug, Clone)]
502pub struct Info<V: Variant, P: PublicKey> {
503    summary: Summary,
504    round: u64,
505    previous: Option<Output<V, P>>,
506    mode: Mode,
507    dealers: Set<P>,
508    players: Set<P>,
509}
510
511impl<V: Variant, P: PublicKey> PartialEq for Info<V, P> {
512    fn eq(&self, other: &Self) -> bool {
513        self.summary == other.summary
514    }
515}
516
517impl<V: Variant, P: PublicKey> Info<V, P> {
518    /// Figure out what the dealer share should be.
519    ///
520    /// If there's no previous round, we need a random value, hence `rng`.
521    ///
522    /// However, if there is a previous round, we expect a share, hence `Result`.
523    fn unwrap_or_random_share(
524        &self,
525        mut rng: impl CryptoRngCore,
526        share: Option<Scalar>,
527    ) -> Result<Scalar, Error> {
528        let out = match (self.previous.as_ref(), share) {
529            (None, None) => Scalar::random(&mut rng),
530            (_, Some(x)) => x,
531            (Some(_), None) => return Err(Error::MissingDealerShare),
532        };
533        Ok(out)
534    }
535
536    const fn num_players(&self) -> NonZeroU32 {
537        // Will not panic because we check that the number of players is non-empty in `new`
538        NZU32!(self.players.len() as u32)
539    }
540
541    fn degree<M: Faults>(&self) -> u32 {
542        self.players.quorum::<M>().saturating_sub(1)
543    }
544
545    fn required_commitments<M: Faults>(&self) -> u32 {
546        let dealer_quorum = self.dealers.quorum::<M>();
547        let prev_quorum = self
548            .previous
549            .as_ref()
550            .map(Output::quorum::<M>)
551            .unwrap_or(u32::MIN);
552        dealer_quorum.max(prev_quorum)
553    }
554
555    fn max_reveals<M: Faults>(&self) -> u32 {
556        self.players.max_faults::<M>()
557    }
558
559    fn player_index(&self, player: &P) -> Result<Participant, Error> {
560        self.players.index(player).ok_or(Error::UnknownPlayer)
561    }
562
563    fn dealer_index(&self, dealer: &P) -> Result<Participant, Error> {
564        self.dealers
565            .index(dealer)
566            .ok_or(Error::UnknownDealer(format!("{dealer:?}")))
567    }
568
569    fn player_scalar(&self, player: &P) -> Result<Scalar, Error> {
570        Ok(self
571            .mode
572            .scalar(self.num_players(), self.player_index(player)?)
573            .expect("player index should be < num_players"))
574    }
575
576    #[must_use]
577    fn check_dealer_pub_msg<M: Faults>(&self, dealer: &P, pub_msg: &DealerPubMsg<V>) -> bool {
578        if self.degree::<M>() != pub_msg.commitment.degree_exact() {
579            return false;
580        }
581        if let Some(previous) = self.previous.as_ref() {
582            let Some(share_commitment) = previous.share_commitment(dealer) else {
583                return false;
584            };
585            if *pub_msg.commitment.constant() != share_commitment {
586                return false;
587            }
588        }
589        true
590    }
591
592    #[must_use]
593    fn check_dealer_priv_msg(
594        &self,
595        player: &P,
596        pub_msg: &DealerPubMsg<V>,
597        priv_msg: &DealerPrivMsg,
598    ) -> bool {
599        let Ok(scalar) = self.player_scalar(player) else {
600            return false;
601        };
602        let expected = pub_msg.commitment.eval_msm(&scalar, &Sequential);
603        priv_msg
604            .share
605            .expose(|share| expected == V::Public::generator() * share)
606    }
607}
608
609impl<V: Variant, P: PublicKey> Info<V, P> {
610    /// Create a new [`Info`].
611    ///
612    /// `namespace` must be provided to isolate different applications
613    /// performing DKGs from each other.
614    /// `round` should be a counter, always incrementing, even for failed DKGs.
615    /// `previous` should be the result of the previous successful DKG.
616    /// `dealers` should be the list of public keys for the dealers. This MUST
617    /// be a subset of the previous round's players.
618    /// `players` should be the list of public keys for the players.
619    pub fn new<M: Faults>(
620        namespace: &[u8],
621        round: u64,
622        previous: Option<Output<V, P>>,
623        mode: Mode,
624        dealers: Set<P>,
625        players: Set<P>,
626    ) -> Result<Self, Error> {
627        let participant_range = 1..u32::MAX as usize;
628        if !participant_range.contains(&dealers.len()) {
629            return Err(Error::NumDealers(dealers.len()));
630        }
631        if !participant_range.contains(&players.len()) {
632            return Err(Error::NumPlayers(players.len()));
633        }
634        if let Some(previous) = previous.as_ref() {
635            if let Some(unknown) = dealers
636                .iter()
637                .find(|d| previous.players.position(d).is_none())
638            {
639                return Err(Error::UnknownDealer(format!("{unknown:?}")));
640            }
641            if dealers.len() < previous.quorum::<M>() as usize {
642                return Err(Error::NumDealers(dealers.len()));
643            }
644        }
645        let summary = {
646            let mut transcript = Transcript::new(NAMESPACE);
647            transcript
648                .commit(namespace)
649                .commit(round.encode())
650                .commit(previous.encode())
651                .commit(dealers.encode())
652                .commit(players.encode());
653            // We want backwards compatibility with the default mode, which wasn't
654            // committed. The absence of the mode is thus treated as an implicit
655            // default in the transcript, so this is sound.
656            if mode != Mode::default() {
657                transcript.commit([mode as u8].as_slice());
658            }
659            transcript.summarize()
660        };
661        Ok(Self {
662            summary,
663            round,
664            previous,
665            mode,
666            dealers,
667            players,
668        })
669    }
670
671    /// Return the round number for this round.
672    ///
673    /// Round numbers should increase sequentially.
674    pub const fn round(&self) -> u64 {
675        self.round
676    }
677}
678
679#[derive(Clone, Debug)]
680pub struct DealerPubMsg<V: Variant> {
681    commitment: Poly<V::Public>,
682}
683
684impl<V: Variant> PartialEq for DealerPubMsg<V> {
685    fn eq(&self, other: &Self) -> bool {
686        self.commitment == other.commitment
687    }
688}
689
690impl<V: Variant> Eq for DealerPubMsg<V> {}
691
692impl<V: Variant> EncodeSize for DealerPubMsg<V> {
693    fn encode_size(&self) -> usize {
694        self.commitment.encode_size()
695    }
696}
697
698impl<V: Variant> Write for DealerPubMsg<V> {
699    fn write(&self, buf: &mut impl bytes::BufMut) {
700        self.commitment.write(buf);
701    }
702}
703
704impl<V: Variant> Read for DealerPubMsg<V> {
705    type Cfg = NonZeroU32;
706
707    fn read_cfg(
708        buf: &mut impl bytes::Buf,
709        &max_size: &Self::Cfg,
710    ) -> Result<Self, commonware_codec::Error> {
711        Ok(Self {
712            commitment: Read::read_cfg(buf, &(RangeCfg::from(NZU32!(1)..=max_size), ()))?,
713        })
714    }
715}
716
717#[cfg(feature = "arbitrary")]
718impl<V: Variant> arbitrary::Arbitrary<'_> for DealerPubMsg<V>
719where
720    V::Public: for<'a> arbitrary::Arbitrary<'a>,
721{
722    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
723        let commitment = u.arbitrary()?;
724        Ok(Self { commitment })
725    }
726}
727
728#[derive(Clone, Debug, PartialEq, Eq)]
729pub struct DealerPrivMsg {
730    share: Secret<Scalar>,
731}
732
733impl DealerPrivMsg {
734    /// Creates a new `DealerPrivMsg` with the given share.
735    pub const fn new(share: Scalar) -> Self {
736        Self {
737            share: Secret::new(share),
738        }
739    }
740}
741
742impl EncodeSize for DealerPrivMsg {
743    fn encode_size(&self) -> usize {
744        self.share.expose(|share| share.encode_size())
745    }
746}
747
748impl Write for DealerPrivMsg {
749    fn write(&self, buf: &mut impl bytes::BufMut) {
750        self.share.expose(|share| share.write(buf));
751    }
752}
753
754impl Read for DealerPrivMsg {
755    type Cfg = ();
756
757    fn read_cfg(
758        buf: &mut impl bytes::Buf,
759        _cfg: &Self::Cfg,
760    ) -> Result<Self, commonware_codec::Error> {
761        Ok(Self::new(ReadExt::read(buf)?))
762    }
763}
764
765#[cfg(feature = "arbitrary")]
766impl arbitrary::Arbitrary<'_> for DealerPrivMsg {
767    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
768        Ok(Self::new(u.arbitrary()?))
769    }
770}
771
772#[derive(Clone, Debug)]
773pub struct PlayerAck<P: PublicKey> {
774    sig: P::Signature,
775}
776
777impl<P: PublicKey> PartialEq for PlayerAck<P> {
778    fn eq(&self, other: &Self) -> bool {
779        self.sig == other.sig
780    }
781}
782
783impl<P: PublicKey> EncodeSize for PlayerAck<P> {
784    fn encode_size(&self) -> usize {
785        self.sig.encode_size()
786    }
787}
788
789impl<P: PublicKey> Write for PlayerAck<P> {
790    fn write(&self, buf: &mut impl bytes::BufMut) {
791        self.sig.write(buf);
792    }
793}
794
795impl<P: PublicKey> Read for PlayerAck<P> {
796    type Cfg = ();
797
798    fn read_cfg(
799        buf: &mut impl bytes::Buf,
800        _cfg: &Self::Cfg,
801    ) -> Result<Self, commonware_codec::Error> {
802        Ok(Self {
803            sig: ReadExt::read(buf)?,
804        })
805    }
806}
807
808#[cfg(feature = "arbitrary")]
809impl<P: PublicKey> arbitrary::Arbitrary<'_> for PlayerAck<P>
810where
811    P::Signature: for<'a> arbitrary::Arbitrary<'a>,
812{
813    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
814        let sig = u.arbitrary()?;
815        Ok(Self { sig })
816    }
817}
818
819#[derive(Clone, PartialEq)]
820enum AckOrReveal<P: PublicKey> {
821    Ack(PlayerAck<P>),
822    Reveal(DealerPrivMsg),
823}
824
825impl<P: PublicKey> AckOrReveal<P> {
826    const fn is_reveal(&self) -> bool {
827        matches!(*self, Self::Reveal(_))
828    }
829}
830
831impl<P: PublicKey> std::fmt::Debug for AckOrReveal<P> {
832    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
833        match self {
834            Self::Ack(x) => write!(f, "Ack({x:?})"),
835            Self::Reveal(_) => write!(f, "Reveal(REDACTED)"),
836        }
837    }
838}
839
840impl<P: PublicKey> EncodeSize for AckOrReveal<P> {
841    fn encode_size(&self) -> usize {
842        1 + match self {
843            Self::Ack(x) => x.encode_size(),
844            Self::Reveal(x) => x.encode_size(),
845        }
846    }
847}
848
849impl<P: PublicKey> Write for AckOrReveal<P> {
850    fn write(&self, buf: &mut impl bytes::BufMut) {
851        match self {
852            Self::Ack(x) => {
853                0u8.write(buf);
854                x.write(buf);
855            }
856            Self::Reveal(x) => {
857                1u8.write(buf);
858                x.write(buf);
859            }
860        }
861    }
862}
863
864impl<P: PublicKey> Read for AckOrReveal<P> {
865    type Cfg = ();
866
867    fn read_cfg(
868        buf: &mut impl bytes::Buf,
869        _cfg: &Self::Cfg,
870    ) -> Result<Self, commonware_codec::Error> {
871        let tag = u8::read(buf)?;
872        match tag {
873            0 => Ok(Self::Ack(ReadExt::read(buf)?)),
874            1 => Ok(Self::Reveal(ReadExt::read(buf)?)),
875            x => Err(commonware_codec::Error::InvalidEnum(x)),
876        }
877    }
878}
879
880#[cfg(feature = "arbitrary")]
881impl<P: PublicKey> arbitrary::Arbitrary<'_> for AckOrReveal<P>
882where
883    P: for<'a> arbitrary::Arbitrary<'a>,
884    P::Signature: for<'a> arbitrary::Arbitrary<'a>,
885{
886    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
887        let choice = u.int_in_range(0..=1)?;
888        match choice {
889            0 => {
890                let ack = u.arbitrary()?;
891                Ok(Self::Ack(ack))
892            }
893            1 => {
894                let reveal = u.arbitrary()?;
895                Ok(Self::Reveal(reveal))
896            }
897            _ => unreachable!(),
898        }
899    }
900}
901
902#[derive(Clone, Debug)]
903enum DealerResult<P: PublicKey> {
904    Ok(Map<P, AckOrReveal<P>>),
905    TooManyReveals,
906}
907
908impl<P: PublicKey> PartialEq for DealerResult<P> {
909    fn eq(&self, other: &Self) -> bool {
910        match (self, other) {
911            (Self::Ok(x), Self::Ok(y)) => x == y,
912            (Self::TooManyReveals, Self::TooManyReveals) => true,
913            _ => false,
914        }
915    }
916}
917
918impl<P: PublicKey> EncodeSize for DealerResult<P> {
919    fn encode_size(&self) -> usize {
920        1 + match self {
921            Self::Ok(r) => r.encode_size(),
922            Self::TooManyReveals => 0,
923        }
924    }
925}
926
927impl<P: PublicKey> Write for DealerResult<P> {
928    fn write(&self, buf: &mut impl bytes::BufMut) {
929        match self {
930            Self::Ok(r) => {
931                0u8.write(buf);
932                r.write(buf);
933            }
934            Self::TooManyReveals => {
935                1u8.write(buf);
936            }
937        }
938    }
939}
940
941impl<P: PublicKey> Read for DealerResult<P> {
942    type Cfg = NonZeroU32;
943
944    fn read_cfg(
945        buf: &mut impl bytes::Buf,
946        &max_players: &Self::Cfg,
947    ) -> Result<Self, commonware_codec::Error> {
948        let tag = u8::read(buf)?;
949        match tag {
950            0 => Ok(Self::Ok(Read::read_cfg(
951                buf,
952                &(RangeCfg::from(0..=max_players.get() as usize), (), ()),
953            )?)),
954            1 => Ok(Self::TooManyReveals),
955            x => Err(commonware_codec::Error::InvalidEnum(x)),
956        }
957    }
958}
959
960#[cfg(feature = "arbitrary")]
961impl<P: PublicKey> arbitrary::Arbitrary<'_> for DealerResult<P>
962where
963    P: for<'a> arbitrary::Arbitrary<'a>,
964    P::Signature: for<'a> arbitrary::Arbitrary<'a>,
965{
966    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
967        let choice = u.int_in_range(0..=1)?;
968        match choice {
969            0 => {
970                use commonware_utils::TryFromIterator;
971                use std::collections::HashMap;
972
973                let base: HashMap<P, AckOrReveal<P>> = u.arbitrary()?;
974                let map =
975                    Map::try_from_iter(base).map_err(|_| arbitrary::Error::IncorrectFormat)?;
976
977                Ok(Self::Ok(map))
978            }
979            1 => Ok(Self::TooManyReveals),
980            _ => unreachable!(),
981        }
982    }
983}
984
985#[derive(Clone, Debug)]
986pub struct DealerLog<V: Variant, P: PublicKey> {
987    pub_msg: DealerPubMsg<V>,
988    results: DealerResult<P>,
989}
990
991impl<V: Variant, P: PublicKey> PartialEq for DealerLog<V, P> {
992    fn eq(&self, other: &Self) -> bool {
993        self.pub_msg == other.pub_msg && self.results == other.results
994    }
995}
996
997impl<V: Variant, P: PublicKey> EncodeSize for DealerLog<V, P> {
998    fn encode_size(&self) -> usize {
999        self.pub_msg.encode_size() + self.results.encode_size()
1000    }
1001}
1002
1003impl<V: Variant, P: PublicKey> Write for DealerLog<V, P> {
1004    fn write(&self, buf: &mut impl bytes::BufMut) {
1005        self.pub_msg.write(buf);
1006        self.results.write(buf);
1007    }
1008}
1009
1010impl<V: Variant, P: PublicKey> Read for DealerLog<V, P> {
1011    type Cfg = NonZeroU32;
1012
1013    fn read_cfg(
1014        buf: &mut impl bytes::Buf,
1015        cfg: &Self::Cfg,
1016    ) -> Result<Self, commonware_codec::Error> {
1017        Ok(Self {
1018            pub_msg: Read::read_cfg(buf, cfg)?,
1019            results: Read::read_cfg(buf, cfg)?,
1020        })
1021    }
1022}
1023
1024impl<V: Variant, P: PublicKey> DealerLog<V, P> {
1025    fn get_ack(&self, player: &P) -> Option<&PlayerAck<P>> {
1026        let DealerResult::Ok(results) = &self.results else {
1027            return None;
1028        };
1029        match results.get_value(player) {
1030            Some(AckOrReveal::Ack(ack)) => Some(ack),
1031            _ => None,
1032        }
1033    }
1034
1035    fn get_reveal(&self, player: &P) -> Option<&DealerPrivMsg> {
1036        let DealerResult::Ok(results) = &self.results else {
1037            return None;
1038        };
1039        match results.get_value(player) {
1040            Some(AckOrReveal::Reveal(priv_msg)) => Some(priv_msg),
1041            _ => None,
1042        }
1043    }
1044
1045    fn zip_players<'a, 'b>(
1046        &'a self,
1047        players: &'b Set<P>,
1048    ) -> Option<impl Iterator<Item = (&'b P, &'a AckOrReveal<P>)>> {
1049        match &self.results {
1050            DealerResult::TooManyReveals => None,
1051            DealerResult::Ok(results) => {
1052                // We don't check this on deserialization.
1053                if results.keys() != players {
1054                    return None;
1055                }
1056                Some(players.iter().zip(results.values().iter()))
1057            }
1058        }
1059    }
1060
1061    /// Return a [`DealerLogSummary`] of the results in this log.
1062    ///
1063    /// This can be useful for observing the progress of the DKG.
1064    pub fn summary(&self) -> DealerLogSummary<P> {
1065        match &self.results {
1066            DealerResult::TooManyReveals => DealerLogSummary::TooManyReveals,
1067            DealerResult::Ok(map) => {
1068                let (reveals, acks): (Vec<_>, Vec<_>) =
1069                    map.iter_pairs().partition(|(_, a_r)| a_r.is_reveal());
1070                DealerLogSummary::Ok {
1071                    acks: acks
1072                        .into_iter()
1073                        .map(|(p, _)| p.clone())
1074                        .try_collect()
1075                        .expect("map keys are deduped"),
1076                    reveals: reveals
1077                        .into_iter()
1078                        .map(|(p, _)| p.clone())
1079                        .try_collect()
1080                        .expect("map keys are deduped"),
1081                }
1082            }
1083        }
1084    }
1085}
1086
1087/// Information about the reveals and acks in a [`DealerLog`].
1088// This exists to have a public interface we're happy maintaining, not leaking
1089// internal details about various things.
1090#[derive(Clone, Debug)]
1091pub enum DealerLogSummary<P> {
1092    /// The dealer is refusing to post any information, because they would have
1093    /// too many reveals otherwise.
1094    TooManyReveals,
1095    /// The dealer has some players who acked, and some players who didn't, that it's revealing.
1096    Ok { acks: Set<P>, reveals: Set<P> },
1097}
1098
1099#[cfg(feature = "arbitrary")]
1100impl<V: Variant, P: PublicKey> arbitrary::Arbitrary<'_> for DealerLog<V, P>
1101where
1102    P: for<'a> arbitrary::Arbitrary<'a>,
1103    V::Public: for<'a> arbitrary::Arbitrary<'a>,
1104    P::Signature: for<'a> arbitrary::Arbitrary<'a>,
1105{
1106    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1107        let pub_msg = u.arbitrary()?;
1108        let results = u.arbitrary()?;
1109        Ok(Self { pub_msg, results })
1110    }
1111}
1112
1113/// A [`DealerLog`], but identified to and signed by a dealer.
1114///
1115/// The [`SignedDealerLog::check`] method allows extracting a public key (the dealer)
1116/// and a [`DealerLog`] from this struct.
1117///
1118/// This avoids having to trust some other party or process for knowing that a
1119/// dealer actually produced a log.
1120#[derive(Clone, Debug)]
1121pub struct SignedDealerLog<V: Variant, S: Signer> {
1122    dealer: S::PublicKey,
1123    log: DealerLog<V, S::PublicKey>,
1124    sig: S::Signature,
1125}
1126
1127impl<V: Variant, S: Signer> PartialEq for SignedDealerLog<V, S> {
1128    fn eq(&self, other: &Self) -> bool {
1129        self.dealer == other.dealer && self.log == other.log && self.sig == other.sig
1130    }
1131}
1132
1133impl<V: Variant, S: Signer> SignedDealerLog<V, S> {
1134    fn sign(sk: &S, info: &Info<V, S::PublicKey>, log: DealerLog<V, S::PublicKey>) -> Self {
1135        let sig = transcript_for_log(info, &log).sign(sk);
1136        Self {
1137            dealer: sk.public_key(),
1138            log,
1139            sig,
1140        }
1141    }
1142
1143    /// Check this log for a particular round.
1144    ///
1145    /// This will produce the public key of the dealer that signed this log,
1146    /// and the underlying log that they signed.
1147    ///
1148    /// This will return [`Option::None`] if the check fails.
1149    #[allow(clippy::type_complexity)]
1150    pub fn check(
1151        self,
1152        info: &Info<V, S::PublicKey>,
1153    ) -> Option<(S::PublicKey, DealerLog<V, S::PublicKey>)> {
1154        if !transcript_for_log(info, &self.log).verify(&self.dealer, &self.sig) {
1155            return None;
1156        }
1157        Some((self.dealer, self.log))
1158    }
1159}
1160
1161impl<V: Variant, S: Signer> EncodeSize for SignedDealerLog<V, S> {
1162    fn encode_size(&self) -> usize {
1163        self.dealer.encode_size() + self.log.encode_size() + self.sig.encode_size()
1164    }
1165}
1166
1167impl<V: Variant, S: Signer> Write for SignedDealerLog<V, S> {
1168    fn write(&self, buf: &mut impl bytes::BufMut) {
1169        self.dealer.write(buf);
1170        self.log.write(buf);
1171        self.sig.write(buf);
1172    }
1173}
1174
1175impl<V: Variant, S: Signer> Read for SignedDealerLog<V, S> {
1176    type Cfg = NonZeroU32;
1177
1178    fn read_cfg(
1179        buf: &mut impl bytes::Buf,
1180        cfg: &Self::Cfg,
1181    ) -> Result<Self, commonware_codec::Error> {
1182        Ok(Self {
1183            dealer: ReadExt::read(buf)?,
1184            log: Read::read_cfg(buf, cfg)?,
1185            sig: ReadExt::read(buf)?,
1186        })
1187    }
1188}
1189
1190#[cfg(feature = "arbitrary")]
1191impl<V: Variant, S: Signer> arbitrary::Arbitrary<'_> for SignedDealerLog<V, S>
1192where
1193    S::PublicKey: for<'a> arbitrary::Arbitrary<'a>,
1194    V::Public: for<'a> arbitrary::Arbitrary<'a>,
1195    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
1196{
1197    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1198        let dealer = u.arbitrary()?;
1199        let log = u.arbitrary()?;
1200        let sig = u.arbitrary()?;
1201        Ok(Self { dealer, log, sig })
1202    }
1203}
1204
1205fn transcript_for_round<V: Variant, P: PublicKey>(info: &Info<V, P>) -> Transcript {
1206    Transcript::resume(info.summary)
1207}
1208
1209fn transcript_for_ack<V: Variant, P: PublicKey>(
1210    transcript: &Transcript,
1211    dealer: &P,
1212    pub_msg: &DealerPubMsg<V>,
1213) -> Transcript {
1214    let mut out = transcript.fork(SIG_ACK);
1215    out.commit(dealer.encode());
1216    out.commit(pub_msg.encode());
1217    out
1218}
1219
1220fn transcript_for_log<V: Variant, P: PublicKey>(
1221    info: &Info<V, P>,
1222    log: &DealerLog<V, P>,
1223) -> Transcript {
1224    let mut out = transcript_for_round(info).fork(SIG_LOG);
1225    out.commit(log.encode());
1226    out
1227}
1228
1229pub struct Dealer<V: Variant, S: Signer> {
1230    me: S,
1231    info: Info<V, S::PublicKey>,
1232    pub_msg: DealerPubMsg<V>,
1233    results: Map<S::PublicKey, AckOrReveal<S::PublicKey>>,
1234    transcript: Transcript,
1235}
1236
1237impl<V: Variant, S: Signer> Dealer<V, S> {
1238    /// Create a [`Dealer`].
1239    ///
1240    /// This needs randomness, to generate a dealing.
1241    ///
1242    /// We also need the dealer's private key, in order to produce the [`SignedDealerLog`].
1243    ///
1244    /// If we're doing a reshare, the dealer should have a share from the previous round.
1245    ///
1246    /// This will produce the [`Dealer`], a [`DealerPubMsg`] to send to every player,
1247    /// and a list of [`DealerPrivMsg`]s, along with which players those need to
1248    /// be sent to.
1249    ///
1250    /// The public message can be sent in the clear, but it's important that players
1251    /// know which dealer sent what public message. You MUST ensure that dealers
1252    /// cannot impersonate each-other when sending this message.
1253    ///
1254    /// The private message MUST be sent encrypted (or, in some other way, privately)
1255    /// to the target player. Similarly, that player MUST be convinced that this dealer
1256    /// sent it that message, without any possibility of impersonation. A simple way
1257    /// to provide both guarantees is through an authenticated channel, e.g. via
1258    /// [crate::handshake], or [commonware-p2p](https://docs.rs/commonware-p2p/latest/commonware_p2p/).
1259    #[allow(clippy::type_complexity)]
1260    pub fn start<M: Faults>(
1261        mut rng: impl CryptoRngCore,
1262        info: Info<V, S::PublicKey>,
1263        me: S,
1264        share: Option<Share>,
1265    ) -> Result<(Self, DealerPubMsg<V>, Vec<(S::PublicKey, DealerPrivMsg)>), Error> {
1266        // Check that this dealer is defined in the round.
1267        info.dealer_index(&me.public_key())?;
1268        let share = info.unwrap_or_random_share(
1269            &mut rng,
1270            // We are extracting the private scalar from `Secret` protection because
1271            // `Poly::new_with_constant` requires an owned value. The extracted scalar is
1272            // scoped to this function and will be zeroized on drop (i.e. the secret is
1273            // only exposed for the duration of this function).
1274            share.map(|x| x.private.expose_unwrap()),
1275        )?;
1276        let my_poly = Poly::new_with_constant(&mut rng, info.degree::<M>(), share);
1277        let priv_msgs = info
1278            .players
1279            .iter()
1280            .map(|pk| {
1281                (
1282                    pk.clone(),
1283                    DealerPrivMsg::new(my_poly.eval_msm(
1284                        &info.player_scalar(pk).expect("player should exist"),
1285                        &Sequential,
1286                    )),
1287                )
1288            })
1289            .collect::<Vec<_>>();
1290        let results: Map<_, _> = priv_msgs
1291            .clone()
1292            .into_iter()
1293            .map(|(pk, priv_msg)| (pk, AckOrReveal::Reveal(priv_msg)))
1294            .try_collect()
1295            .expect("players are unique");
1296        let commitment = Poly::commit(my_poly);
1297        let pub_msg = DealerPubMsg { commitment };
1298        let transcript = {
1299            let t = transcript_for_round(&info);
1300            transcript_for_ack(&t, &me.public_key(), &pub_msg)
1301        };
1302        let this = Self {
1303            me,
1304            info,
1305            pub_msg: pub_msg.clone(),
1306            results,
1307            transcript,
1308        };
1309        Ok((this, pub_msg, priv_msgs))
1310    }
1311
1312    /// Process an acknowledgement from a player.
1313    ///
1314    /// Acknowledgements should really only be processed once per player,
1315    /// but this method is idempotent nonetheless.
1316    pub fn receive_player_ack(
1317        &mut self,
1318        player: S::PublicKey,
1319        ack: PlayerAck<S::PublicKey>,
1320    ) -> Result<(), Error> {
1321        let res_mut = self
1322            .results
1323            .get_value_mut(&player)
1324            .ok_or(Error::UnknownPlayer)?;
1325        if self.transcript.verify(&player, &ack.sig) {
1326            *res_mut = AckOrReveal::Ack(ack);
1327        }
1328        Ok(())
1329    }
1330
1331    /// Finalize the dealer, producing a signed log.
1332    ///
1333    /// This should be called at the point where no more acks will be processed.
1334    pub fn finalize<M: Faults>(self) -> SignedDealerLog<V, S> {
1335        let reveals = self
1336            .results
1337            .values()
1338            .iter()
1339            .filter(|x| x.is_reveal())
1340            .count() as u32;
1341        // Omit results if there are too many reveals.
1342        let results = if reveals > self.info.max_reveals::<M>() {
1343            DealerResult::TooManyReveals
1344        } else {
1345            DealerResult::Ok(self.results)
1346        };
1347        let log = DealerLog {
1348            pub_msg: self.pub_msg,
1349            results,
1350        };
1351        SignedDealerLog::sign(&self.me, &self.info, log)
1352    }
1353}
1354
1355#[allow(clippy::type_complexity)]
1356fn select<V: Variant, P: PublicKey, M: Faults>(
1357    info: &Info<V, P>,
1358    logs: BTreeMap<P, DealerLog<V, P>>,
1359) -> Result<Map<P, DealerLog<V, P>>, Error> {
1360    let required_commitments = info.required_commitments::<M>() as usize;
1361    let transcript = transcript_for_round(info);
1362    let out = logs
1363        .into_iter()
1364        .filter_map(|(dealer, log)| {
1365            info.dealer_index(&dealer).ok()?;
1366            if !info.check_dealer_pub_msg::<M>(&dealer, &log.pub_msg) {
1367                return None;
1368            }
1369            let results_iter = log.zip_players(&info.players)?;
1370            let transcript = transcript_for_ack(&transcript, &dealer, &log.pub_msg);
1371            let mut reveal_count = 0;
1372            let max_reveals = info.max_reveals::<M>();
1373            for (player, result) in results_iter {
1374                match result {
1375                    AckOrReveal::Ack(ack) => {
1376                        if !transcript.verify(player, &ack.sig) {
1377                            return None;
1378                        }
1379                    }
1380                    AckOrReveal::Reveal(priv_msg) => {
1381                        reveal_count += 1;
1382                        if reveal_count > max_reveals {
1383                            return None;
1384                        }
1385                        if !info.check_dealer_priv_msg(player, &log.pub_msg, priv_msg) {
1386                            return None;
1387                        }
1388                    }
1389                }
1390            }
1391            Some((dealer, log))
1392        })
1393        .take(required_commitments)
1394        .try_collect::<Map<_, _>>()
1395        .expect("logs has at most one entry per dealer");
1396    if out.len() < required_commitments {
1397        return Err(Error::DkgFailed);
1398    }
1399    Ok(out)
1400}
1401
1402struct ObserveInner<V: Variant, P: PublicKey> {
1403    output: Output<V, P>,
1404    weights: Option<Interpolator<P, Scalar>>,
1405}
1406
1407impl<V: Variant, P: PublicKey> ObserveInner<V, P> {
1408    fn reckon<M: Faults>(
1409        info: Info<V, P>,
1410        selected: Map<P, DealerLog<V, P>>,
1411        strategy: &impl Strategy,
1412    ) -> Result<Self, Error> {
1413        // Track players with too many reveals
1414        let max_faults = info.players.max_faults::<M>();
1415        let mut reveal_counts: BTreeMap<P, u32> = BTreeMap::new();
1416        let mut revealed = Vec::new();
1417        for log in selected.values() {
1418            let Some(iter) = log.zip_players(&info.players) else {
1419                continue;
1420            };
1421            for (player, result) in iter {
1422                if !result.is_reveal() {
1423                    continue;
1424                }
1425                let count = reveal_counts.entry(player.clone()).or_insert(0);
1426                *count += 1;
1427                if *count == max_faults + 1 {
1428                    revealed.push(player.clone());
1429                }
1430            }
1431        }
1432        let revealed: Set<P> = revealed
1433            .into_iter()
1434            .try_collect()
1435            .expect("players are unique");
1436
1437        // Extract dealers before consuming selected
1438        let dealers: Set<P> = selected
1439            .keys()
1440            .iter()
1441            .cloned()
1442            .try_collect()
1443            .expect("selected dealers are unique");
1444
1445        // Recover the public polynomial
1446        let (public, weights) = if let Some(previous) = info.previous.as_ref() {
1447            let weights = previous
1448                .public()
1449                .mode()
1450                .subset_interpolator(previous.players(), selected.keys())
1451                .expect("the result of select should produce a valid subset");
1452            let commitments = selected
1453                .into_iter()
1454                .map(|(dealer, log)| (dealer, log.pub_msg.commitment))
1455                .try_collect::<Map<_, _>>()
1456                .expect("Map should have unique keys");
1457            let public = weights
1458                .interpolate(&commitments, strategy)
1459                .expect("select checks that enough points have been provided");
1460            if previous.public().public() != public.constant() {
1461                return Err(Error::DkgFailed);
1462            }
1463            (public, Some(weights))
1464        } else {
1465            let mut public = Poly::zero();
1466            for log in selected.values() {
1467                public += &log.pub_msg.commitment;
1468            }
1469            (public, None)
1470        };
1471        let n = info.players.len() as u32;
1472        let output = Output {
1473            summary: info.summary,
1474            public: Sharing::new(info.mode, NZU32!(n), public),
1475            dealers,
1476            players: info.players,
1477            revealed,
1478        };
1479        Ok(Self { output, weights })
1480    }
1481}
1482
1483/// Observe the result of a DKG, using the public results.
1484///
1485/// The log mapping dealers to their log is the shared piece of information
1486/// that the participants (players, observers) of the DKG must all agree on.
1487///
1488/// From this log, we can (potentially, as the DKG can fail) compute the public output.
1489///
1490/// This will only ever return [`Error::DkgFailed`].
1491pub fn observe<V: Variant, P: PublicKey, M: Faults>(
1492    info: Info<V, P>,
1493    logs: BTreeMap<P, DealerLog<V, P>>,
1494    strategy: &impl Strategy,
1495) -> Result<Output<V, P>, Error> {
1496    let selected = select::<V, P, M>(&info, logs)?;
1497    ObserveInner::<V, P>::reckon::<M>(info, selected, strategy).map(|x| x.output)
1498}
1499
1500/// Represents a player in the DKG / reshare process.
1501///
1502/// The player is attempting to get a share of the key.
1503///
1504/// They need not have participated in prior rounds.
1505pub struct Player<V: Variant, S: Signer> {
1506    me: S,
1507    me_pub: S::PublicKey,
1508    info: Info<V, S::PublicKey>,
1509    index: Participant,
1510    transcript: Transcript,
1511    view: BTreeMap<S::PublicKey, (DealerPubMsg<V>, DealerPrivMsg)>,
1512}
1513
1514impl<V: Variant, S: Signer> Player<V, S> {
1515    /// Create a new [`Player`].
1516    ///
1517    /// We need the player's private key in order to sign messages.
1518    pub fn new(info: Info<V, S::PublicKey>, me: S) -> Result<Self, Error> {
1519        let me_pub = me.public_key();
1520        Ok(Self {
1521            index: info.player_index(&me_pub)?,
1522            me,
1523            me_pub,
1524            transcript: transcript_for_round(&info),
1525            info,
1526            view: BTreeMap::new(),
1527        })
1528    }
1529
1530    /// Resume a [`Player`], given some existing public state.
1531    ///
1532    /// This is equivalent to calling [`Self::new`] and then [`Self::dealer_message`]
1533    /// with the appropriate messages, but includes extra safeguards to detect
1534    /// missing / corrupted state.
1535    ///
1536    /// It's imperative that the `logs` passed in have been verified. This is done
1537    /// naturally when converting from a [`SignedDealerLog`] to a [`DealerLog`],
1538    /// but this function, like [`Player::finalize`], assumes that this check has
1539    /// been done.
1540    ///
1541    /// All messages the player should have received must be passed into this method,
1542    /// and if any messages which should be present based on this player's actions
1543    /// in the log are missing, this method will return [`Error::MissingPlayerDealing`].
1544    ///
1545    /// For example, if a particular private message containing a share is not
1546    /// present in `msgs`, but we've already acknowledged it, and this has been
1547    /// included in a public log, then this method will fail.
1548    /// This method cannot catch all cases where state has been corrupted. In
1549    /// particular, if a dealer has not posted their log publicly yet, but has
1550    /// already received an ack, then this method cannot help in that case,
1551    /// but the issue still remains.
1552    ///
1553    /// The returned map contains the acknowledgements generated while replaying
1554    /// `msgs`, keyed by dealer.
1555    #[allow(clippy::type_complexity)]
1556    pub fn resume<M: Faults>(
1557        info: Info<V, S::PublicKey>,
1558        me: S,
1559        logs: &BTreeMap<S::PublicKey, DealerLog<V, S::PublicKey>>,
1560        msgs: impl IntoIterator<Item = (S::PublicKey, DealerPubMsg<V>, DealerPrivMsg)>,
1561    ) -> Result<(Self, BTreeMap<S::PublicKey, PlayerAck<S::PublicKey>>), Error> {
1562        // Record all acks we've emitted (by dealer).
1563        let mut this = Self::new(info, me)?;
1564        let mut acks = BTreeMap::new();
1565        for (dealer, pub_msg, priv_msg) in msgs {
1566            if let Some(ack) = this.dealer_message::<M>(dealer.clone(), pub_msg, priv_msg) {
1567                acks.insert(dealer, ack);
1568            }
1569        }
1570
1571        // Have we emitted any valid acks, publicly recorded, for which we do
1572        // not have the private message?
1573        if logs.iter().any(|(dealer, log)| {
1574            let Some(ack) = log.get_ack(&this.me_pub) else {
1575                return false;
1576            };
1577            // Only trust this ack if the signature is valid for this round.
1578            transcript_for_ack(&this.transcript, dealer, &log.pub_msg)
1579                .verify(&this.me_pub, &ack.sig)
1580                && !acks.contains_key(dealer)
1581        }) {
1582            // If so, we have a problem, because we're missing a dealing that we're
1583            // supposed to have, and that we publicly committed to having.
1584            return Err(Error::MissingPlayerDealing);
1585        }
1586
1587        Ok((this, acks))
1588    }
1589
1590    /// Process a message from a dealer.
1591    ///
1592    /// It's important that nobody can impersonate the dealer, and that the
1593    /// private message was not exposed to anyone else. A convenient way to
1594    /// provide this is by using an authenticated channel, e.g. via
1595    /// [crate::handshake], or [commonware-p2p](https://docs.rs/commonware-p2p/latest/commonware_p2p/).
1596    pub fn dealer_message<M: Faults>(
1597        &mut self,
1598        dealer: S::PublicKey,
1599        pub_msg: DealerPubMsg<V>,
1600        priv_msg: DealerPrivMsg,
1601    ) -> Option<PlayerAck<S::PublicKey>> {
1602        if self.view.contains_key(&dealer) {
1603            return None;
1604        }
1605        self.info.dealer_index(&dealer).ok()?;
1606        if !self.info.check_dealer_pub_msg::<M>(&dealer, &pub_msg) {
1607            return None;
1608        }
1609        if !self
1610            .info
1611            .check_dealer_priv_msg(&self.me_pub, &pub_msg, &priv_msg)
1612        {
1613            return None;
1614        }
1615        let sig = transcript_for_ack(&self.transcript, &dealer, &pub_msg).sign(&self.me);
1616        self.view.insert(dealer, (pub_msg, priv_msg));
1617        Some(PlayerAck { sig })
1618    }
1619
1620    /// Finalize the player, producing an output, and a share.
1621    ///
1622    /// This should agree with [`observe`], in terms of `Ok` vs `Err` (with one exception)
1623    /// and the public output, so long as the logs agree. It's crucial that the players
1624    /// come to agreement, in some way, on exactly which logs they need to use
1625    /// for finalize.
1626    ///
1627    /// The exception is that if this function returns [`Error::MissingPlayerDealing`],
1628    /// then [`observe`] will return `Ok`, because this error indicates that this
1629    /// player's state has been corrupted, but the DKG has otherwise succeeded.
1630    /// However, this player's share is not recoverable without external intervention.
1631    ///
1632    /// Otherwise, the only error this function can return is [`Error::DkgFailed`].
1633    pub fn finalize<M: Faults>(
1634        self,
1635        logs: BTreeMap<S::PublicKey, DealerLog<V, S::PublicKey>>,
1636        strategy: &impl Strategy,
1637    ) -> Result<(Output<V, S::PublicKey>, Share), Error> {
1638        // `select` verifies ack signatures, so any ack present in `selected`
1639        // is trustworthy for this round/dealer/pub_msg transcript.
1640        // If there's a log that contains an ack of ours, but no corresponding view,
1641        // then we're missing a dealing.
1642        let selected = select::<V, S::PublicKey, M>(&self.info, logs)?;
1643        if selected
1644            .iter_pairs()
1645            .any(|(d, l)| l.get_ack(&self.me_pub).is_some() && !self.view.contains_key(d))
1646        {
1647            return Err(Error::MissingPlayerDealing);
1648        }
1649
1650        // We are extracting the private scalars from `Secret` protection
1651        // because interpolation/summation needs owned scalars for polynomial
1652        // arithmetic. The extracted scalars are scoped to this function and
1653        // will be zeroized on drop (i.e. the secrets are only exposed for the
1654        // duration of this function).
1655        let dealings = selected
1656            .iter_pairs()
1657            .map(|(dealer, log)| {
1658                let share = self
1659                    .view
1660                    .get(dealer)
1661                    .map(|(_, priv_msg)| priv_msg.share.clone().expose_unwrap())
1662                    .unwrap_or_else(|| {
1663                        log.get_reveal(&self.me_pub).map_or_else(
1664                            || {
1665                                unreachable!(
1666                                    "select didn't check dealer reveal, or we're not a player?"
1667                                )
1668                            },
1669                            |priv_msg| priv_msg.share.clone().expose_unwrap(),
1670                        )
1671                    });
1672                (dealer.clone(), share)
1673            })
1674            .try_collect::<Map<_, _>>()
1675            .expect("select produces at most one entry per dealer");
1676        let ObserveInner { output, weights } =
1677            ObserveInner::<V, S::PublicKey>::reckon::<M>(self.info, selected, strategy)?;
1678        let private = weights.map_or_else(
1679            || {
1680                let mut out = <Scalar as Additive>::zero();
1681                for s in dealings.values() {
1682                    out += s;
1683                }
1684                out
1685            },
1686            |weights| {
1687                weights
1688                    .interpolate(&dealings, strategy)
1689                    .expect("select ensures that we can recover")
1690            },
1691        );
1692        let share = Share::new(self.index, Private::new(private));
1693        Ok((output, share))
1694    }
1695}
1696
1697/// The result of dealing shares to players.
1698pub type DealResult<V, P> = Result<(Output<V, P>, Map<P, Share>), Error>;
1699
1700/// Simply distribute shares at random, instead of performing a distributed protocol.
1701pub fn deal<V: Variant, P: Clone + Ord, M: Faults>(
1702    mut rng: impl CryptoRngCore,
1703    mode: Mode,
1704    players: Set<P>,
1705) -> DealResult<V, P> {
1706    if players.is_empty() {
1707        return Err(Error::NumPlayers(0));
1708    }
1709    let n = NZU32!(players.len() as u32);
1710    let t = players.quorum::<M>();
1711    let private = Poly::new(&mut rng, t - 1);
1712    let shares: Map<_, _> = players
1713        .iter()
1714        .enumerate()
1715        .map(|(i, p)| {
1716            let participant = Participant::from_usize(i);
1717            let eval = private.eval_msm(
1718                &mode
1719                    .scalar(n, participant)
1720                    .expect("player index should be valid"),
1721                &Sequential,
1722            );
1723            let share = Share::new(participant, Private::new(eval));
1724            (p.clone(), share)
1725        })
1726        .try_collect()
1727        .expect("players are unique");
1728    let output = Output {
1729        summary: Summary::random(&mut rng),
1730        public: Sharing::new(mode, n, Poly::commit(private)),
1731        dealers: players.clone(),
1732        players,
1733        revealed: Set::default(),
1734    };
1735    Ok((output, shares))
1736}
1737
1738/// Like [`deal`], but without linking the result to specific public keys.
1739///
1740/// This can be more convenient for testing, where you don't want to go through
1741/// the trouble of generating signing keys. The downside is that the result isn't
1742/// compatible with subsequent DKGs, which need an [`Output`].
1743pub fn deal_anonymous<V: Variant, M: Faults>(
1744    rng: impl CryptoRngCore,
1745    mode: Mode,
1746    n: NonZeroU32,
1747) -> (Sharing<V>, Vec<Share>) {
1748    let players = (0..n.get()).try_collect().unwrap();
1749    let (output, shares) = deal::<V, _, M>(rng, mode, players).unwrap();
1750    (output.public().clone(), shares.values().to_vec())
1751}
1752
1753#[cfg(any(feature = "arbitrary", test))]
1754mod test_plan {
1755    use super::*;
1756    use crate::{
1757        bls12381::primitives::{
1758            ops::{self, threshold},
1759            variant::Variant,
1760        },
1761        ed25519, PublicKey,
1762    };
1763    use anyhow::anyhow;
1764    use bytes::BytesMut;
1765    use commonware_utils::{Faults, N3f1, TryCollect};
1766    use core::num::NonZeroI32;
1767    use rand::{rngs::StdRng, SeedableRng as _};
1768    use std::collections::BTreeSet;
1769
1770    /// Apply a mask to some bytes, returning whether or not a modification happened
1771    fn apply_mask(bytes: &mut BytesMut, mask: &[u8]) -> bool {
1772        let mut modified = false;
1773        for (l, &r) in bytes.iter_mut().zip(mask.iter()) {
1774            modified |= r != 0;
1775            *l ^= r;
1776        }
1777        modified
1778    }
1779
1780    #[derive(Clone, Default, Debug)]
1781    pub struct Masks {
1782        pub info_summary: Vec<u8>,
1783        pub dealer: Vec<u8>,
1784        pub pub_msg: Vec<u8>,
1785        pub log: Vec<u8>,
1786    }
1787
1788    impl Masks {
1789        fn modifies_player_ack(&self) -> bool {
1790            self.info_summary.iter().any(|&b| b != 0)
1791                || self.dealer.iter().any(|&b| b != 0)
1792                || self.pub_msg.iter().any(|&b| b != 0)
1793        }
1794
1795        fn transcript_for_round<V: Variant, P: PublicKey>(
1796            &self,
1797            info: &Info<V, P>,
1798        ) -> anyhow::Result<(bool, Transcript)> {
1799            let mut summary_bs = info.summary.encode_mut();
1800            let modified = apply_mask(&mut summary_bs, &self.info_summary);
1801            let summary = Summary::read(&mut summary_bs)?;
1802            Ok((modified, Transcript::resume(summary)))
1803        }
1804
1805        fn transcript_for_player_ack<V: Variant, P: PublicKey>(
1806            &self,
1807            info: &Info<V, P>,
1808            dealer: &P,
1809            pub_msg: &DealerPubMsg<V>,
1810        ) -> anyhow::Result<(bool, Transcript)> {
1811            let (mut modified, transcript) = self.transcript_for_round(info)?;
1812            let mut transcript = transcript.fork(SIG_ACK);
1813
1814            let mut dealer_bs = dealer.encode_mut();
1815            modified |= apply_mask(&mut dealer_bs, &self.dealer);
1816            transcript.commit(&mut dealer_bs);
1817
1818            let mut pub_msg_bs = pub_msg.encode_mut();
1819            modified |= apply_mask(&mut pub_msg_bs, &self.pub_msg);
1820            transcript.commit(&mut pub_msg_bs);
1821
1822            Ok((modified, transcript))
1823        }
1824
1825        fn transcript_for_signed_dealer_log<V: Variant, P: PublicKey>(
1826            &self,
1827            info: &Info<V, P>,
1828            log: &DealerLog<V, P>,
1829        ) -> anyhow::Result<(bool, Transcript)> {
1830            let (mut modified, transcript) = self.transcript_for_round(info)?;
1831            let mut transcript = transcript.fork(SIG_LOG);
1832
1833            let mut log_bs = log.encode_mut();
1834            modified |= apply_mask(&mut log_bs, &self.log);
1835            transcript.commit(&mut log_bs);
1836
1837            Ok((modified, transcript))
1838        }
1839    }
1840
1841    /// A round in the DKG test plan.
1842    #[derive(Debug, Default)]
1843    pub struct Round {
1844        dealers: Vec<u32>,
1845        players: Vec<u32>,
1846        crash_resume_players: BTreeSet<(u32, u32)>,
1847        resume_missing_dealer_msg_fails: BTreeSet<(u32, u32)>,
1848        finalize_missing_dealer_msg_fails: BTreeSet<u32>,
1849        no_acks: BTreeSet<(u32, u32)>,
1850        bad_shares: BTreeSet<(u32, u32)>,
1851        bad_player_sigs: BTreeMap<(u32, u32), Masks>,
1852        bad_reveals: BTreeSet<(u32, u32)>,
1853        bad_dealer_sigs: BTreeMap<u32, Masks>,
1854        replace_shares: BTreeSet<u32>,
1855        shift_degrees: BTreeMap<u32, NonZeroI32>,
1856    }
1857
1858    impl Round {
1859        pub fn new(dealers: Vec<u32>, players: Vec<u32>) -> Self {
1860            Self {
1861                dealers,
1862                players,
1863                ..Default::default()
1864            }
1865        }
1866
1867        pub fn no_ack(mut self, dealer: u32, player: u32) -> Self {
1868            self.no_acks.insert((dealer, player));
1869            self
1870        }
1871
1872        pub fn crash_resume_player(mut self, after_dealer: u32, player: u32) -> Self {
1873            self.crash_resume_players.insert((after_dealer, player));
1874            self
1875        }
1876
1877        pub fn resume_missing_dealer_msg_fails(
1878            mut self,
1879            after_dealer: u32,
1880            missing_dealer: u32,
1881        ) -> Self {
1882            self.resume_missing_dealer_msg_fails
1883                .insert((after_dealer, missing_dealer));
1884            self
1885        }
1886
1887        pub fn finalize_missing_dealer_msg_fails(mut self, player: u32) -> Self {
1888            self.finalize_missing_dealer_msg_fails.insert(player);
1889            self
1890        }
1891
1892        pub fn bad_share(mut self, dealer: u32, player: u32) -> Self {
1893            self.bad_shares.insert((dealer, player));
1894            self
1895        }
1896
1897        pub fn bad_player_sig(mut self, dealer: u32, player: u32, masks: Masks) -> Self {
1898            self.bad_player_sigs.insert((dealer, player), masks);
1899            self
1900        }
1901
1902        pub fn bad_reveal(mut self, dealer: u32, player: u32) -> Self {
1903            self.bad_reveals.insert((dealer, player));
1904            self
1905        }
1906
1907        pub fn bad_dealer_sig(mut self, dealer: u32, masks: Masks) -> Self {
1908            self.bad_dealer_sigs.insert(dealer, masks);
1909            self
1910        }
1911
1912        pub fn replace_share(mut self, dealer: u32) -> Self {
1913            self.replace_shares.insert(dealer);
1914            self
1915        }
1916
1917        pub fn shift_degree(mut self, dealer: u32, shift: NonZeroI32) -> Self {
1918            self.shift_degrees.insert(dealer, shift);
1919            self
1920        }
1921
1922        /// Validate that this round is well-formed given the number of participants
1923        /// and the previous successful round's players.
1924        pub fn validate(
1925            &self,
1926            num_participants: u32,
1927            previous_players: Option<&[u32]>,
1928        ) -> anyhow::Result<()> {
1929            if self.dealers.is_empty() {
1930                return Err(anyhow!("dealers is empty"));
1931            }
1932            if self.players.is_empty() {
1933                return Err(anyhow!("players is empty"));
1934            }
1935            // Check dealer/player ranges
1936            for &d in &self.dealers {
1937                if d >= num_participants {
1938                    return Err(anyhow!("dealer {d} out of range [1, {num_participants}]"));
1939                }
1940            }
1941            for &p in &self.players {
1942                if p >= num_participants {
1943                    return Err(anyhow!("player {p} out of range [1, {num_participants}]"));
1944                }
1945            }
1946            // Crash/resume checkpoints must reference in-round dealers/players.
1947            for &(after_dealer, player) in &self.crash_resume_players {
1948                if !self.dealers.contains(&after_dealer) {
1949                    return Err(anyhow!("crash_resume dealer {after_dealer} not in round"));
1950                }
1951                if !self.players.contains(&player) {
1952                    return Err(anyhow!("crash_resume player {player} not in round"));
1953                }
1954            }
1955            let dealer_positions: BTreeMap<u32, usize> = self
1956                .dealers
1957                .iter()
1958                .enumerate()
1959                .map(|(idx, &dealer)| (dealer, idx))
1960                .collect();
1961            let previous_successful_round = previous_players.is_some();
1962            for &(after_dealer, missing_dealer) in &self.resume_missing_dealer_msg_fails {
1963                if !self.dealers.contains(&after_dealer) {
1964                    return Err(anyhow!("resume_missing dealer {after_dealer} not in round"));
1965                }
1966                if !self.dealers.contains(&missing_dealer) {
1967                    return Err(anyhow!(
1968                        "resume_missing missing_dealer {missing_dealer} not in round"
1969                    ));
1970                }
1971                let after_pos = dealer_positions[&after_dealer];
1972                let missing_pos = dealer_positions[&missing_dealer];
1973                if missing_pos > after_pos {
1974                    return Err(anyhow!(
1975                        "resume_missing missing_dealer {missing_dealer} appears after {after_dealer}"
1976                    ));
1977                }
1978                if self.bad(previous_successful_round, missing_dealer) {
1979                    return Err(anyhow!(
1980                        "resume_missing_dealer_msg_fails requires dealer {missing_dealer} to be good"
1981                    ));
1982                }
1983                let any_valid_ack = self.players.iter().any(|&player| {
1984                    let ack_corrupted = self.no_acks.contains(&(missing_dealer, player))
1985                        || self.bad_shares.contains(&(missing_dealer, player))
1986                        || self
1987                            .bad_player_sigs
1988                            .get(&(missing_dealer, player))
1989                            .is_some_and(Masks::modifies_player_ack);
1990                    !ack_corrupted
1991                });
1992                if !any_valid_ack {
1993                    return Err(anyhow!(
1994                        "resume_missing_dealer_msg_fails requires dealer {missing_dealer} to ack at least one player"
1995                    ));
1996                }
1997            }
1998            for &player in &self.finalize_missing_dealer_msg_fails {
1999                if !self.players.contains(&player) {
2000                    return Err(anyhow!("finalize_missing player {player} not in round"));
2001                }
2002            }
2003
2004            // If there's a previous round, check dealer constraints
2005            if let Some(prev_players) = previous_players {
2006                // Every dealer must have been a player in the previous round
2007                for &d in &self.dealers {
2008                    if !prev_players.contains(&d) {
2009                        return Err(anyhow!("dealer {d} was not a player in previous round"));
2010                    }
2011                }
2012                // Must have >= quorum(prev_players) dealers
2013                let required = N3f1::quorum(prev_players.len());
2014                if (self.dealers.len() as u32) < required {
2015                    return Err(anyhow!(
2016                        "not enough dealers: have {}, need {} (quorum of {} previous players)",
2017                        self.dealers.len(),
2018                        required,
2019                        prev_players.len()
2020                    ));
2021                }
2022            }
2023
2024            Ok(())
2025        }
2026
2027        fn bad(&self, previous_successful_round: bool, dealer: u32) -> bool {
2028            if self.replace_shares.contains(&dealer) && previous_successful_round {
2029                return true;
2030            }
2031            if let Some(shift) = self.shift_degrees.get(&dealer) {
2032                let degree = N3f1::quorum(self.players.len()) as i32 - 1;
2033                // We shift the degree, but saturate at 0, so it's possible
2034                // that the shift isn't actually doing anything.
2035                //
2036                // This is effectively the same as checking degree == 0 && shift < 0,
2037                // but matches what ends up happening a bit better.
2038                if (degree + shift.get()).max(0) != degree {
2039                    return true;
2040                }
2041            }
2042            if self.bad_reveals.iter().any(|&(d, _)| d == dealer) {
2043                return true;
2044            }
2045            let revealed_players = self
2046                .bad_shares
2047                .iter()
2048                .copied()
2049                .chain(self.no_acks.iter().copied())
2050                .filter_map(|(d, p)| if d == dealer { Some(p) } else { None })
2051                .collect::<BTreeSet<_>>();
2052            revealed_players.len() as u32 > N3f1::max_faults(self.players.len())
2053        }
2054
2055        /// Determine if this round is expected to fail.
2056        fn expect_failure(&self, previous_successful_round: Option<u32>) -> bool {
2057            let good_dealer_count = self
2058                .dealers
2059                .iter()
2060                .filter(|&&d| !self.bad(previous_successful_round.is_some(), d))
2061                .count();
2062            let required = previous_successful_round
2063                .map(N3f1::quorum)
2064                .unwrap_or_default()
2065                .max(N3f1::quorum(self.dealers.len())) as usize;
2066            good_dealer_count < required
2067        }
2068    }
2069
2070    /// A DKG test plan consisting of multiple rounds.
2071    #[derive(Debug)]
2072    pub struct Plan {
2073        num_participants: NonZeroU32,
2074        rounds: Vec<Round>,
2075    }
2076
2077    impl Plan {
2078        pub const fn new(num_participants: NonZeroU32) -> Self {
2079            Self {
2080                num_participants,
2081                rounds: Vec::new(),
2082            }
2083        }
2084
2085        pub fn with(mut self, round: Round) -> Self {
2086            self.rounds.push(round);
2087            self
2088        }
2089
2090        /// Validate the entire plan.
2091        pub(crate) fn validate(&self) -> anyhow::Result<()> {
2092            let mut last_successful_players: Option<Vec<u32>> = None;
2093
2094            for round in &self.rounds {
2095                round.validate(
2096                    self.num_participants.get(),
2097                    last_successful_players.as_deref(),
2098                )?;
2099
2100                // If this round is expected to succeed, update last_successful_players
2101                if !round.expect_failure(last_successful_players.as_ref().map(|x| x.len() as u32)) {
2102                    last_successful_players = Some(round.players.clone());
2103                }
2104            }
2105            Ok(())
2106        }
2107
2108        /// Run the test plan with a given seed.
2109        pub fn run<V: Variant>(self, seed: u64) -> anyhow::Result<()> {
2110            self.validate()?;
2111
2112            let mut rng = StdRng::seed_from_u64(seed);
2113
2114            // Generate keys for all participants (1-indexed to num_participants)
2115            let keys = (0..self.num_participants.get())
2116                .map(|_| ed25519::PrivateKey::random(&mut rng))
2117                .collect::<Vec<_>>();
2118
2119            // Precompute mapping from public key to key index to avoid confusion
2120            // between key indices and positions in sorted Sets.
2121            let pk_to_key_idx: BTreeMap<ed25519::PublicKey, u32> = keys
2122                .iter()
2123                .enumerate()
2124                .map(|(i, k)| (k.public_key(), i as u32))
2125                .collect();
2126
2127            // The max_read_size needs to account for shifted polynomial degrees.
2128            // Find the maximum positive shift across all rounds.
2129            let max_shift = self
2130                .rounds
2131                .iter()
2132                .flat_map(|r| r.shift_degrees.values())
2133                .map(|s| s.get())
2134                .max()
2135                .unwrap_or(0)
2136                .max(0) as u32;
2137            let max_read_size =
2138                NonZeroU32::new(self.num_participants.get() + max_shift).expect("non-zero");
2139
2140            let mut previous_output: Option<Output<V, ed25519::PublicKey>> = None;
2141            let mut shares: BTreeMap<ed25519::PublicKey, Share> = BTreeMap::new();
2142            let mut threshold_public_key: Option<V::Public> = None;
2143
2144            for (i_round, round) in self.rounds.into_iter().enumerate() {
2145                let previous_successful_round =
2146                    previous_output.as_ref().map(|o| o.players.len() as u32);
2147
2148                let dealer_set = round
2149                    .dealers
2150                    .iter()
2151                    .map(|&i| keys[i as usize].public_key())
2152                    .try_collect::<Set<_>>()
2153                    .unwrap();
2154                let player_set: Set<ed25519::PublicKey> = round
2155                    .players
2156                    .iter()
2157                    .map(|&i| keys[i as usize].public_key())
2158                    .try_collect()
2159                    .unwrap();
2160
2161                // Create round info
2162                let info = Info::new::<N3f1>(
2163                    b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
2164                    i_round as u64,
2165                    previous_output.clone(),
2166                    Default::default(),
2167                    dealer_set.clone(),
2168                    player_set.clone(),
2169                )?;
2170
2171                let mut players: Map<_, _> = round
2172                    .players
2173                    .iter()
2174                    .map(|&i| {
2175                        let sk = keys[i as usize].clone();
2176                        let pk = sk.public_key();
2177                        let player = Player::new(info.clone(), sk)?;
2178                        Ok((pk, player))
2179                    })
2180                    .collect::<anyhow::Result<Vec<_>>>()?
2181                    .try_into()
2182                    .unwrap();
2183                let mut acked_dealings: BTreeMap<
2184                    ed25519::PublicKey,
2185                    Vec<(ed25519::PublicKey, DealerPubMsg<V>, DealerPrivMsg)>,
2186                > = player_set
2187                    .iter()
2188                    .cloned()
2189                    .map(|pk| (pk, Vec::new()))
2190                    .collect();
2191                let mut crash_resume_by_dealer: BTreeMap<u32, Vec<u32>> = BTreeMap::new();
2192                for &(after_dealer, player) in &round.crash_resume_players {
2193                    crash_resume_by_dealer
2194                        .entry(after_dealer)
2195                        .or_default()
2196                        .push(player);
2197                }
2198                let mut resume_missing_msg_by_dealer: BTreeMap<u32, Vec<u32>> = BTreeMap::new();
2199                for &(after_dealer, missing_dealer) in &round.resume_missing_dealer_msg_fails {
2200                    resume_missing_msg_by_dealer
2201                        .entry(after_dealer)
2202                        .or_default()
2203                        .push(missing_dealer);
2204                }
2205
2206                // Run dealer protocol
2207                let mut dealer_logs = BTreeMap::new();
2208                for &i_dealer in &round.dealers {
2209                    let sk = keys[i_dealer as usize].clone();
2210                    let pk = sk.public_key();
2211                    let share = match (shares.get(&pk), round.replace_shares.contains(&i_dealer)) {
2212                        (None, _) => None,
2213                        (Some(s), false) => Some(s.clone()),
2214                        (Some(_), true) => Some(Share::new(
2215                            Participant::new(i_dealer),
2216                            Private::random(&mut rng),
2217                        )),
2218                    };
2219
2220                    // Start dealer (with potential modifications)
2221                    let (mut dealer, pub_msg, mut priv_msgs) =
2222                        if let Some(shift) = round.shift_degrees.get(&i_dealer) {
2223                            // Create dealer with shifted degree
2224                            let degree = u32::try_from(info.degree::<N3f1>() as i32 + shift.get())
2225                                .unwrap_or_default();
2226
2227                            // Manually create the dealer with adjusted polynomial
2228                            let share = info
2229                                .unwrap_or_random_share(
2230                                    &mut rng,
2231                                    share.map(|s| s.private.expose_unwrap()),
2232                                )
2233                                .expect("Failed to generate dealer share");
2234
2235                            let my_poly = Poly::new_with_constant(&mut rng, degree, share);
2236                            let priv_msgs = info
2237                                .players
2238                                .iter()
2239                                .map(|pk| {
2240                                    (
2241                                        pk.clone(),
2242                                        DealerPrivMsg::new(my_poly.eval_msm(
2243                                            &info.player_scalar(pk).expect("player should exist"),
2244                                            &Sequential,
2245                                        )),
2246                                    )
2247                                })
2248                                .collect::<Vec<_>>();
2249                            let results: Map<_, _> = priv_msgs
2250                                .iter()
2251                                .map(|(pk, pm)| (pk.clone(), AckOrReveal::Reveal(pm.clone())))
2252                                .try_collect()
2253                                .unwrap();
2254                            let commitment = Poly::commit(my_poly);
2255                            let pub_msg = DealerPubMsg { commitment };
2256                            let transcript = {
2257                                let t = transcript_for_round(&info);
2258                                transcript_for_ack(&t, &pk, &pub_msg)
2259                            };
2260                            let dealer = Dealer {
2261                                me: sk.clone(),
2262                                info: info.clone(),
2263                                pub_msg: pub_msg.clone(),
2264                                results,
2265                                transcript,
2266                            };
2267                            (dealer, pub_msg, priv_msgs)
2268                        } else {
2269                            Dealer::start::<N3f1>(&mut rng, info.clone(), sk.clone(), share)?
2270                        };
2271
2272                    // Apply BadShare perturbations
2273                    for (player, priv_msg) in &mut priv_msgs {
2274                        let player_key_idx = pk_to_key_idx[player];
2275                        if round.bad_shares.contains(&(i_dealer, player_key_idx)) {
2276                            *priv_msg = DealerPrivMsg::new(Scalar::random(&mut rng));
2277                        }
2278                    }
2279                    assert_eq!(priv_msgs.len(), players.len());
2280
2281                    // Process player acks
2282                    let mut num_reveals = players.len() as u32;
2283                    for (player_pk, priv_msg) in priv_msgs {
2284                        // Check priv msg encoding.
2285                        assert_eq!(priv_msg, ReadExt::read(&mut priv_msg.encode())?);
2286
2287                        let i_player = players
2288                            .index(&player_pk)
2289                            .ok_or_else(|| anyhow!("unknown player: {:?}", &player_pk))?;
2290                        let player_key_idx = pk_to_key_idx[&player_pk];
2291                        let player = &mut players.values_mut()[usize::from(i_player)];
2292                        let persisted = priv_msg.clone();
2293
2294                        let ack =
2295                            player.dealer_message::<N3f1>(pk.clone(), pub_msg.clone(), priv_msg);
2296                        assert_eq!(ack, ReadExt::read(&mut ack.encode())?);
2297                        if let Some(ack) = ack {
2298                            acked_dealings
2299                                .get_mut(&player_pk)
2300                                .expect("player should be present")
2301                                .push((pk.clone(), pub_msg.clone(), persisted));
2302                            let masks = round
2303                                .bad_player_sigs
2304                                .get(&(i_dealer, player_key_idx))
2305                                .cloned()
2306                                .unwrap_or_default();
2307                            let (modified, transcript) =
2308                                masks.transcript_for_player_ack(&info, &pk, &pub_msg)?;
2309                            assert_eq!(transcript.verify(&player_pk, &ack.sig), !modified);
2310
2311                            // Skip receiving ack if NoAck perturbation
2312                            if !round.no_acks.contains(&(i_dealer, player_key_idx)) {
2313                                dealer.receive_player_ack(player_pk, ack)?;
2314                                num_reveals -= 1;
2315                            }
2316                        } else {
2317                            assert!(
2318                                round.bad_shares.contains(&(i_dealer, player_key_idx))
2319                                    || round.bad(previous_successful_round.is_some(), i_dealer)
2320                            );
2321                        }
2322                    }
2323
2324                    // Finalize dealer
2325                    let signed_log = dealer.finalize::<N3f1>();
2326                    assert_eq!(
2327                        signed_log,
2328                        Read::read_cfg(&mut signed_log.encode(), &max_read_size)?
2329                    );
2330
2331                    // Check for BadDealerSig
2332                    let masks = round
2333                        .bad_dealer_sigs
2334                        .get(&i_dealer)
2335                        .cloned()
2336                        .unwrap_or_default();
2337                    let (modified, transcript) =
2338                        masks.transcript_for_signed_dealer_log(&info, &signed_log.log)?;
2339                    assert_eq!(transcript.verify(&pk, &signed_log.sig), !modified);
2340                    let (found_pk, mut log) = signed_log
2341                        .check(&info)
2342                        .ok_or_else(|| anyhow!("signed log should verify"))?;
2343                    assert_eq!(pk, found_pk);
2344                    // Apply BadReveal perturbations
2345                    match &mut log.results {
2346                        DealerResult::TooManyReveals => {
2347                            assert!(num_reveals > info.max_reveals::<N3f1>());
2348                        }
2349                        DealerResult::Ok(results) => {
2350                            assert_eq!(results.len(), players.len());
2351                            for &i_player in &round.players {
2352                                if !round.bad_reveals.contains(&(i_dealer, i_player)) {
2353                                    continue;
2354                                }
2355                                let player_pk = keys[i_player as usize].public_key();
2356                                *results
2357                                    .get_value_mut(&player_pk)
2358                                    .ok_or_else(|| anyhow!("unknown player: {:?}", &player_pk))? =
2359                                    AckOrReveal::Reveal(DealerPrivMsg::new(Scalar::random(
2360                                        &mut rng,
2361                                    )));
2362                            }
2363                        }
2364                    }
2365                    dealer_logs.insert(pk, log);
2366
2367                    // For selected checkpoints, omit a good dealer's private message and
2368                    // ensure resume reports corruption. Do not mutate player state.
2369                    for &missing_dealer in resume_missing_msg_by_dealer
2370                        .get(&i_dealer)
2371                        .into_iter()
2372                        .flatten()
2373                    {
2374                        assert!(
2375                            !round.bad(previous_successful_round.is_some(), missing_dealer),
2376                            "resume_missing_dealer_msg_fails requires dealer {missing_dealer} to be good"
2377                        );
2378                        let missing_pk = keys[missing_dealer as usize].public_key();
2379                        let missing_log = dealer_logs
2380                            .get(&missing_pk)
2381                            .unwrap_or_else(|| panic!("missing dealer log for {:?}", &missing_pk));
2382                        for &i_player in &round.players {
2383                            let player_pk = keys[i_player as usize].public_key();
2384                            let was_acked = missing_log.get_ack(&player_pk).is_some();
2385
2386                            let replay = acked_dealings
2387                                .get(&player_pk)
2388                                .cloned()
2389                                .expect("player should be present");
2390                            let replay_without = replay
2391                                .into_iter()
2392                                .filter(|(dealer, _, _)| dealer != &missing_pk);
2393                            let player_sk = keys[i_player as usize].clone();
2394                            let resumed = Player::resume::<N3f1>(
2395                                info.clone(),
2396                                player_sk,
2397                                &dealer_logs,
2398                                replay_without,
2399                            );
2400                            if was_acked {
2401                                assert!(
2402                                    matches!(resumed, Err(Error::MissingPlayerDealing)),
2403                                    "resume without dealer {missing_dealer} message should report MissingPlayerDealing for player {i_player}"
2404                                );
2405                            } else {
2406                                assert!(
2407                                    resumed.is_ok(),
2408                                    "resume without dealer {missing_dealer} message should succeed for unacked player {i_player}"
2409                                );
2410                            }
2411                        }
2412                    }
2413
2414                    // Crash/resume selected players after this dealer has finalized.
2415                    for &i_player in crash_resume_by_dealer.get(&i_dealer).into_iter().flatten() {
2416                        let player_pk = keys[i_player as usize].public_key();
2417                        let player_sk = keys[i_player as usize].clone();
2418                        let replay = acked_dealings
2419                            .get(&player_pk)
2420                            .cloned()
2421                            .expect("player should be present");
2422                        let (resumed, _) =
2423                            Player::resume::<N3f1>(info.clone(), player_sk, &dealer_logs, replay)
2424                                .expect("player resume perturbation should succeed");
2425                        *players
2426                            .get_value_mut(&player_pk)
2427                            .expect("player should be present") = resumed;
2428                    }
2429                }
2430
2431                // Make sure that bad dealers are not selected.
2432                let selection = select::<_, _, N3f1>(&info, dealer_logs.clone());
2433                if let Ok(ref selection) = selection {
2434                    let good_pks = selection
2435                        .iter_pairs()
2436                        .map(|(pk, _)| pk.clone())
2437                        .collect::<BTreeSet<_>>();
2438                    for &i_dealer in &round.dealers {
2439                        if round.bad(previous_successful_round.is_some(), i_dealer) {
2440                            assert!(!good_pks.contains(&keys[i_dealer as usize].public_key()));
2441                        }
2442                    }
2443                }
2444                // Run observer
2445                let observe_result =
2446                    observe::<_, _, N3f1>(info.clone(), dealer_logs.clone(), &Sequential);
2447                if round.expect_failure(previous_successful_round) {
2448                    assert!(
2449                        observe_result.is_err(),
2450                        "Round {i_round} should have failed but succeeded",
2451                    );
2452                    continue;
2453                }
2454                let observer_output = observe_result?;
2455                let selection = selection.expect("select should succeed if observe succeeded");
2456
2457                // Compute expected dealers: good dealers up to required_commitments
2458                // The select function iterates dealer_logs (BTreeMap) in public key order
2459                let required_commitments = info.required_commitments::<N3f1>() as usize;
2460                let expected_dealers: Set<ed25519::PublicKey> = dealer_set
2461                    .iter()
2462                    .filter(|pk| {
2463                        let i = keys.iter().position(|k| &k.public_key() == *pk).unwrap() as u32;
2464                        !round.bad(previous_successful_round.is_some(), i)
2465                    })
2466                    .take(required_commitments)
2467                    .cloned()
2468                    .try_collect()
2469                    .expect("dealers are unique");
2470                let expected_dealer_indices: BTreeSet<u32> = expected_dealers
2471                    .iter()
2472                    .filter_map(|pk| {
2473                        keys.iter()
2474                            .position(|k| &k.public_key() == pk)
2475                            .map(|i| i as u32)
2476                    })
2477                    .collect();
2478                assert_eq!(
2479                    observer_output.dealers(),
2480                    &expected_dealers,
2481                    "Output dealers should match expected good dealers"
2482                );
2483
2484                // Map selected dealers to their key indices (for later use)
2485                let selected_dealers: BTreeSet<u32> = selection
2486                    .keys()
2487                    .iter()
2488                    .filter_map(|pk| {
2489                        keys.iter()
2490                            .position(|k| &k.public_key() == pk)
2491                            .map(|i| i as u32)
2492                    })
2493                    .collect();
2494                assert_eq!(
2495                    selected_dealers, expected_dealer_indices,
2496                    "Selection should match expected dealers"
2497                );
2498                let selected_players: Set<ed25519::PublicKey> = round
2499                    .players
2500                    .iter()
2501                    .map(|&i| keys[i as usize].public_key())
2502                    .try_collect()
2503                    .expect("players are unique");
2504                for &i_player in &round.finalize_missing_dealer_msg_fails {
2505                    let player_pk = keys[i_player as usize].public_key();
2506                    let player_sk = keys[i_player as usize].clone();
2507                    let mut tested = 0u32;
2508                    for &dealer_idx in &selected_dealers {
2509                        if round.bad(previous_successful_round.is_some(), dealer_idx) {
2510                            continue;
2511                        }
2512                        let dealer_pk = keys[dealer_idx as usize].public_key();
2513                        let dealer_log = dealer_logs
2514                            .get(&dealer_pk)
2515                            .unwrap_or_else(|| panic!("missing dealer log for {:?}", &dealer_pk));
2516                        if dealer_log.get_ack(&player_pk).is_none() {
2517                            continue;
2518                        }
2519                        let replay = acked_dealings
2520                            .get(&player_pk)
2521                            .cloned()
2522                            .expect("player should be present");
2523                        let replay_without = replay
2524                            .into_iter()
2525                            .filter(|(dealer, _, _)| dealer != &dealer_pk);
2526                        let resume_logs: BTreeMap<_, _> = dealer_logs
2527                            .iter()
2528                            .filter(|(dealer, _)| *dealer != &dealer_pk)
2529                            .map(|(dealer, log)| (dealer.clone(), log.clone()))
2530                            .collect();
2531                        let (resumed, _) = Player::resume::<N3f1>(
2532                            info.clone(),
2533                            player_sk.clone(),
2534                            &resume_logs,
2535                            replay_without,
2536                        )
2537                        .expect("resume should succeed with stale logs");
2538                        let finalize_res =
2539                            resumed.finalize::<N3f1>(dealer_logs.clone(), &Sequential);
2540                        assert!(
2541                            matches!(finalize_res, Err(Error::MissingPlayerDealing)),
2542                            "finalize without dealer {dealer_idx} message should return MissingPlayerDealing for player {i_player}"
2543                        );
2544                        tested += 1;
2545                    }
2546                    assert!(
2547                        tested > 0,
2548                        "finalize_missing_dealer_msg_fails for player {i_player} tested no dealers"
2549                    );
2550                }
2551
2552                // Compute expected reveals
2553                //
2554                // Note: We use union of no_acks and bad_shares since each (dealer, player) pair
2555                // results in at most one reveal in the protocol, regardless of whether the player
2556                // didn't ack, got a bad share, or both.
2557                let mut expected_reveals: BTreeMap<ed25519::PublicKey, u32> = BTreeMap::new();
2558                for &(dealer_idx, player_key_idx) in round.no_acks.union(&round.bad_shares) {
2559                    if !selected_dealers.contains(&dealer_idx) {
2560                        continue;
2561                    }
2562                    let pk = keys[player_key_idx as usize].public_key();
2563                    if selected_players.position(&pk).is_none() {
2564                        continue;
2565                    }
2566                    *expected_reveals.entry(pk).or_insert(0) += 1;
2567                }
2568
2569                // Verify each player's revealed status
2570                let max_faults = selected_players.max_faults::<N3f1>();
2571                for player in player_set.iter() {
2572                    let expected = expected_reveals.get(player).copied().unwrap_or(0) > max_faults;
2573                    let actual = observer_output.revealed().position(player).is_some();
2574                    assert_eq!(expected, actual, "Unexpected outcome for player {player:?} (expected={expected}, actual={actual})");
2575                }
2576
2577                // Finalize each player
2578                for (player_pk, player) in players.into_iter() {
2579                    let (player_output, share) = player
2580                        .finalize::<N3f1>(dealer_logs.clone(), &Sequential)
2581                        .expect("Player finalize should succeed");
2582
2583                    assert_eq!(
2584                        player_output, observer_output,
2585                        "Player output should match observer output"
2586                    );
2587
2588                    // Verify share matches public polynomial
2589                    let expected_public = observer_output
2590                        .public
2591                        .partial_public(share.index)
2592                        .expect("share index should be valid");
2593                    let actual_public = share.public::<V>();
2594                    assert_eq!(
2595                        expected_public, actual_public,
2596                        "Share should match public polynomial"
2597                    );
2598
2599                    shares.insert(player_pk.clone(), share);
2600                }
2601
2602                // Initialize or verify threshold public key
2603                let current_public = *observer_output.public().public();
2604                match threshold_public_key {
2605                    None => threshold_public_key = Some(current_public),
2606                    Some(tpk) => {
2607                        assert_eq!(
2608                            tpk, current_public,
2609                            "Public key should remain constant across reshares"
2610                        );
2611                    }
2612                }
2613
2614                // Generate and verify threshold signature
2615                let test_message = format!("test message round {i_round}").into_bytes();
2616                let namespace = b"test";
2617
2618                let mut partial_sigs = Vec::new();
2619                for &i_player in &round.players {
2620                    let share = &shares[&keys[i_player as usize].public_key()];
2621                    let partial_sig = threshold::sign_message::<V>(share, namespace, &test_message);
2622
2623                    threshold::verify_message::<V>(
2624                        &observer_output.public,
2625                        namespace,
2626                        &test_message,
2627                        &partial_sig,
2628                    )
2629                    .expect("Partial signature verification should succeed");
2630
2631                    partial_sigs.push(partial_sig);
2632                }
2633
2634                let threshold = observer_output.quorum::<N3f1>();
2635                let threshold_sig = threshold::recover::<V, _, N3f1>(
2636                    &observer_output.public,
2637                    &partial_sigs[0..threshold as usize],
2638                    &Sequential,
2639                )
2640                .expect("Should recover threshold signature");
2641
2642                // Verify against the saved public key
2643                ops::verify_message::<V>(
2644                    threshold_public_key.as_ref().unwrap(),
2645                    namespace,
2646                    &test_message,
2647                    &threshold_sig,
2648                )
2649                .expect("Threshold signature verification should succeed");
2650
2651                // Update state for next round
2652                previous_output = Some(observer_output);
2653            }
2654            Ok(())
2655        }
2656    }
2657
2658    #[cfg(feature = "arbitrary")]
2659    mod impl_arbitrary {
2660        use super::*;
2661        use arbitrary::{Arbitrary, Unstructured};
2662        use core::ops::ControlFlow;
2663
2664        const MAX_NUM_PARTICIPANTS: u32 = 20;
2665        const MAX_ROUNDS: u32 = 10;
2666
2667        fn arbitrary_masks<'a>(u: &mut Unstructured<'a>) -> arbitrary::Result<Masks> {
2668            Ok(Masks {
2669                info_summary: Arbitrary::arbitrary(u)?,
2670                dealer: Arbitrary::arbitrary(u)?,
2671                pub_msg: Arbitrary::arbitrary(u)?,
2672                log: Arbitrary::arbitrary(u)?,
2673            })
2674        }
2675
2676        /// Pick at most `num` elements at random from `data`, returning them.
2677        ///
2678        /// This needs mutable access to perform a shuffle.
2679        ///
2680        fn pick<'a, T>(
2681            u: &mut Unstructured<'a>,
2682            num: usize,
2683            mut data: Vec<T>,
2684        ) -> arbitrary::Result<Vec<T>> {
2685            let len = data.len();
2686            let num = num.min(len);
2687            // Invariant: 0..start is a random subset of data.
2688            for start in 0..num {
2689                data.swap(start, u.int_in_range(start..=len - 1)?);
2690            }
2691            data.truncate(num);
2692            Ok(data)
2693        }
2694
2695        fn arbitrary_round<'a>(
2696            u: &mut Unstructured<'a>,
2697            num_participants: u32,
2698            last_successful_players: Option<&Set<u32>>,
2699        ) -> arbitrary::Result<Round> {
2700            let dealers = if let Some(players) = last_successful_players {
2701                let to_pick = u.int_in_range(players.quorum::<N3f1>() as usize..=players.len())?;
2702                pick(u, to_pick, players.into_iter().copied().collect())?
2703            } else {
2704                let to_pick = u.int_in_range(1..=num_participants as usize)?;
2705                pick(u, to_pick, (0..num_participants).collect())?
2706            };
2707            let players = {
2708                let to_pick = u.int_in_range(1..=num_participants as usize)?;
2709                pick(u, to_pick, (0..num_participants).collect())?
2710            };
2711            let pairs = dealers
2712                .iter()
2713                .flat_map(|d| players.iter().map(|p| (*d, *p)))
2714                .collect::<Vec<_>>();
2715            let pick_pair_set = |u: &mut Unstructured<'a>| {
2716                let num = u.int_in_range(0..=pairs.len())?;
2717                if num == 0 {
2718                    return Ok(BTreeSet::new());
2719                }
2720                Ok(pick(u, num, pairs.clone())?.into_iter().collect())
2721            };
2722            let pick_dealer_set = |u: &mut Unstructured<'a>| {
2723                let num = u.int_in_range(0..=dealers.len())?;
2724                if num == 0 {
2725                    return Ok(BTreeSet::new());
2726                }
2727                Ok(pick(u, num, dealers.clone())?.into_iter().collect())
2728            };
2729            let round = Round {
2730                crash_resume_players: BTreeSet::new(),
2731                resume_missing_dealer_msg_fails: BTreeSet::new(),
2732                finalize_missing_dealer_msg_fails: BTreeSet::new(),
2733                no_acks: pick_pair_set(u)?,
2734                bad_shares: pick_pair_set(u)?,
2735                bad_player_sigs: {
2736                    let indices = pick_pair_set(u)?;
2737                    indices
2738                        .into_iter()
2739                        .map(|k| Ok((k, arbitrary_masks(u)?)))
2740                        .collect::<arbitrary::Result<_>>()?
2741                },
2742                bad_reveals: pick_pair_set(u)?,
2743                bad_dealer_sigs: {
2744                    let indices = pick_dealer_set(u)?;
2745                    indices
2746                        .into_iter()
2747                        .map(|k| Ok((k, arbitrary_masks(u)?)))
2748                        .collect::<arbitrary::Result<_>>()?
2749                },
2750                replace_shares: pick_dealer_set(u)?,
2751                shift_degrees: {
2752                    let indices = pick_dealer_set(u)?;
2753                    indices
2754                        .into_iter()
2755                        .map(|k| {
2756                            let expected = N3f1::quorum(players.len()) as i32 - 1;
2757                            let shift = u.int_in_range(1..=expected.max(1))?;
2758                            let shift = if bool::arbitrary(u)? { -shift } else { shift };
2759                            Ok((k, NonZeroI32::new(shift).expect("checked to not be zero")))
2760                        })
2761                        .collect::<arbitrary::Result<_>>()?
2762                },
2763                dealers,
2764                players,
2765            };
2766            Ok(round)
2767        }
2768
2769        impl<'a> Arbitrary<'a> for Plan {
2770            fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
2771                let num_participants = u.int_in_range(1..=MAX_NUM_PARTICIPANTS)?;
2772                let mut rounds = Vec::new();
2773                let mut last_successful_players: Option<Set<u32>> = None;
2774                u.arbitrary_loop(None, Some(MAX_ROUNDS), |u| {
2775                    let round =
2776                        arbitrary_round(u, num_participants, last_successful_players.as_ref())?;
2777                    if !round
2778                        .expect_failure(last_successful_players.as_ref().map(|x| x.len() as u32))
2779                    {
2780                        last_successful_players =
2781                            Some(round.players.iter().copied().try_collect().unwrap());
2782                    }
2783                    rounds.push(round);
2784                    Ok(ControlFlow::Continue(()))
2785                })?;
2786                let plan = Self {
2787                    num_participants: NZU32!(num_participants),
2788                    rounds,
2789                };
2790                plan.validate()
2791                    .map_err(|_| arbitrary::Error::IncorrectFormat)?;
2792                Ok(plan)
2793            }
2794        }
2795    }
2796}
2797
2798#[cfg(feature = "arbitrary")]
2799pub use test_plan::Plan as FuzzPlan;
2800
2801#[cfg(test)]
2802mod test {
2803    use super::{test_plan::*, *};
2804    use crate::{bls12381::primitives::variant::MinPk, ed25519};
2805    use anyhow::anyhow;
2806    use commonware_math::algebra::Random;
2807    use commonware_utils::{test_rng, N3f1};
2808    use core::num::NonZeroI32;
2809
2810    #[test]
2811    fn single_round() -> anyhow::Result<()> {
2812        Plan::new(NZU32!(4))
2813            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2814            .run::<MinPk>(0)
2815    }
2816
2817    #[test]
2818    fn multiple_rounds() -> anyhow::Result<()> {
2819        Plan::new(NZU32!(4))
2820            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2821            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2822            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2823            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2824            .run::<MinPk>(0)
2825    }
2826
2827    #[test]
2828    fn player_crash_resume_after_dealer() -> anyhow::Result<()> {
2829        Plan::new(NZU32!(4))
2830            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).crash_resume_player(1, 2))
2831            .run::<MinPk>(0)
2832    }
2833
2834    #[test]
2835    fn resume_missing_good_dealer_message_fails_after_checkpoint() -> anyhow::Result<()> {
2836        Plan::new(NZU32!(4))
2837            .with(
2838                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
2839                    .resume_missing_dealer_msg_fails(2, 1),
2840            )
2841            .run::<MinPk>(0)
2842    }
2843
2844    #[test]
2845    fn resume_missing_good_dealer_message_skips_unacked_players() -> anyhow::Result<()> {
2846        Plan::new(NZU32!(4))
2847            .with(
2848                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
2849                    .no_ack(1, 0)
2850                    .resume_missing_dealer_msg_fails(2, 1),
2851            )
2852            .run::<MinPk>(0)
2853    }
2854
2855    #[test]
2856    fn finalize_fails_after_resume_without_good_dealer_message() -> anyhow::Result<()> {
2857        Plan::new(NZU32!(4))
2858            .with(
2859                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
2860                    .no_ack(0, 1)
2861                    .finalize_missing_dealer_msg_fails(0),
2862            )
2863            .run::<MinPk>(0)
2864    }
2865
2866    #[test]
2867    fn invalid_checkpoint_configs_fail_validation() {
2868        assert!(Plan::new(NZU32!(4))
2869            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).crash_resume_player(4, 2))
2870            .validate()
2871            .is_err());
2872        assert!(Plan::new(NZU32!(4))
2873            .with(
2874                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
2875                    .resume_missing_dealer_msg_fails(1, 2),
2876            )
2877            .validate()
2878            .is_err());
2879        assert!(Plan::new(NZU32!(4))
2880            .with(
2881                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
2882                    .bad_reveal(1, 0)
2883                    .resume_missing_dealer_msg_fails(2, 1),
2884            )
2885            .validate()
2886            .is_err());
2887    }
2888
2889    #[test]
2890    fn changing_committee() -> anyhow::Result<()> {
2891        Plan::new(NonZeroU32::new(5).unwrap())
2892            .with(Round::new(vec![0, 1, 2], vec![1, 2, 3]))
2893            .with(Round::new(vec![1, 2, 3], vec![2, 3, 4]))
2894            .with(Round::new(vec![2, 3, 4], vec![3, 4, 0]))
2895            .with(Round::new(vec![3, 4, 0], vec![4, 0, 1]))
2896            .run::<MinPk>(0)
2897    }
2898
2899    #[test]
2900    fn missing_ack() -> anyhow::Result<()> {
2901        // With 4 players, max_faults = 1, so 1 missing ack per dealer is OK
2902        Plan::new(NonZeroU32::new(4).unwrap())
2903            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 0))
2904            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 1))
2905            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 2))
2906            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 3))
2907            .run::<MinPk>(0)
2908    }
2909
2910    #[test]
2911    fn increasing_decreasing_committee() -> anyhow::Result<()> {
2912        Plan::new(NonZeroU32::new(5).unwrap())
2913            .with(Round::new(vec![0, 1], vec![0, 1, 2]))
2914            .with(Round::new(vec![0, 1, 2], vec![0, 1, 2, 3]))
2915            .with(Round::new(vec![0, 1, 2], vec![0, 1]))
2916            .with(Round::new(vec![0, 1], vec![0, 1, 2, 3, 4]))
2917            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1]))
2918            .run::<MinPk>(0)
2919    }
2920
2921    #[test]
2922    fn bad_reveal_fails() -> anyhow::Result<()> {
2923        Plan::new(NonZeroU32::new(4).unwrap())
2924            .with(Round::new(vec![0], vec![0, 1, 2, 3]).bad_reveal(0, 1))
2925            .run::<MinPk>(0)
2926    }
2927
2928    #[test]
2929    fn bad_share() -> anyhow::Result<()> {
2930        Plan::new(NonZeroU32::new(4).unwrap())
2931            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).bad_share(0, 1))
2932            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).bad_share(0, 2))
2933            .run::<MinPk>(0)
2934    }
2935
2936    #[test]
2937    fn shift_degree_fails() -> anyhow::Result<()> {
2938        Plan::new(NonZeroU32::new(4).unwrap())
2939            .with(Round::new(vec![0], vec![0, 1, 2, 3]).shift_degree(
2940                0,
2941                NonZeroI32::new(1).ok_or_else(|| anyhow!("invalid NZI32"))?,
2942            ))
2943            .run::<MinPk>(0)
2944    }
2945
2946    #[test]
2947    fn replace_share_fails() -> anyhow::Result<()> {
2948        Plan::new(NonZeroU32::new(4).unwrap())
2949            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2950            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).replace_share(0))
2951            .run::<MinPk>(0)
2952    }
2953
2954    #[test]
2955    fn too_many_reveals_dealer() -> anyhow::Result<()> {
2956        Plan::new(NonZeroU32::new(4).unwrap())
2957            .with(
2958                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
2959                    .no_ack(0, 0)
2960                    .no_ack(0, 1),
2961            )
2962            .run::<MinPk>(0)
2963    }
2964
2965    #[test]
2966    fn too_many_reveals_player() -> anyhow::Result<()> {
2967        Plan::new(NonZeroU32::new(4).unwrap())
2968            .with(
2969                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
2970                    .no_ack(0, 0)
2971                    .no_ack(1, 0)
2972                    .no_ack(3, 0),
2973            )
2974            .run::<MinPk>(0)
2975    }
2976
2977    #[test]
2978    fn bad_sigs() -> anyhow::Result<()> {
2979        Plan::new(NonZeroU32::new(4).unwrap())
2980            .with(
2981                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
2982                    .bad_dealer_sig(
2983                        0,
2984                        Masks {
2985                            log: vec![0xFF; 8],
2986                            ..Default::default()
2987                        },
2988                    )
2989                    .bad_player_sig(
2990                        0,
2991                        1,
2992                        Masks {
2993                            pub_msg: vec![0xFF; 8],
2994                            ..Default::default()
2995                        },
2996                    ),
2997            )
2998            .run::<MinPk>(0)
2999    }
3000
3001    #[test]
3002    fn issue_2745_regression() -> anyhow::Result<()> {
3003        Plan::new(NonZeroU32::new(6).unwrap())
3004            .with(
3005                Round::new(vec![0], vec![5, 1, 3, 0, 4])
3006                    .no_ack(0, 5)
3007                    .bad_share(0, 5),
3008            )
3009            .with(Round::new(vec![0, 1, 3, 4], vec![0]))
3010            .with(Round::new(vec![0], vec![0]))
3011            .run::<MinPk>(0)
3012    }
3013
3014    #[test]
3015    fn signed_dealer_log_commitment() -> Result<(), Error> {
3016        let sk = ed25519::PrivateKey::from_seed(0);
3017        let pk = sk.public_key();
3018        let info = Info::<MinPk, _>::new::<N3f1>(
3019            b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3020            0,
3021            None,
3022            Default::default(),
3023            vec![sk.public_key()].try_into().unwrap(),
3024            vec![sk.public_key()].try_into().unwrap(),
3025        )?;
3026        let mut log0 = {
3027            let (dealer, _, _) =
3028                Dealer::start::<N3f1>(&mut test_rng(), info.clone(), sk.clone(), None)?;
3029            dealer.finalize::<N3f1>()
3030        };
3031        let mut log1 = {
3032            let (mut dealer, pub_msg, priv_msgs) =
3033                Dealer::start::<N3f1>(&mut test_rng(), info.clone(), sk.clone(), None)?;
3034            let mut player = Player::new(info.clone(), sk)?;
3035            let ack = player
3036                .dealer_message::<N3f1>(pk.clone(), pub_msg, priv_msgs[0].1.clone())
3037                .unwrap();
3038            dealer.receive_player_ack(pk, ack)?;
3039            dealer.finalize::<N3f1>()
3040        };
3041        std::mem::swap(&mut log0.log, &mut log1.log);
3042        assert!(log0.check(&info).is_none());
3043        assert!(log1.check(&info).is_none());
3044
3045        Ok(())
3046    }
3047
3048    #[test]
3049    fn info_with_different_mode_is_not_equal() -> Result<(), Error> {
3050        let sk = ed25519::PrivateKey::from_seed(0);
3051        let pk = sk.public_key();
3052        let dealers: Set<ed25519::PublicKey> = vec![pk.clone()].try_into().unwrap();
3053        let players: Set<ed25519::PublicKey> = vec![pk].try_into().unwrap();
3054
3055        let default_mode_info = Info::<MinPk, _>::new::<N3f1>(
3056            b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3057            0,
3058            None,
3059            Mode::default(),
3060            dealers.clone(),
3061            players.clone(),
3062        )?;
3063        let roots_of_unity_mode_info = Info::<MinPk, _>::new::<N3f1>(
3064            b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3065            0,
3066            None,
3067            Mode::RootsOfUnity,
3068            dealers,
3069            players,
3070        )?;
3071
3072        assert_ne!(default_mode_info, roots_of_unity_mode_info);
3073        Ok(())
3074    }
3075
3076    #[test]
3077    fn resume_ignores_invalid_logged_ack_signature() -> Result<(), Error> {
3078        let dealer_sk = ed25519::PrivateKey::from_seed(11);
3079        let dealer_pk = dealer_sk.public_key();
3080        let player_sk = ed25519::PrivateKey::from_seed(22);
3081        let player_pk = player_sk.public_key();
3082        let dealers: Set<ed25519::PublicKey> = vec![dealer_pk.clone()].try_into().unwrap();
3083        let players: Set<ed25519::PublicKey> = vec![player_pk.clone()].try_into().unwrap();
3084        let info = Info::<MinPk, _>::new::<N3f1>(
3085            b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3086            0,
3087            None,
3088            Default::default(),
3089            dealers.clone(),
3090            players.clone(),
3091        )?;
3092        let wrong_round_info = Info::<MinPk, _>::new::<N3f1>(
3093            b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3094            1,
3095            None,
3096            Default::default(),
3097            dealers,
3098            players,
3099        )?;
3100        let (_, pub_msg, _) =
3101            Dealer::start::<N3f1>(&mut test_rng(), info.clone(), dealer_sk, None)?;
3102        let bad_ack = PlayerAck {
3103            sig: transcript_for_ack(
3104                &transcript_for_round(&wrong_round_info),
3105                &dealer_pk,
3106                &pub_msg,
3107            )
3108            .sign(&player_sk),
3109        };
3110        let results: Map<_, _> = vec![(player_pk, AckOrReveal::Ack(bad_ack))]
3111            .into_iter()
3112            .try_collect()
3113            .unwrap();
3114        let mut logs = BTreeMap::new();
3115        logs.insert(
3116            dealer_pk,
3117            DealerLog {
3118                pub_msg,
3119                results: DealerResult::Ok(results),
3120            },
3121        );
3122
3123        let resumed = Player::resume::<N3f1>(info, player_sk, &logs, []);
3124        assert!(resumed.is_ok());
3125        let (_, acks) = resumed.unwrap();
3126        assert!(acks.is_empty());
3127
3128        Ok(())
3129    }
3130
3131    #[test]
3132    fn test_dealer_priv_msg_redacted() {
3133        let mut rng = test_rng();
3134        let msg = DealerPrivMsg::new(Scalar::random(&mut rng));
3135        let debug = format!("{:?}", msg);
3136        assert!(debug.contains("REDACTED"));
3137    }
3138
3139    #[cfg(feature = "arbitrary")]
3140    mod conformance {
3141        use super::*;
3142        use commonware_codec::conformance::CodecConformance;
3143
3144        commonware_conformance::conformance_tests! {
3145            CodecConformance<Output<MinPk, ed25519::PublicKey>>,
3146            CodecConformance<DealerPubMsg<MinPk>>,
3147            CodecConformance<DealerPrivMsg>,
3148            CodecConformance<PlayerAck<ed25519::PublicKey>>,
3149            CodecConformance<AckOrReveal<ed25519::PublicKey>>,
3150            CodecConformance<DealerResult<ed25519::PublicKey>>,
3151            CodecConformance<DealerLog<MinPk, ed25519::PublicKey>>,
3152            CodecConformance<SignedDealerLog<MinPk, ed25519::PrivateKey>>,
3153        }
3154    }
3155}