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 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 shares
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 share 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 shares
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 share 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 shares,
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//! # Caveats
102//!
103//! ## Synchrony Assumption
104//!
105//! Under synchrony (where `t` is the maximum amount of time it takes for a message to be sent between any two participants),
106//! this construction can be used to maintain a shared secret where at least `f + 1` honest players must participate to
107//! recover the shared secret (`2f + 1` threshold where at most `f` players are Byzantine). To see how this is true,
108//! first consider that in any successful round there must exist `2f + 1` commitments with at most `f` reveals. This implies
109//! that all players must have acknowledged or have access to a reveal for each of the `2f + 1` selected commitments (allowing
110//! them to derive their share). Next, consider that when the network is synchronous that all `2f + 1` honest players send
111//! acknowledgements to honest dealers before `2t`. Because `2f + 1` commitments must be chosen, at least `f + 1` commitments
112//! must be from honest dealers (where no honest player dealing is revealed). Even if the remaining `f` commitments are from
113//! Byzantine dealers, there will not be enough dealings to recover the derived share of any honest player (at most `f` of
114//! `2f + 1` dealings publicly revealed). Given all `2f + 1` honest players have access to their shares and it is not possible
115//! for a Byzantine player to derive any honest player's share, this claim holds.
116//!
117//! If the network is not synchronous, however, Byzantine players can collude to recover a shared secret with the
118//! participation of a single honest player (rather than `f + 1`) and `f + 1` honest players will each be able to derive
119//! the shared secret (if the Byzantine players reveal their shares). To see how this could be, consider a network where
120//! `f` honest participants are in one partition and (`f + 1` honest and `f` Byzantine participants) are in another. All
121//! `f` Byzantine players acknowledge dealings from the `f + 1` honest dealers. Participants in the second partition will
122//! complete a round and all the reveals will belong to the same set of `f` honest players (that are in the first partition).
123//! A colluding Byzantine adversary will then have access to their acknowledged `f` shares and the revealed `f` shares
124//! (requiring only the participation of a single honest player that was in their partition to recover the shared secret).
125//! If the Byzantine adversary reveals all of their (still private) shares at this time, each of the `f + 1` honest players
126//! that were in the second partition will be able to derive the shared secret without collusion (using their private share
127//! and the `2f` public shares). It will not be possible for any external observer, however, to recover the shared secret.
128//!
129//! ### Future Work: Dropping the Synchrony Assumption?
130//!
131//! It is possible to design a DKG/Resharing scheme that maintains a shared secret where at least `f + 1` honest players
132//! must participate to recover the shared secret that doesn't require a synchrony assumption (`2f + 1` threshold
133//! where at most `f` players are Byzantine). However, known constructions that satisfy this requirement require both
134//! broadcasting encrypted dealings publicly and employing Zero-Knowledge Proofs (ZKPs) to attest that encrypted dealings
135//! were generated correctly ([Groth21](https://eprint.iacr.org/2021/339), [Kate23](https://eprint.iacr.org/2023/451)).
136//!
137//! As of January 2025, these constructions are still considered novel (2-3 years in production), require stronger
138//! cryptographic assumptions, don't scale to hundreds of participants (unless dealers have powerful hardware), and provide
139//! observers the opportunity to brute force decrypt shares (even if honest players are online).
140//!
141//! ## Handling Complaints
142//!
143//! This crate does not provide an integrated mechanism for tracking complaints from players (of malicious dealers). However, it is
144//! possible to implement your own mechanism and to manually disqualify dealers from a given round in the arbiter. This decision was made
145//! because the mechanism for communicating commitments/shares/acknowledgements is highly dependent on the context in which this
146//! construction is used.
147//!
148//! In practice:
149//! - [`Player::dealer_message`] returns `None` for invalid messages (implicit complaint)
150//! - [`Dealer::receive_player_ack`] validates acknowledgements
151//! - Other custom mechanisms can exclude dealers before calling [`observe`] or [`Player::finalize`],
152//!   to enforce other rules for "misbehavior" beyond what the DKG does already.
153//!
154//! ## Non-Uniform Distribution
155//!
156//! The Joint-Feldman DKG protocol does not guarantee a uniformly random secret key is generated. An adversary
157//! can introduce `O(lg N)` bits of bias into the key with `O(poly(N))` amount of computation. For uses
158//! like signing, threshold encryption, where the security of the scheme reduces to that of
159//! the underlying assumption that cryptographic constructions using the curve are secure (i.e.
160//! that the Discrete Logarithm Problem, or stronger variants, are hard), then this caveat does
161//! not affect the security of the scheme. This must be taken into account when integrating this
162//! component into more esoteric schemes.
163//!
164//! This choice was explicitly made, because the best known protocols guaranteeing a uniform output
165//! require an extra round of broadcast ([GJKR02](https://www.researchgate.net/publication/2558744_Revisiting_the_Distributed_Key_Generation_for_Discrete-Log_Based_Cryptosystems),
166//! [BK25](https://eprint.iacr.org/2025/819)).
167//!
168//! ## Share Reveals
169//!
170//! In order to prevent malicious dealers from withholding shares from players, we
171//! require the dealers reveal the shares for which they did not receive acks.
172//! Because of the synchrony assumption above, this will only happen if either:
173//! - the dealer is malicious, not sending a share, but honestly revealing,
174//! - or, the player is malicious, not sending an ack when they should.
175//!
176//! Thus, for honest players, in the worst case, `f` reveals get created, because
177//! they correctly did not ack the `f` malicious dealers who failed to send them
178//! a share. In that case, their final share remains secret, because it is the linear
179//! combination of at least `f + 1` shares received from dealers.
180//!
181//! # Example
182//!
183//! ```
184//! use commonware_cryptography::bls12381::{
185//!     dkg::{Dealer, Player, Info, SignedDealerLog, observe},
186//!     primitives::{variant::MinSig, sharing::Mode},
187//! };
188//! use commonware_cryptography::{ed25519, Signer};
189//! use commonware_math::algebra::Random;
190//! use commonware_utils::{ordered::Set, TryCollect, N3f1};
191//! use std::collections::BTreeMap;
192//! use rand::SeedableRng;
193//! use rand_chacha::ChaCha8Rng;
194//!
195//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
196//! let mut rng = ChaCha8Rng::seed_from_u64(42);
197//!
198//! // Generate 4 Ed25519 private keys for participants
199//! let mut private_keys = Vec::new();
200//! for _ in 0..4 {
201//!     let private_key = ed25519::PrivateKey::random(&mut rng);
202//!     private_keys.push(private_key);
203//! }
204//!
205//! // All 4 participants are both dealers and players in initial DKG
206//! let dealer_set: Set<ed25519::PublicKey> = private_keys.iter()
207//!     .map(|k| k.public_key())
208//!     .try_collect()?;
209//! let player_set = dealer_set.clone();
210//!
211//! // Step 1: Create round info for initial DKG
212//! let info = Info::<MinSig, ed25519::PublicKey>::new::<N3f1>(
213//!     b"application-namespace",
214//!     0,                        // round number
215//!     None,                     // no previous output (initial DKG)
216//!     Mode::default(),   // sharing mode
217//!     dealer_set.clone(),       // dealers
218//!     player_set.clone(),       // players
219//! )?;
220//!
221//! // Step 2: Initialize players
222//! let mut players = BTreeMap::new();
223//! for private_key in &private_keys {
224//!     let player = Player::<MinSig, ed25519::PrivateKey>::new(
225//!         info.clone(),
226//!         private_key.clone(),
227//!     )?;
228//!     players.insert(private_key.public_key(), player);
229//! }
230//!
231//! // Step 3: Run dealer protocol for each participant
232//! let mut dealer_logs = BTreeMap::new();
233//! for dealer_priv in &private_keys {
234//!     // Each dealer generates messages for all players
235//!     let (mut dealer, pub_msg, priv_msgs) = Dealer::start::<N3f1>(
236//!         &mut rng,
237//!         info.clone(),
238//!         dealer_priv.clone(),
239//!         None,  // no previous share for initial DKG
240//!     )?;
241//!
242//!     // Distribute messages to players and collect acknowledgements
243//!     for (player_pk, priv_msg) in priv_msgs {
244//!         if let Some(player) = players.get_mut(&player_pk) {
245//!             if let Some(ack) = player.dealer_message::<N3f1>(
246//!                 dealer_priv.public_key(),
247//!                 pub_msg.clone(),
248//!                 priv_msg,
249//!             ) {
250//!                 dealer.receive_player_ack(player_pk, ack)?;
251//!             }
252//!         }
253//!     }
254//!
255//!     // Finalize dealer and verify log
256//!     let signed_log = dealer.finalize::<N3f1>();
257//!     if let Some((dealer_pk, log)) = signed_log.check(&info) {
258//!         dealer_logs.insert(dealer_pk, log);
259//!     }
260//! }
261//!
262//! // Step 4: Players finalize to get their shares
263//! let mut player_shares = BTreeMap::new();
264//! for (player_pk, player) in players {
265//!     let (output, share) = player.finalize::<N3f1>(
266//!       dealer_logs.clone(),
267//!       &commonware_parallel::Sequential,
268//!     )?;
269//!     println!("Player {:?} got share at index {}", player_pk, share.index);
270//!     player_shares.insert(player_pk, share);
271//! }
272//!
273//! // Step 5: Observer can also compute the public output
274//! let observer_output = observe::<MinSig, ed25519::PublicKey, N3f1>(
275//!     info,
276//!     dealer_logs,
277//!     &commonware_parallel::Sequential,
278//! )?;
279//! println!("DKG completed with threshold {}", observer_output.quorum::<N3f1>());
280//! # Ok(())
281//! # }
282//! ```
283//!
284//! For a complete example with resharing, see [commonware-reshare](https://docs.rs/commonware-reshare).
285
286use super::primitives::group::{Private, Share};
287use crate::{
288    bls12381::primitives::{
289        group::Scalar,
290        sharing::{Mode, Sharing},
291        variant::Variant,
292    },
293    transcript::{Summary, Transcript},
294    PublicKey, Secret, Signer,
295};
296use commonware_codec::{Encode, EncodeSize, RangeCfg, Read, ReadExt, Write};
297use commonware_math::{
298    algebra::{Additive, CryptoGroup, Random},
299    poly::{Interpolator, Poly},
300};
301use commonware_parallel::{Sequential, Strategy};
302#[cfg(feature = "arbitrary")]
303use commonware_utils::N3f1;
304use commonware_utils::{
305    ordered::{Map, Quorum, Set},
306    Faults, Participant, TryCollect, NZU32,
307};
308use core::num::NonZeroU32;
309use rand_core::CryptoRngCore;
310use std::collections::BTreeMap;
311use thiserror::Error;
312
313const NAMESPACE: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG";
314const SIG_ACK: &[u8] = b"ack";
315const SIG_LOG: &[u8] = b"log";
316
317/// The error type for the DKG protocol.
318///
319/// The only error which can happen through no fault of your own is
320/// [`Error::DkgFailed`]. Everything else only happens if you use a configuration
321/// for [`Info`] or [`Dealer`] which is invalid in some way.
322#[derive(Debug, Error)]
323pub enum Error {
324    #[error("missing dealer's share from the previous round")]
325    MissingDealerShare,
326    #[error("player is not present in the list of players")]
327    UnknownPlayer,
328    #[error("dealer is not present in the previous list of players")]
329    UnknownDealer(String),
330    #[error("invalid number of dealers: {0}")]
331    NumDealers(usize),
332    #[error("invalid number of players: {0}")]
333    NumPlayers(usize),
334    #[error("dkg failed for some reason")]
335    DkgFailed,
336}
337
338/// The output of a successful DKG.
339#[derive(Debug, Clone, PartialEq, Eq)]
340pub struct Output<V: Variant, P> {
341    summary: Summary,
342    public: Sharing<V>,
343    dealers: Set<P>,
344    players: Set<P>,
345    revealed: Set<P>,
346}
347
348impl<V: Variant, P: Ord> Output<V, P> {
349    fn share_commitment(&self, player: &P) -> Option<V::Public> {
350        self.public.partial_public(self.players.index(player)?).ok()
351    }
352
353    /// Return the quorum, i.e. the number of players needed to reconstruct the key.
354    pub fn quorum<M: Faults>(&self) -> u32 {
355        self.players.quorum::<M>()
356    }
357
358    /// Get the public polynomial associated with this output.
359    ///
360    /// This is useful for verifying partial signatures, with [crate::bls12381::primitives::ops::threshold::verify_message].
361    pub const fn public(&self) -> &Sharing<V> {
362        &self.public
363    }
364
365    /// Return the dealers who were selected in this round of the DKG.
366    pub const fn dealers(&self) -> &Set<P> {
367        &self.dealers
368    }
369
370    /// Return the players who participated in this round of the DKG, and should have shares.
371    pub const fn players(&self) -> &Set<P> {
372        &self.players
373    }
374
375    /// Return the set of players whose shares may have been revealed.
376    ///
377    /// These are players who had more than `max_faults` reveals.
378    pub const fn revealed(&self) -> &Set<P> {
379        &self.revealed
380    }
381}
382
383impl<V: Variant, P: PublicKey> EncodeSize for Output<V, P> {
384    fn encode_size(&self) -> usize {
385        self.summary.encode_size()
386            + self.public.encode_size()
387            + self.dealers.encode_size()
388            + self.players.encode_size()
389            + self.revealed.encode_size()
390    }
391}
392
393impl<V: Variant, P: PublicKey> Write for Output<V, P> {
394    fn write(&self, buf: &mut impl bytes::BufMut) {
395        self.summary.write(buf);
396        self.public.write(buf);
397        self.dealers.write(buf);
398        self.players.write(buf);
399        self.revealed.write(buf);
400    }
401}
402
403impl<V: Variant, P: PublicKey> Read for Output<V, P> {
404    type Cfg = NonZeroU32;
405
406    fn read_cfg(
407        buf: &mut impl bytes::Buf,
408        &max_participants: &Self::Cfg,
409    ) -> Result<Self, commonware_codec::Error> {
410        let max_participants_usize = max_participants.get() as usize;
411        Ok(Self {
412            summary: ReadExt::read(buf)?,
413            public: Read::read_cfg(buf, &max_participants)?,
414            dealers: Read::read_cfg(buf, &(RangeCfg::new(1..=max_participants_usize), ()))?, // at least one dealer must be part of a dealing
415            players: Read::read_cfg(buf, &(RangeCfg::new(1..=max_participants_usize), ()))?, // at least one player must be part of a dealing
416            revealed: Read::read_cfg(buf, &(RangeCfg::new(0..=max_participants_usize), ()))?, // there may not be any reveals
417        })
418    }
419}
420
421#[cfg(feature = "arbitrary")]
422impl<P: PublicKey, V: Variant> arbitrary::Arbitrary<'_> for Output<V, P>
423where
424    P: for<'a> arbitrary::Arbitrary<'a> + Ord,
425    V::Public: for<'a> arbitrary::Arbitrary<'a>,
426{
427    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
428        let summary = u.arbitrary()?;
429        let public: Sharing<V> = u.arbitrary()?;
430        let total = public.total().get() as usize;
431
432        let num_dealers = u.int_in_range(1..=total * 2)?;
433        let dealers = Set::try_from(
434            u.arbitrary_iter::<P>()?
435                .take(num_dealers)
436                .collect::<Result<Vec<_>, _>>()?,
437        )
438        .map_err(|_| arbitrary::Error::IncorrectFormat)?;
439
440        let players = Set::try_from(
441            u.arbitrary_iter::<P>()?
442                .take(total)
443                .collect::<Result<Vec<_>, _>>()?,
444        )
445        .map_err(|_| arbitrary::Error::IncorrectFormat)?;
446
447        let max_revealed = N3f1::max_faults(total) as usize;
448        let revealed = Set::from_iter_dedup(
449            players
450                .iter()
451                .filter(|_| u.arbitrary::<bool>().unwrap_or(false))
452                .take(max_revealed)
453                .cloned(),
454        );
455
456        Ok(Self {
457            summary,
458            public,
459            dealers,
460            players,
461            revealed,
462        })
463    }
464}
465
466/// Information about the current round of the DKG.
467///
468/// This is used to bind signatures to the current round, and to provide the
469/// information that dealers, players, and observers need to perform their actions.
470#[derive(Debug, Clone)]
471pub struct Info<V: Variant, P: PublicKey> {
472    summary: Summary,
473    round: u64,
474    previous: Option<Output<V, P>>,
475    mode: Mode,
476    dealers: Set<P>,
477    players: Set<P>,
478}
479
480impl<V: Variant, P: PublicKey> PartialEq for Info<V, P> {
481    fn eq(&self, other: &Self) -> bool {
482        self.summary == other.summary
483    }
484}
485
486impl<V: Variant, P: PublicKey> Info<V, P> {
487    /// Figure out what the dealer share should be.
488    ///
489    /// If there's no previous round, we need a random value, hence `rng`.
490    ///
491    /// However, if there is a previous round, we expect a share, hence `Result`.
492    fn unwrap_or_random_share(
493        &self,
494        mut rng: impl CryptoRngCore,
495        share: Option<Scalar>,
496    ) -> Result<Scalar, Error> {
497        let out = match (self.previous.as_ref(), share) {
498            (None, None) => Scalar::random(&mut rng),
499            (_, Some(x)) => x,
500            (Some(_), None) => return Err(Error::MissingDealerShare),
501        };
502        Ok(out)
503    }
504
505    const fn num_players(&self) -> NonZeroU32 {
506        // Will not panic because we check that the number of players is non-empty in `new`
507        NZU32!(self.players.len() as u32)
508    }
509
510    fn degree<M: Faults>(&self) -> u32 {
511        self.players.quorum::<M>().saturating_sub(1)
512    }
513
514    fn required_commitments<M: Faults>(&self) -> u32 {
515        let dealer_quorum = self.dealers.quorum::<M>();
516        let prev_quorum = self
517            .previous
518            .as_ref()
519            .map(Output::quorum::<M>)
520            .unwrap_or(u32::MIN);
521        dealer_quorum.max(prev_quorum)
522    }
523
524    fn max_reveals<M: Faults>(&self) -> u32 {
525        self.players.max_faults::<M>()
526    }
527
528    fn player_index(&self, player: &P) -> Result<Participant, Error> {
529        self.players.index(player).ok_or(Error::UnknownPlayer)
530    }
531
532    fn dealer_index(&self, dealer: &P) -> Result<Participant, Error> {
533        self.dealers
534            .index(dealer)
535            .ok_or(Error::UnknownDealer(format!("{dealer:?}")))
536    }
537
538    fn player_scalar(&self, player: &P) -> Result<Scalar, Error> {
539        Ok(self
540            .mode
541            .scalar(self.num_players(), self.player_index(player)?)
542            .expect("player index should be < num_players"))
543    }
544
545    #[must_use]
546    fn check_dealer_pub_msg<M: Faults>(&self, dealer: &P, pub_msg: &DealerPubMsg<V>) -> bool {
547        if self.degree::<M>() != pub_msg.commitment.degree_exact() {
548            return false;
549        }
550        if let Some(previous) = self.previous.as_ref() {
551            let Some(share_commitment) = previous.share_commitment(dealer) else {
552                return false;
553            };
554            if *pub_msg.commitment.constant() != share_commitment {
555                return false;
556            }
557        }
558        true
559    }
560
561    #[must_use]
562    fn check_dealer_priv_msg(
563        &self,
564        player: &P,
565        pub_msg: &DealerPubMsg<V>,
566        priv_msg: &DealerPrivMsg,
567    ) -> bool {
568        let Ok(scalar) = self.player_scalar(player) else {
569            return false;
570        };
571        let expected = pub_msg.commitment.eval_msm(&scalar, &Sequential);
572        priv_msg
573            .share
574            .expose(|share| expected == V::Public::generator() * share)
575    }
576}
577
578impl<V: Variant, P: PublicKey> Info<V, P> {
579    /// Create a new [`Info`].
580    ///
581    /// `namespace` must be provided to isolate different applications
582    /// performing DKGs from each other.
583    /// `round` should be a counter, always incrementing, even for failed DKGs.
584    /// `previous` should be the result of the previous successful DKG.
585    /// `dealers` should be the list of public keys for the dealers. This MUST
586    /// be a subset of the previous round's players.
587    /// `players` should be the list of public keys for the players.
588    pub fn new<M: Faults>(
589        namespace: &[u8],
590        round: u64,
591        previous: Option<Output<V, P>>,
592        mode: Mode,
593        dealers: Set<P>,
594        players: Set<P>,
595    ) -> Result<Self, Error> {
596        let participant_range = 1..u32::MAX as usize;
597        if !participant_range.contains(&dealers.len()) {
598            return Err(Error::NumDealers(dealers.len()));
599        }
600        if !participant_range.contains(&players.len()) {
601            return Err(Error::NumPlayers(players.len()));
602        }
603        if let Some(previous) = previous.as_ref() {
604            if let Some(unknown) = dealers
605                .iter()
606                .find(|d| previous.players.position(d).is_none())
607            {
608                return Err(Error::UnknownDealer(format!("{unknown:?}")));
609            }
610            if dealers.len() < previous.quorum::<M>() as usize {
611                return Err(Error::NumDealers(dealers.len()));
612            }
613        }
614        let summary = Transcript::new(NAMESPACE)
615            .commit(namespace)
616            .commit(round.encode())
617            .commit(previous.encode())
618            .commit(dealers.encode())
619            .commit(players.encode())
620            .summarize();
621        Ok(Self {
622            summary,
623            round,
624            previous,
625            mode,
626            dealers,
627            players,
628        })
629    }
630
631    /// Return the round number for this round.
632    ///
633    /// Round numbers should increase sequentially.
634    pub const fn round(&self) -> u64 {
635        self.round
636    }
637}
638
639#[derive(Clone, Debug)]
640pub struct DealerPubMsg<V: Variant> {
641    commitment: Poly<V::Public>,
642}
643
644impl<V: Variant> PartialEq for DealerPubMsg<V> {
645    fn eq(&self, other: &Self) -> bool {
646        self.commitment == other.commitment
647    }
648}
649
650impl<V: Variant> Eq for DealerPubMsg<V> {}
651
652impl<V: Variant> EncodeSize for DealerPubMsg<V> {
653    fn encode_size(&self) -> usize {
654        self.commitment.encode_size()
655    }
656}
657
658impl<V: Variant> Write for DealerPubMsg<V> {
659    fn write(&self, buf: &mut impl bytes::BufMut) {
660        self.commitment.write(buf);
661    }
662}
663
664impl<V: Variant> Read for DealerPubMsg<V> {
665    type Cfg = NonZeroU32;
666
667    fn read_cfg(
668        buf: &mut impl bytes::Buf,
669        &max_size: &Self::Cfg,
670    ) -> Result<Self, commonware_codec::Error> {
671        Ok(Self {
672            commitment: Read::read_cfg(buf, &(RangeCfg::from(NZU32!(1)..=max_size), ()))?,
673        })
674    }
675}
676
677#[cfg(feature = "arbitrary")]
678impl<V: Variant> arbitrary::Arbitrary<'_> for DealerPubMsg<V>
679where
680    V::Public: for<'a> arbitrary::Arbitrary<'a>,
681{
682    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
683        let commitment = u.arbitrary()?;
684        Ok(Self { commitment })
685    }
686}
687
688#[derive(Clone, Debug, PartialEq, Eq)]
689pub struct DealerPrivMsg {
690    share: Secret<Scalar>,
691}
692
693impl DealerPrivMsg {
694    /// Creates a new `DealerPrivMsg` with the given share.
695    pub const fn new(share: Scalar) -> Self {
696        Self {
697            share: Secret::new(share),
698        }
699    }
700}
701
702impl EncodeSize for DealerPrivMsg {
703    fn encode_size(&self) -> usize {
704        self.share.expose(|share| share.encode_size())
705    }
706}
707
708impl Write for DealerPrivMsg {
709    fn write(&self, buf: &mut impl bytes::BufMut) {
710        self.share.expose(|share| share.write(buf));
711    }
712}
713
714impl Read for DealerPrivMsg {
715    type Cfg = ();
716
717    fn read_cfg(
718        buf: &mut impl bytes::Buf,
719        _cfg: &Self::Cfg,
720    ) -> Result<Self, commonware_codec::Error> {
721        Ok(Self::new(ReadExt::read(buf)?))
722    }
723}
724
725#[cfg(feature = "arbitrary")]
726impl arbitrary::Arbitrary<'_> for DealerPrivMsg {
727    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
728        Ok(Self::new(u.arbitrary()?))
729    }
730}
731
732#[derive(Clone, Debug)]
733pub struct PlayerAck<P: PublicKey> {
734    sig: P::Signature,
735}
736
737impl<P: PublicKey> PartialEq for PlayerAck<P> {
738    fn eq(&self, other: &Self) -> bool {
739        self.sig == other.sig
740    }
741}
742
743impl<P: PublicKey> EncodeSize for PlayerAck<P> {
744    fn encode_size(&self) -> usize {
745        self.sig.encode_size()
746    }
747}
748
749impl<P: PublicKey> Write for PlayerAck<P> {
750    fn write(&self, buf: &mut impl bytes::BufMut) {
751        self.sig.write(buf);
752    }
753}
754
755impl<P: PublicKey> Read for PlayerAck<P> {
756    type Cfg = ();
757
758    fn read_cfg(
759        buf: &mut impl bytes::Buf,
760        _cfg: &Self::Cfg,
761    ) -> Result<Self, commonware_codec::Error> {
762        Ok(Self {
763            sig: ReadExt::read(buf)?,
764        })
765    }
766}
767
768#[cfg(feature = "arbitrary")]
769impl<P: PublicKey> arbitrary::Arbitrary<'_> for PlayerAck<P>
770where
771    P::Signature: for<'a> arbitrary::Arbitrary<'a>,
772{
773    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
774        let sig = u.arbitrary()?;
775        Ok(Self { sig })
776    }
777}
778
779#[derive(Clone, PartialEq)]
780enum AckOrReveal<P: PublicKey> {
781    Ack(PlayerAck<P>),
782    Reveal(DealerPrivMsg),
783}
784
785impl<P: PublicKey> AckOrReveal<P> {
786    const fn is_reveal(&self) -> bool {
787        matches!(*self, Self::Reveal(_))
788    }
789}
790
791impl<P: PublicKey> std::fmt::Debug for AckOrReveal<P> {
792    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
793        match self {
794            Self::Ack(x) => write!(f, "Ack({x:?})"),
795            Self::Reveal(_) => write!(f, "Reveal(REDACTED)"),
796        }
797    }
798}
799
800impl<P: PublicKey> EncodeSize for AckOrReveal<P> {
801    fn encode_size(&self) -> usize {
802        1 + match self {
803            Self::Ack(x) => x.encode_size(),
804            Self::Reveal(x) => x.encode_size(),
805        }
806    }
807}
808
809impl<P: PublicKey> Write for AckOrReveal<P> {
810    fn write(&self, buf: &mut impl bytes::BufMut) {
811        match self {
812            Self::Ack(x) => {
813                0u8.write(buf);
814                x.write(buf);
815            }
816            Self::Reveal(x) => {
817                1u8.write(buf);
818                x.write(buf);
819            }
820        }
821    }
822}
823
824impl<P: PublicKey> Read for AckOrReveal<P> {
825    type Cfg = ();
826
827    fn read_cfg(
828        buf: &mut impl bytes::Buf,
829        _cfg: &Self::Cfg,
830    ) -> Result<Self, commonware_codec::Error> {
831        let tag = u8::read(buf)?;
832        match tag {
833            0 => Ok(Self::Ack(ReadExt::read(buf)?)),
834            1 => Ok(Self::Reveal(ReadExt::read(buf)?)),
835            x => Err(commonware_codec::Error::InvalidEnum(x)),
836        }
837    }
838}
839
840#[cfg(feature = "arbitrary")]
841impl<P: PublicKey> arbitrary::Arbitrary<'_> for AckOrReveal<P>
842where
843    P: for<'a> arbitrary::Arbitrary<'a>,
844    P::Signature: for<'a> arbitrary::Arbitrary<'a>,
845{
846    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
847        let choice = u.int_in_range(0..=1)?;
848        match choice {
849            0 => {
850                let ack = u.arbitrary()?;
851                Ok(Self::Ack(ack))
852            }
853            1 => {
854                let reveal = u.arbitrary()?;
855                Ok(Self::Reveal(reveal))
856            }
857            _ => unreachable!(),
858        }
859    }
860}
861
862#[derive(Clone, Debug)]
863enum DealerResult<P: PublicKey> {
864    Ok(Map<P, AckOrReveal<P>>),
865    TooManyReveals,
866}
867
868impl<P: PublicKey> PartialEq for DealerResult<P> {
869    fn eq(&self, other: &Self) -> bool {
870        match (self, other) {
871            (Self::Ok(x), Self::Ok(y)) => x == y,
872            (Self::TooManyReveals, Self::TooManyReveals) => true,
873            _ => false,
874        }
875    }
876}
877
878impl<P: PublicKey> EncodeSize for DealerResult<P> {
879    fn encode_size(&self) -> usize {
880        1 + match self {
881            Self::Ok(r) => r.encode_size(),
882            Self::TooManyReveals => 0,
883        }
884    }
885}
886
887impl<P: PublicKey> Write for DealerResult<P> {
888    fn write(&self, buf: &mut impl bytes::BufMut) {
889        match self {
890            Self::Ok(r) => {
891                0u8.write(buf);
892                r.write(buf);
893            }
894            Self::TooManyReveals => {
895                1u8.write(buf);
896            }
897        }
898    }
899}
900
901impl<P: PublicKey> Read for DealerResult<P> {
902    type Cfg = NonZeroU32;
903
904    fn read_cfg(
905        buf: &mut impl bytes::Buf,
906        &max_players: &Self::Cfg,
907    ) -> Result<Self, commonware_codec::Error> {
908        let tag = u8::read(buf)?;
909        match tag {
910            0 => Ok(Self::Ok(Read::read_cfg(
911                buf,
912                &(RangeCfg::from(0..=max_players.get() as usize), (), ()),
913            )?)),
914            1 => Ok(Self::TooManyReveals),
915            x => Err(commonware_codec::Error::InvalidEnum(x)),
916        }
917    }
918}
919
920#[cfg(feature = "arbitrary")]
921impl<P: PublicKey> arbitrary::Arbitrary<'_> for DealerResult<P>
922where
923    P: for<'a> arbitrary::Arbitrary<'a>,
924    P::Signature: for<'a> arbitrary::Arbitrary<'a>,
925{
926    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
927        let choice = u.int_in_range(0..=1)?;
928        match choice {
929            0 => {
930                use commonware_utils::TryFromIterator;
931                use std::collections::HashMap;
932
933                let base: HashMap<P, AckOrReveal<P>> = u.arbitrary()?;
934                let map = Map::try_from_iter(base.into_iter())
935                    .map_err(|_| arbitrary::Error::IncorrectFormat)?;
936
937                Ok(Self::Ok(map))
938            }
939            1 => Ok(Self::TooManyReveals),
940            _ => unreachable!(),
941        }
942    }
943}
944
945#[derive(Clone, Debug)]
946pub struct DealerLog<V: Variant, P: PublicKey> {
947    pub_msg: DealerPubMsg<V>,
948    results: DealerResult<P>,
949}
950
951impl<V: Variant, P: PublicKey> PartialEq for DealerLog<V, P> {
952    fn eq(&self, other: &Self) -> bool {
953        self.pub_msg == other.pub_msg && self.results == other.results
954    }
955}
956
957impl<V: Variant, P: PublicKey> EncodeSize for DealerLog<V, P> {
958    fn encode_size(&self) -> usize {
959        self.pub_msg.encode_size() + self.results.encode_size()
960    }
961}
962
963impl<V: Variant, P: PublicKey> Write for DealerLog<V, P> {
964    fn write(&self, buf: &mut impl bytes::BufMut) {
965        self.pub_msg.write(buf);
966        self.results.write(buf);
967    }
968}
969
970impl<V: Variant, P: PublicKey> Read for DealerLog<V, P> {
971    type Cfg = NonZeroU32;
972
973    fn read_cfg(
974        buf: &mut impl bytes::Buf,
975        cfg: &Self::Cfg,
976    ) -> Result<Self, commonware_codec::Error> {
977        Ok(Self {
978            pub_msg: Read::read_cfg(buf, cfg)?,
979            results: Read::read_cfg(buf, cfg)?,
980        })
981    }
982}
983
984impl<V: Variant, P: PublicKey> DealerLog<V, P> {
985    fn get_reveal(&self, player: &P) -> Option<&DealerPrivMsg> {
986        let DealerResult::Ok(results) = &self.results else {
987            return None;
988        };
989        match results.get_value(player) {
990            Some(AckOrReveal::Reveal(priv_msg)) => Some(priv_msg),
991            _ => None,
992        }
993    }
994
995    fn zip_players<'a, 'b>(
996        &'a self,
997        players: &'b Set<P>,
998    ) -> Option<impl Iterator<Item = (&'b P, &'a AckOrReveal<P>)>> {
999        match &self.results {
1000            DealerResult::TooManyReveals => None,
1001            DealerResult::Ok(results) => {
1002                // We don't check this on deserialization.
1003                if results.keys() != players {
1004                    return None;
1005                }
1006                Some(players.iter().zip(results.values().iter()))
1007            }
1008        }
1009    }
1010}
1011
1012#[cfg(feature = "arbitrary")]
1013impl<V: Variant, P: PublicKey> arbitrary::Arbitrary<'_> for DealerLog<V, P>
1014where
1015    P: for<'a> arbitrary::Arbitrary<'a>,
1016    V::Public: for<'a> arbitrary::Arbitrary<'a>,
1017    P::Signature: for<'a> arbitrary::Arbitrary<'a>,
1018{
1019    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1020        let pub_msg = u.arbitrary()?;
1021        let results = u.arbitrary()?;
1022        Ok(Self { pub_msg, results })
1023    }
1024}
1025
1026/// A [`DealerLog`], but identified to and signed by a dealer.
1027///
1028/// The [`SignedDealerLog::check`] method allows extracting a public key (the dealer)
1029/// and a [`DealerLog`] from this struct.
1030///
1031/// This avoids having to trust some other party or process for knowing that a
1032/// dealer actually produced a log.
1033#[derive(Clone, Debug)]
1034pub struct SignedDealerLog<V: Variant, S: Signer> {
1035    dealer: S::PublicKey,
1036    log: DealerLog<V, S::PublicKey>,
1037    sig: S::Signature,
1038}
1039
1040impl<V: Variant, S: Signer> PartialEq for SignedDealerLog<V, S> {
1041    fn eq(&self, other: &Self) -> bool {
1042        self.dealer == other.dealer && self.log == other.log && self.sig == other.sig
1043    }
1044}
1045
1046impl<V: Variant, S: Signer> SignedDealerLog<V, S> {
1047    fn sign(sk: &S, info: &Info<V, S::PublicKey>, log: DealerLog<V, S::PublicKey>) -> Self {
1048        let sig = transcript_for_log(info, &log).sign(sk);
1049        Self {
1050            dealer: sk.public_key(),
1051            log,
1052            sig,
1053        }
1054    }
1055
1056    /// Check this log for a particular round.
1057    ///
1058    /// This will produce the public key of the dealer that signed this log,
1059    /// and the underlying log that they signed.
1060    ///
1061    /// This will return [`Option::None`] if the check fails.
1062    #[allow(clippy::type_complexity)]
1063    pub fn check(
1064        self,
1065        info: &Info<V, S::PublicKey>,
1066    ) -> Option<(S::PublicKey, DealerLog<V, S::PublicKey>)> {
1067        if !transcript_for_log(info, &self.log).verify(&self.dealer, &self.sig) {
1068            return None;
1069        }
1070        Some((self.dealer, self.log))
1071    }
1072}
1073
1074impl<V: Variant, S: Signer> EncodeSize for SignedDealerLog<V, S> {
1075    fn encode_size(&self) -> usize {
1076        self.dealer.encode_size() + self.log.encode_size() + self.sig.encode_size()
1077    }
1078}
1079
1080impl<V: Variant, S: Signer> Write for SignedDealerLog<V, S> {
1081    fn write(&self, buf: &mut impl bytes::BufMut) {
1082        self.dealer.write(buf);
1083        self.log.write(buf);
1084        self.sig.write(buf);
1085    }
1086}
1087
1088impl<V: Variant, S: Signer> Read for SignedDealerLog<V, S> {
1089    type Cfg = NonZeroU32;
1090
1091    fn read_cfg(
1092        buf: &mut impl bytes::Buf,
1093        cfg: &Self::Cfg,
1094    ) -> Result<Self, commonware_codec::Error> {
1095        Ok(Self {
1096            dealer: ReadExt::read(buf)?,
1097            log: Read::read_cfg(buf, cfg)?,
1098            sig: ReadExt::read(buf)?,
1099        })
1100    }
1101}
1102
1103#[cfg(feature = "arbitrary")]
1104impl<V: Variant, S: Signer> arbitrary::Arbitrary<'_> for SignedDealerLog<V, S>
1105where
1106    S::PublicKey: for<'a> arbitrary::Arbitrary<'a>,
1107    V::Public: for<'a> arbitrary::Arbitrary<'a>,
1108    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
1109{
1110    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1111        let dealer = u.arbitrary()?;
1112        let log = u.arbitrary()?;
1113        let sig = u.arbitrary()?;
1114        Ok(Self { dealer, log, sig })
1115    }
1116}
1117
1118fn transcript_for_round<V: Variant, P: PublicKey>(info: &Info<V, P>) -> Transcript {
1119    Transcript::resume(info.summary)
1120}
1121
1122fn transcript_for_ack<V: Variant, P: PublicKey>(
1123    transcript: &Transcript,
1124    dealer: &P,
1125    pub_msg: &DealerPubMsg<V>,
1126) -> Transcript {
1127    let mut out = transcript.fork(SIG_ACK);
1128    out.commit(dealer.encode());
1129    out.commit(pub_msg.encode());
1130    out
1131}
1132
1133fn transcript_for_log<V: Variant, P: PublicKey>(
1134    info: &Info<V, P>,
1135    log: &DealerLog<V, P>,
1136) -> Transcript {
1137    let mut out = transcript_for_round(info).fork(SIG_LOG);
1138    out.commit(log.encode());
1139    out
1140}
1141
1142pub struct Dealer<V: Variant, S: Signer> {
1143    me: S,
1144    info: Info<V, S::PublicKey>,
1145    pub_msg: DealerPubMsg<V>,
1146    results: Map<S::PublicKey, AckOrReveal<S::PublicKey>>,
1147    transcript: Transcript,
1148}
1149
1150impl<V: Variant, S: Signer> Dealer<V, S> {
1151    /// Create a [`Dealer`].
1152    ///
1153    /// This needs randomness, to generate a dealing.
1154    ///
1155    /// We also need the dealer's private key, in order to produce the [`SignedDealerLog`].
1156    ///
1157    /// If we're doing a reshare, the dealer should have a share from the previous round.
1158    ///
1159    /// This will produce the [`Dealer`], a [`DealerPubMsg`] to send to every player,
1160    /// and a list of [`DealerPrivMsg`]s, along with which players those need to
1161    /// be sent to.
1162    ///
1163    /// The public message can be sent in the clear, but it's important that players
1164    /// know which dealer sent what public message. You MUST ensure that dealers
1165    /// cannot impersonate each-other when sending this message.
1166    ///
1167    /// The private message MUST be sent encrypted (or, in some other way, privately)
1168    /// to the target player. Similarly, that player MUST be convinced that this dealer
1169    /// sent it that message, without any possibility of impersonation. A simple way
1170    /// to provide both guarantees is through an authenticated channel, e.g. via
1171    /// [crate::handshake], or [commonware-p2p](https://docs.rs/commonware-p2p/latest/commonware_p2p/).
1172    #[allow(clippy::type_complexity)]
1173    pub fn start<M: Faults>(
1174        mut rng: impl CryptoRngCore,
1175        info: Info<V, S::PublicKey>,
1176        me: S,
1177        share: Option<Share>,
1178    ) -> Result<(Self, DealerPubMsg<V>, Vec<(S::PublicKey, DealerPrivMsg)>), Error> {
1179        // Check that this dealer is defined in the round.
1180        info.dealer_index(&me.public_key())?;
1181        let share = info.unwrap_or_random_share(
1182            &mut rng,
1183            // We are extracting the private scalar from `Secret` protection because
1184            // `Poly::new_with_constant` requires an owned value. The extracted scalar is
1185            // scoped to this function and will be zeroized on drop (i.e. the secret is
1186            // only exposed for the duration of this function).
1187            share.map(|x| x.private.expose_unwrap()),
1188        )?;
1189        let my_poly = Poly::new_with_constant(&mut rng, info.degree::<M>(), share);
1190        let priv_msgs = info
1191            .players
1192            .iter()
1193            .map(|pk| {
1194                (
1195                    pk.clone(),
1196                    DealerPrivMsg::new(my_poly.eval_msm(
1197                        &info.player_scalar(pk).expect("player should exist"),
1198                        &Sequential,
1199                    )),
1200                )
1201            })
1202            .collect::<Vec<_>>();
1203        let results: Map<_, _> = priv_msgs
1204            .clone()
1205            .into_iter()
1206            .map(|(pk, priv_msg)| (pk, AckOrReveal::Reveal(priv_msg)))
1207            .try_collect()
1208            .expect("players are unique");
1209        let commitment = Poly::commit(my_poly);
1210        let pub_msg = DealerPubMsg { commitment };
1211        let transcript = {
1212            let t = transcript_for_round(&info);
1213            transcript_for_ack(&t, &me.public_key(), &pub_msg)
1214        };
1215        let this = Self {
1216            me,
1217            info,
1218            pub_msg: pub_msg.clone(),
1219            results,
1220            transcript,
1221        };
1222        Ok((this, pub_msg, priv_msgs))
1223    }
1224
1225    /// Process an acknowledgement from a player.
1226    ///
1227    /// Acknowledgements should really only be processed once per player,
1228    /// but this method is idempotent nonetheless.
1229    pub fn receive_player_ack(
1230        &mut self,
1231        player: S::PublicKey,
1232        ack: PlayerAck<S::PublicKey>,
1233    ) -> Result<(), Error> {
1234        let res_mut = self
1235            .results
1236            .get_value_mut(&player)
1237            .ok_or(Error::UnknownPlayer)?;
1238        if self.transcript.verify(&player, &ack.sig) {
1239            *res_mut = AckOrReveal::Ack(ack);
1240        }
1241        Ok(())
1242    }
1243
1244    /// Finalize the dealer, producing a signed log.
1245    ///
1246    /// This should be called at the point where no more acks will be processed.
1247    pub fn finalize<M: Faults>(self) -> SignedDealerLog<V, S> {
1248        let reveals = self
1249            .results
1250            .values()
1251            .iter()
1252            .filter(|x| x.is_reveal())
1253            .count() as u32;
1254        // Omit results if there are too many reveals.
1255        let results = if reveals > self.info.max_reveals::<M>() {
1256            DealerResult::TooManyReveals
1257        } else {
1258            DealerResult::Ok(self.results)
1259        };
1260        let log = DealerLog {
1261            pub_msg: self.pub_msg,
1262            results,
1263        };
1264        SignedDealerLog::sign(&self.me, &self.info, log)
1265    }
1266}
1267
1268#[allow(clippy::type_complexity)]
1269fn select<V: Variant, P: PublicKey, M: Faults>(
1270    info: &Info<V, P>,
1271    logs: BTreeMap<P, DealerLog<V, P>>,
1272) -> Result<Map<P, DealerLog<V, P>>, Error> {
1273    let required_commitments = info.required_commitments::<M>() as usize;
1274    let transcript = transcript_for_round(info);
1275    let out = logs
1276        .into_iter()
1277        .filter_map(|(dealer, log)| {
1278            info.dealer_index(&dealer).ok()?;
1279            if !info.check_dealer_pub_msg::<M>(&dealer, &log.pub_msg) {
1280                return None;
1281            }
1282            let results_iter = log.zip_players(&info.players)?;
1283            let transcript = transcript_for_ack(&transcript, &dealer, &log.pub_msg);
1284            let mut reveal_count = 0;
1285            let max_reveals = info.max_reveals::<M>();
1286            for (player, result) in results_iter {
1287                match result {
1288                    AckOrReveal::Ack(ack) => {
1289                        if !transcript.verify(player, &ack.sig) {
1290                            return None;
1291                        }
1292                    }
1293                    AckOrReveal::Reveal(priv_msg) => {
1294                        reveal_count += 1;
1295                        if reveal_count > max_reveals {
1296                            return None;
1297                        }
1298                        if !info.check_dealer_priv_msg(player, &log.pub_msg, priv_msg) {
1299                            return None;
1300                        }
1301                    }
1302                }
1303            }
1304            Some((dealer, log))
1305        })
1306        .take(required_commitments)
1307        .try_collect::<Map<_, _>>()
1308        .expect("logs has at most one entry per dealer");
1309    if out.len() < required_commitments {
1310        return Err(Error::DkgFailed);
1311    }
1312    Ok(out)
1313}
1314
1315struct ObserveInner<V: Variant, P: PublicKey> {
1316    output: Output<V, P>,
1317    weights: Option<Interpolator<P, Scalar>>,
1318}
1319
1320impl<V: Variant, P: PublicKey> ObserveInner<V, P> {
1321    fn reckon<M: Faults>(
1322        info: Info<V, P>,
1323        selected: Map<P, DealerLog<V, P>>,
1324        strategy: &impl Strategy,
1325    ) -> Result<Self, Error> {
1326        // Track players with too many reveals
1327        let max_faults = info.players.max_faults::<M>();
1328        let mut reveal_counts: BTreeMap<P, u32> = BTreeMap::new();
1329        let mut revealed = Vec::new();
1330        for log in selected.values() {
1331            let Some(iter) = log.zip_players(&info.players) else {
1332                continue;
1333            };
1334            for (player, result) in iter {
1335                if !result.is_reveal() {
1336                    continue;
1337                }
1338                let count = reveal_counts.entry(player.clone()).or_insert(0);
1339                *count += 1;
1340                if *count == max_faults + 1 {
1341                    revealed.push(player.clone());
1342                }
1343            }
1344        }
1345        let revealed: Set<P> = revealed
1346            .into_iter()
1347            .try_collect()
1348            .expect("players are unique");
1349
1350        // Extract dealers before consuming selected
1351        let dealers: Set<P> = selected
1352            .keys()
1353            .iter()
1354            .cloned()
1355            .try_collect()
1356            .expect("selected dealers are unique");
1357
1358        // Recover the public polynomial
1359        let (public, weights) = if let Some(previous) = info.previous.as_ref() {
1360            let weights = previous
1361                .public()
1362                .mode()
1363                .subset_interpolator(previous.players(), selected.keys())
1364                .expect("the result of select should produce a valid subset");
1365            let commitments = selected
1366                .into_iter()
1367                .map(|(dealer, log)| (dealer, log.pub_msg.commitment))
1368                .try_collect::<Map<_, _>>()
1369                .expect("Map should have unique keys");
1370            let public = weights
1371                .interpolate(&commitments, strategy)
1372                .expect("select checks that enough points have been provided");
1373            if previous.public().public() != public.constant() {
1374                return Err(Error::DkgFailed);
1375            }
1376            (public, Some(weights))
1377        } else {
1378            let mut public = Poly::zero();
1379            for log in selected.values() {
1380                public += &log.pub_msg.commitment;
1381            }
1382            (public, None)
1383        };
1384        let n = info.players.len() as u32;
1385        let output = Output {
1386            summary: info.summary,
1387            public: Sharing::new(info.mode, NZU32!(n), public),
1388            dealers,
1389            players: info.players,
1390            revealed,
1391        };
1392        Ok(Self { output, weights })
1393    }
1394}
1395
1396/// Observe the result of a DKG, using the public results.
1397///
1398/// The log mapping dealers to their log is the shared piece of information
1399/// that the participants (players, observers) of the DKG must all agree on.
1400///
1401/// From this log, we can (potentially, as the DKG can fail) compute the public output.
1402///
1403/// This will only ever return [`Error::DkgFailed`].
1404pub fn observe<V: Variant, P: PublicKey, M: Faults>(
1405    info: Info<V, P>,
1406    logs: BTreeMap<P, DealerLog<V, P>>,
1407    strategy: &impl Strategy,
1408) -> Result<Output<V, P>, Error> {
1409    let selected = select::<V, P, M>(&info, logs)?;
1410    ObserveInner::<V, P>::reckon::<M>(info, selected, strategy).map(|x| x.output)
1411}
1412
1413/// Represents a player in the DKG / reshare process.
1414///
1415/// The player is attempting to get a share of the key.
1416///
1417/// They need not have participated in prior rounds.
1418pub struct Player<V: Variant, S: Signer> {
1419    me: S,
1420    me_pub: S::PublicKey,
1421    info: Info<V, S::PublicKey>,
1422    index: Participant,
1423    transcript: Transcript,
1424    view: BTreeMap<S::PublicKey, (DealerPubMsg<V>, DealerPrivMsg)>,
1425}
1426
1427impl<V: Variant, S: Signer> Player<V, S> {
1428    /// Create a new [`Player`].
1429    ///
1430    /// We need the player's private key in order to sign messages.
1431    pub fn new(info: Info<V, S::PublicKey>, me: S) -> Result<Self, Error> {
1432        let me_pub = me.public_key();
1433        Ok(Self {
1434            index: info.player_index(&me_pub)?,
1435            me,
1436            me_pub,
1437            transcript: transcript_for_round(&info),
1438            info,
1439            view: BTreeMap::new(),
1440        })
1441    }
1442
1443    /// Process a message from a dealer.
1444    ///
1445    /// It's important that nobody can impersonate the dealer, and that the
1446    /// private message was not exposed to anyone else. A convenient way to
1447    /// provide this is by using an authenticated channel, e.g. via
1448    /// [crate::handshake], or [commonware-p2p](https://docs.rs/commonware-p2p/latest/commonware_p2p/).
1449    pub fn dealer_message<M: Faults>(
1450        &mut self,
1451        dealer: S::PublicKey,
1452        pub_msg: DealerPubMsg<V>,
1453        priv_msg: DealerPrivMsg,
1454    ) -> Option<PlayerAck<S::PublicKey>> {
1455        if self.view.contains_key(&dealer) {
1456            return None;
1457        }
1458        self.info.dealer_index(&dealer).ok()?;
1459        if !self.info.check_dealer_pub_msg::<M>(&dealer, &pub_msg) {
1460            return None;
1461        }
1462        if !self
1463            .info
1464            .check_dealer_priv_msg(&self.me_pub, &pub_msg, &priv_msg)
1465        {
1466            return None;
1467        }
1468        let sig = transcript_for_ack(&self.transcript, &dealer, &pub_msg).sign(&self.me);
1469        self.view.insert(dealer, (pub_msg, priv_msg));
1470        Some(PlayerAck { sig })
1471    }
1472
1473    /// Finalize the player, producing an output, and a share.
1474    ///
1475    /// This should agree with [`observe`], in terms of `Ok` vs `Err` and the
1476    /// public output, so long as the logs agree. It's crucial that the players
1477    /// come to agreement, in some way, on exactly which logs they need to use
1478    /// for finalize.
1479    ///
1480    /// This will only ever return [`Error::DkgFailed`].
1481    pub fn finalize<M: Faults>(
1482        self,
1483        logs: BTreeMap<S::PublicKey, DealerLog<V, S::PublicKey>>,
1484        strategy: &impl Strategy,
1485    ) -> Result<(Output<V, S::PublicKey>, Share), Error> {
1486        let selected = select::<V, S::PublicKey, M>(&self.info, logs)?;
1487        // We are extracting the private scalars from `Secret` protection
1488        // because interpolation/summation needs owned scalars for polynomial
1489        // arithmetic. The extracted scalars are scoped to this function and
1490        // will be zeroized on drop (i.e. the secrets are only exposed for the
1491        // duration of this function).
1492        let dealings = selected
1493            .iter_pairs()
1494            .map(|(dealer, log)| {
1495                let share = self
1496                    .view
1497                    .get(dealer)
1498                    .map(|(_, priv_msg)| priv_msg.share.clone().expose_unwrap())
1499                    .unwrap_or_else(|| {
1500                        log.get_reveal(&self.me_pub).map_or_else(
1501                            || {
1502                                unreachable!(
1503                                    "select didn't check dealer reveal, or we're not a player?"
1504                                )
1505                            },
1506                            |priv_msg| priv_msg.share.clone().expose_unwrap(),
1507                        )
1508                    });
1509                (dealer.clone(), share)
1510            })
1511            .try_collect::<Map<_, _>>()
1512            .expect("select produces at most one entry per dealer");
1513        let ObserveInner { output, weights } =
1514            ObserveInner::<V, S::PublicKey>::reckon::<M>(self.info, selected, strategy)?;
1515        let private = weights.map_or_else(
1516            || {
1517                let mut out = <Scalar as Additive>::zero();
1518                for s in dealings.values() {
1519                    out += s;
1520                }
1521                out
1522            },
1523            |weights| {
1524                weights
1525                    .interpolate(&dealings, strategy)
1526                    .expect("select ensures that we can recover")
1527            },
1528        );
1529        let share = Share::new(self.index, Private::new(private));
1530        Ok((output, share))
1531    }
1532}
1533
1534/// The result of dealing shares to players.
1535pub type DealResult<V, P> = Result<(Output<V, P>, Map<P, Share>), Error>;
1536
1537/// Simply distribute shares at random, instead of performing a distributed protocol.
1538pub fn deal<V: Variant, P: Clone + Ord, M: Faults>(
1539    mut rng: impl CryptoRngCore,
1540    mode: Mode,
1541    players: Set<P>,
1542) -> DealResult<V, P> {
1543    if players.is_empty() {
1544        return Err(Error::NumPlayers(0));
1545    }
1546    let n = NZU32!(players.len() as u32);
1547    let t = players.quorum::<M>();
1548    let private = Poly::new(&mut rng, t - 1);
1549    let shares: Map<_, _> = players
1550        .iter()
1551        .enumerate()
1552        .map(|(i, p)| {
1553            let participant = Participant::from_usize(i);
1554            let eval = private.eval_msm(
1555                &mode
1556                    .scalar(n, participant)
1557                    .expect("player index should be valid"),
1558                &Sequential,
1559            );
1560            let share = Share::new(participant, Private::new(eval));
1561            (p.clone(), share)
1562        })
1563        .try_collect()
1564        .expect("players are unique");
1565    let output = Output {
1566        summary: Summary::random(&mut rng),
1567        public: Sharing::new(mode, n, Poly::commit(private)),
1568        dealers: players.clone(),
1569        players,
1570        revealed: Set::default(),
1571    };
1572    Ok((output, shares))
1573}
1574
1575/// Like [`deal`], but without linking the result to specific public keys.
1576///
1577/// This can be more convenient for testing, where you don't want to go through
1578/// the trouble of generating signing keys. The downside is that the result isn't
1579/// compatible with subsequent DKGs, which need an [`Output`].
1580pub fn deal_anonymous<V: Variant, M: Faults>(
1581    rng: impl CryptoRngCore,
1582    mode: Mode,
1583    n: NonZeroU32,
1584) -> (Sharing<V>, Vec<Share>) {
1585    let players = (0..n.get()).try_collect().unwrap();
1586    let (output, shares) = deal::<V, _, M>(rng, mode, players).unwrap();
1587    (output.public().clone(), shares.values().to_vec())
1588}
1589
1590#[cfg(any(feature = "arbitrary", test))]
1591mod test_plan {
1592    use super::*;
1593    use crate::{
1594        bls12381::primitives::{
1595            ops::{self, threshold},
1596            variant::Variant,
1597        },
1598        ed25519, PublicKey,
1599    };
1600    use anyhow::anyhow;
1601    use bytes::BytesMut;
1602    use commonware_utils::{Faults, N3f1, TryCollect};
1603    use core::num::NonZeroI32;
1604    use rand::{rngs::StdRng, SeedableRng as _};
1605    use std::collections::BTreeSet;
1606
1607    /// Apply a mask to some bytes, returning whether or not a modification happened
1608    fn apply_mask(bytes: &mut BytesMut, mask: &[u8]) -> bool {
1609        let mut modified = false;
1610        for (l, &r) in bytes.iter_mut().zip(mask.iter()) {
1611            modified |= r != 0;
1612            *l ^= r;
1613        }
1614        modified
1615    }
1616
1617    #[derive(Clone, Default, Debug)]
1618    pub struct Masks {
1619        pub info_summary: Vec<u8>,
1620        pub dealer: Vec<u8>,
1621        pub pub_msg: Vec<u8>,
1622        pub log: Vec<u8>,
1623    }
1624
1625    impl Masks {
1626        fn transcript_for_round<V: Variant, P: PublicKey>(
1627            &self,
1628            info: &Info<V, P>,
1629        ) -> anyhow::Result<(bool, Transcript)> {
1630            let mut summary_bs = info.summary.encode_mut();
1631            let modified = apply_mask(&mut summary_bs, &self.info_summary);
1632            let summary = Summary::read(&mut summary_bs)?;
1633            Ok((modified, Transcript::resume(summary)))
1634        }
1635
1636        fn transcript_for_player_ack<V: Variant, P: PublicKey>(
1637            &self,
1638            info: &Info<V, P>,
1639            dealer: &P,
1640            pub_msg: &DealerPubMsg<V>,
1641        ) -> anyhow::Result<(bool, Transcript)> {
1642            let (mut modified, transcript) = self.transcript_for_round(info)?;
1643            let mut transcript = transcript.fork(SIG_ACK);
1644
1645            let mut dealer_bs = dealer.encode_mut();
1646            modified |= apply_mask(&mut dealer_bs, &self.dealer);
1647            transcript.commit(&mut dealer_bs);
1648
1649            let mut pub_msg_bs = pub_msg.encode_mut();
1650            modified |= apply_mask(&mut pub_msg_bs, &self.pub_msg);
1651            transcript.commit(&mut pub_msg_bs);
1652
1653            Ok((modified, transcript))
1654        }
1655
1656        fn transcript_for_signed_dealer_log<V: Variant, P: PublicKey>(
1657            &self,
1658            info: &Info<V, P>,
1659            log: &DealerLog<V, P>,
1660        ) -> anyhow::Result<(bool, Transcript)> {
1661            let (mut modified, transcript) = self.transcript_for_round(info)?;
1662            let mut transcript = transcript.fork(SIG_LOG);
1663
1664            let mut log_bs = log.encode_mut();
1665            modified |= apply_mask(&mut log_bs, &self.log);
1666            transcript.commit(&mut log_bs);
1667
1668            Ok((modified, transcript))
1669        }
1670    }
1671
1672    /// A round in the DKG test plan.
1673    #[derive(Debug, Default)]
1674    pub struct Round {
1675        dealers: Vec<u32>,
1676        players: Vec<u32>,
1677        no_acks: BTreeSet<(u32, u32)>,
1678        bad_shares: BTreeSet<(u32, u32)>,
1679        bad_player_sigs: BTreeMap<(u32, u32), Masks>,
1680        bad_reveals: BTreeSet<(u32, u32)>,
1681        bad_dealer_sigs: BTreeMap<u32, Masks>,
1682        replace_shares: BTreeSet<u32>,
1683        shift_degrees: BTreeMap<u32, NonZeroI32>,
1684    }
1685
1686    impl Round {
1687        pub fn new(dealers: Vec<u32>, players: Vec<u32>) -> Self {
1688            Self {
1689                dealers,
1690                players,
1691                ..Default::default()
1692            }
1693        }
1694
1695        pub fn no_ack(mut self, dealer: u32, player: u32) -> Self {
1696            self.no_acks.insert((dealer, player));
1697            self
1698        }
1699
1700        pub fn bad_share(mut self, dealer: u32, player: u32) -> Self {
1701            self.bad_shares.insert((dealer, player));
1702            self
1703        }
1704
1705        pub fn bad_player_sig(mut self, dealer: u32, player: u32, masks: Masks) -> Self {
1706            self.bad_player_sigs.insert((dealer, player), masks);
1707            self
1708        }
1709
1710        pub fn bad_reveal(mut self, dealer: u32, player: u32) -> Self {
1711            self.bad_reveals.insert((dealer, player));
1712            self
1713        }
1714
1715        pub fn bad_dealer_sig(mut self, dealer: u32, masks: Masks) -> Self {
1716            self.bad_dealer_sigs.insert(dealer, masks);
1717            self
1718        }
1719
1720        pub fn replace_share(mut self, dealer: u32) -> Self {
1721            self.replace_shares.insert(dealer);
1722            self
1723        }
1724
1725        pub fn shift_degree(mut self, dealer: u32, shift: NonZeroI32) -> Self {
1726            self.shift_degrees.insert(dealer, shift);
1727            self
1728        }
1729
1730        /// Validate that this round is well-formed given the number of participants
1731        /// and the previous successful round's players.
1732        pub fn validate(
1733            &self,
1734            num_participants: u32,
1735            previous_players: Option<&[u32]>,
1736        ) -> anyhow::Result<()> {
1737            if self.dealers.is_empty() {
1738                return Err(anyhow!("dealers is empty"));
1739            }
1740            if self.players.is_empty() {
1741                return Err(anyhow!("players is empty"));
1742            }
1743            // Check dealer/player ranges
1744            for &d in &self.dealers {
1745                if d >= num_participants {
1746                    return Err(anyhow!("dealer {d} out of range [1, {num_participants}]"));
1747                }
1748            }
1749            for &p in &self.players {
1750                if p >= num_participants {
1751                    return Err(anyhow!("player {p} out of range [1, {num_participants}]"));
1752                }
1753            }
1754
1755            // If there's a previous round, check dealer constraints
1756            if let Some(prev_players) = previous_players {
1757                // Every dealer must have been a player in the previous round
1758                for &d in &self.dealers {
1759                    if !prev_players.contains(&d) {
1760                        return Err(anyhow!("dealer {d} was not a player in previous round"));
1761                    }
1762                }
1763                // Must have >= quorum(prev_players) dealers
1764                let required = N3f1::quorum(prev_players.len());
1765                if (self.dealers.len() as u32) < required {
1766                    return Err(anyhow!(
1767                        "not enough dealers: have {}, need {} (quorum of {} previous players)",
1768                        self.dealers.len(),
1769                        required,
1770                        prev_players.len()
1771                    ));
1772                }
1773            }
1774
1775            Ok(())
1776        }
1777
1778        fn bad(&self, previous_successful_round: bool, dealer: u32) -> bool {
1779            if self.replace_shares.contains(&dealer) && previous_successful_round {
1780                return true;
1781            }
1782            if let Some(shift) = self.shift_degrees.get(&dealer) {
1783                let degree = N3f1::quorum(self.players.len()) as i32 - 1;
1784                // We shift the degree, but saturate at 0, so it's possible
1785                // that the shift isn't actually doing anything.
1786                //
1787                // This is effectively the same as checking degree == 0 && shift < 0,
1788                // but matches what ends up happening a bit better.
1789                if (degree + shift.get()).max(0) != degree {
1790                    return true;
1791                }
1792            }
1793            if self.bad_reveals.iter().any(|&(d, _)| d == dealer) {
1794                return true;
1795            }
1796            let revealed_players = self
1797                .bad_shares
1798                .iter()
1799                .copied()
1800                .chain(self.no_acks.iter().copied())
1801                .filter_map(|(d, p)| if d == dealer { Some(p) } else { None })
1802                .collect::<BTreeSet<_>>();
1803            revealed_players.len() as u32 > N3f1::max_faults(self.players.len())
1804        }
1805
1806        /// Determine if this round is expected to fail.
1807        fn expect_failure(&self, previous_successful_round: Option<u32>) -> bool {
1808            let good_dealer_count = self
1809                .dealers
1810                .iter()
1811                .filter(|&&d| !self.bad(previous_successful_round.is_some(), d))
1812                .count();
1813            let required = previous_successful_round
1814                .map(N3f1::quorum)
1815                .unwrap_or_default()
1816                .max(N3f1::quorum(self.dealers.len())) as usize;
1817            good_dealer_count < required
1818        }
1819    }
1820
1821    /// A DKG test plan consisting of multiple rounds.
1822    #[derive(Debug)]
1823    pub struct Plan {
1824        num_participants: NonZeroU32,
1825        rounds: Vec<Round>,
1826    }
1827
1828    impl Plan {
1829        pub const fn new(num_participants: NonZeroU32) -> Self {
1830            Self {
1831                num_participants,
1832                rounds: Vec::new(),
1833            }
1834        }
1835
1836        pub fn with(mut self, round: Round) -> Self {
1837            self.rounds.push(round);
1838            self
1839        }
1840
1841        /// Validate the entire plan.
1842        fn validate(&self) -> anyhow::Result<()> {
1843            let mut last_successful_players: Option<Vec<u32>> = None;
1844
1845            for round in &self.rounds {
1846                round.validate(
1847                    self.num_participants.get(),
1848                    last_successful_players.as_deref(),
1849                )?;
1850
1851                // If this round is expected to succeed, update last_successful_players
1852                if !round.expect_failure(last_successful_players.as_ref().map(|x| x.len() as u32)) {
1853                    last_successful_players = Some(round.players.clone());
1854                }
1855            }
1856            Ok(())
1857        }
1858
1859        /// Run the test plan with a given seed.
1860        pub fn run<V: Variant>(self, seed: u64) -> anyhow::Result<()> {
1861            self.validate()?;
1862
1863            let mut rng = StdRng::seed_from_u64(seed);
1864
1865            // Generate keys for all participants (1-indexed to num_participants)
1866            let keys = (0..self.num_participants.get())
1867                .map(|_| ed25519::PrivateKey::random(&mut rng))
1868                .collect::<Vec<_>>();
1869
1870            // Precompute mapping from public key to key index to avoid confusion
1871            // between key indices and positions in sorted Sets.
1872            let pk_to_key_idx: BTreeMap<ed25519::PublicKey, u32> = keys
1873                .iter()
1874                .enumerate()
1875                .map(|(i, k)| (k.public_key(), i as u32))
1876                .collect();
1877
1878            // The max_read_size needs to account for shifted polynomial degrees.
1879            // Find the maximum positive shift across all rounds.
1880            let max_shift = self
1881                .rounds
1882                .iter()
1883                .flat_map(|r| r.shift_degrees.values())
1884                .map(|s| s.get())
1885                .max()
1886                .unwrap_or(0)
1887                .max(0) as u32;
1888            let max_read_size =
1889                NonZeroU32::new(self.num_participants.get() + max_shift).expect("non-zero");
1890
1891            let mut previous_output: Option<Output<V, ed25519::PublicKey>> = None;
1892            let mut shares: BTreeMap<ed25519::PublicKey, Share> = BTreeMap::new();
1893            let mut threshold_public_key: Option<V::Public> = None;
1894
1895            for (i_round, round) in self.rounds.into_iter().enumerate() {
1896                let previous_successful_round =
1897                    previous_output.as_ref().map(|o| o.players.len() as u32);
1898
1899                let dealer_set = round
1900                    .dealers
1901                    .iter()
1902                    .map(|&i| keys[i as usize].public_key())
1903                    .try_collect::<Set<_>>()
1904                    .unwrap();
1905                let player_set: Set<ed25519::PublicKey> = round
1906                    .players
1907                    .iter()
1908                    .map(|&i| keys[i as usize].public_key())
1909                    .try_collect()
1910                    .unwrap();
1911
1912                // Create round info
1913                let info = Info::new::<N3f1>(
1914                    b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
1915                    i_round as u64,
1916                    previous_output.clone(),
1917                    Default::default(),
1918                    dealer_set.clone(),
1919                    player_set.clone(),
1920                )?;
1921
1922                let mut players: Map<_, _> = round
1923                    .players
1924                    .iter()
1925                    .map(|&i| {
1926                        let sk = keys[i as usize].clone();
1927                        let pk = sk.public_key();
1928                        let player = Player::new(info.clone(), sk)?;
1929                        Ok((pk, player))
1930                    })
1931                    .collect::<anyhow::Result<Vec<_>>>()?
1932                    .try_into()
1933                    .unwrap();
1934
1935                // Run dealer protocol
1936                let mut dealer_logs = BTreeMap::new();
1937                for &i_dealer in &round.dealers {
1938                    let sk = keys[i_dealer as usize].clone();
1939                    let pk = sk.public_key();
1940                    let share = match (shares.get(&pk), round.replace_shares.contains(&i_dealer)) {
1941                        (None, _) => None,
1942                        (Some(s), false) => Some(s.clone()),
1943                        (Some(_), true) => Some(Share::new(
1944                            Participant::new(i_dealer),
1945                            Private::random(&mut rng),
1946                        )),
1947                    };
1948
1949                    // Start dealer (with potential modifications)
1950                    let (mut dealer, pub_msg, mut priv_msgs) =
1951                        if let Some(shift) = round.shift_degrees.get(&i_dealer) {
1952                            // Create dealer with shifted degree
1953                            let degree = u32::try_from(info.degree::<N3f1>() as i32 + shift.get())
1954                                .unwrap_or_default();
1955
1956                            // Manually create the dealer with adjusted polynomial
1957                            let share = info
1958                                .unwrap_or_random_share(
1959                                    &mut rng,
1960                                    share.map(|s| s.private.expose_unwrap()),
1961                                )
1962                                .expect("Failed to generate dealer share");
1963
1964                            let my_poly = Poly::new_with_constant(&mut rng, degree, share);
1965                            let priv_msgs = info
1966                                .players
1967                                .iter()
1968                                .map(|pk| {
1969                                    (
1970                                        pk.clone(),
1971                                        DealerPrivMsg::new(my_poly.eval_msm(
1972                                            &info.player_scalar(pk).expect("player should exist"),
1973                                            &Sequential,
1974                                        )),
1975                                    )
1976                                })
1977                                .collect::<Vec<_>>();
1978                            let results: Map<_, _> = priv_msgs
1979                                .iter()
1980                                .map(|(pk, pm)| (pk.clone(), AckOrReveal::Reveal(pm.clone())))
1981                                .try_collect()
1982                                .unwrap();
1983                            let commitment = Poly::commit(my_poly);
1984                            let pub_msg = DealerPubMsg { commitment };
1985                            let transcript = {
1986                                let t = transcript_for_round(&info);
1987                                transcript_for_ack(&t, &pk, &pub_msg)
1988                            };
1989                            let dealer = Dealer {
1990                                me: sk.clone(),
1991                                info: info.clone(),
1992                                pub_msg: pub_msg.clone(),
1993                                results,
1994                                transcript,
1995                            };
1996                            (dealer, pub_msg, priv_msgs)
1997                        } else {
1998                            Dealer::start::<N3f1>(&mut rng, info.clone(), sk.clone(), share)?
1999                        };
2000
2001                    // Apply BadShare perturbations
2002                    for (player, priv_msg) in &mut priv_msgs {
2003                        let player_key_idx = pk_to_key_idx[player];
2004                        if round.bad_shares.contains(&(i_dealer, player_key_idx)) {
2005                            *priv_msg = DealerPrivMsg::new(Scalar::random(&mut rng));
2006                        }
2007                    }
2008                    assert_eq!(priv_msgs.len(), players.len());
2009
2010                    // Process player acks
2011                    let mut num_reveals = players.len() as u32;
2012                    for (player_pk, priv_msg) in priv_msgs {
2013                        // Check priv msg encoding.
2014                        assert_eq!(priv_msg, ReadExt::read(&mut priv_msg.encode())?);
2015
2016                        let i_player = players
2017                            .index(&player_pk)
2018                            .ok_or_else(|| anyhow!("unknown player: {:?}", &player_pk))?;
2019                        let player_key_idx = pk_to_key_idx[&player_pk];
2020                        let player = &mut players.values_mut()[usize::from(i_player)];
2021
2022                        let ack =
2023                            player.dealer_message::<N3f1>(pk.clone(), pub_msg.clone(), priv_msg);
2024                        assert_eq!(ack, ReadExt::read(&mut ack.encode())?);
2025                        if let Some(ack) = ack {
2026                            let masks = round
2027                                .bad_player_sigs
2028                                .get(&(i_dealer, player_key_idx))
2029                                .cloned()
2030                                .unwrap_or_default();
2031                            let (modified, transcript) =
2032                                masks.transcript_for_player_ack(&info, &pk, &pub_msg)?;
2033                            assert_eq!(transcript.verify(&player_pk, &ack.sig), !modified);
2034
2035                            // Skip receiving ack if NoAck perturbation
2036                            if !round.no_acks.contains(&(i_dealer, player_key_idx)) {
2037                                dealer.receive_player_ack(player_pk, ack)?;
2038                                num_reveals -= 1;
2039                            }
2040                        } else {
2041                            assert!(
2042                                round.bad_shares.contains(&(i_dealer, player_key_idx))
2043                                    || round.bad(previous_successful_round.is_some(), i_dealer)
2044                            );
2045                        }
2046                    }
2047
2048                    // Finalize dealer
2049                    let signed_log = dealer.finalize::<N3f1>();
2050                    assert_eq!(
2051                        signed_log,
2052                        Read::read_cfg(&mut signed_log.encode(), &max_read_size)?
2053                    );
2054
2055                    // Check for BadDealerSig
2056                    let masks = round
2057                        .bad_dealer_sigs
2058                        .get(&i_dealer)
2059                        .cloned()
2060                        .unwrap_or_default();
2061                    let (modified, transcript) =
2062                        masks.transcript_for_signed_dealer_log(&info, &signed_log.log)?;
2063                    assert_eq!(transcript.verify(&pk, &signed_log.sig), !modified);
2064                    let (found_pk, mut log) = signed_log
2065                        .check(&info)
2066                        .ok_or_else(|| anyhow!("signed log should verify"))?;
2067                    assert_eq!(pk, found_pk);
2068                    // Apply BadReveal perturbations
2069                    match &mut log.results {
2070                        DealerResult::TooManyReveals => {
2071                            assert!(num_reveals > info.max_reveals::<N3f1>());
2072                        }
2073                        DealerResult::Ok(results) => {
2074                            assert_eq!(results.len(), players.len());
2075                            for &i_player in &round.players {
2076                                if !round.bad_reveals.contains(&(i_dealer, i_player)) {
2077                                    continue;
2078                                }
2079                                let player_pk = keys[i_player as usize].public_key();
2080                                *results
2081                                    .get_value_mut(&player_pk)
2082                                    .ok_or_else(|| anyhow!("unknown player: {:?}", &player_pk))? =
2083                                    AckOrReveal::Reveal(DealerPrivMsg::new(Scalar::random(
2084                                        &mut rng,
2085                                    )));
2086                            }
2087                        }
2088                    }
2089                    dealer_logs.insert(pk, log);
2090                }
2091
2092                // Make sure that bad dealers are not selected.
2093                let selection = select::<_, _, N3f1>(&info, dealer_logs.clone());
2094                if let Ok(ref selection) = selection {
2095                    let good_pks = selection
2096                        .iter_pairs()
2097                        .map(|(pk, _)| pk.clone())
2098                        .collect::<BTreeSet<_>>();
2099                    for &i_dealer in &round.dealers {
2100                        if round.bad(previous_successful_round.is_some(), i_dealer) {
2101                            assert!(!good_pks.contains(&keys[i_dealer as usize].public_key()));
2102                        }
2103                    }
2104                }
2105                // Run observer
2106                let observe_result =
2107                    observe::<_, _, N3f1>(info.clone(), dealer_logs.clone(), &Sequential);
2108                if round.expect_failure(previous_successful_round) {
2109                    assert!(
2110                        observe_result.is_err(),
2111                        "Round {i_round} should have failed but succeeded",
2112                    );
2113                    continue;
2114                }
2115                let observer_output = observe_result?;
2116                let selection = selection.expect("select should succeed if observe succeeded");
2117
2118                // Compute expected dealers: good dealers up to required_commitments
2119                // The select function iterates dealer_logs (BTreeMap) in public key order
2120                let required_commitments = info.required_commitments::<N3f1>() as usize;
2121                let expected_dealers: Set<ed25519::PublicKey> = dealer_set
2122                    .iter()
2123                    .filter(|pk| {
2124                        let i = keys.iter().position(|k| &k.public_key() == *pk).unwrap() as u32;
2125                        !round.bad(previous_successful_round.is_some(), i)
2126                    })
2127                    .take(required_commitments)
2128                    .cloned()
2129                    .try_collect()
2130                    .expect("dealers are unique");
2131                let expected_dealer_indices: BTreeSet<u32> = expected_dealers
2132                    .iter()
2133                    .filter_map(|pk| {
2134                        keys.iter()
2135                            .position(|k| &k.public_key() == pk)
2136                            .map(|i| i as u32)
2137                    })
2138                    .collect();
2139                assert_eq!(
2140                    observer_output.dealers(),
2141                    &expected_dealers,
2142                    "Output dealers should match expected good dealers"
2143                );
2144
2145                // Map selected dealers to their key indices (for later use)
2146                let selected_dealers: BTreeSet<u32> = selection
2147                    .keys()
2148                    .iter()
2149                    .filter_map(|pk| {
2150                        keys.iter()
2151                            .position(|k| &k.public_key() == pk)
2152                            .map(|i| i as u32)
2153                    })
2154                    .collect();
2155                assert_eq!(
2156                    selected_dealers, expected_dealer_indices,
2157                    "Selection should match expected dealers"
2158                );
2159                let selected_players: Set<ed25519::PublicKey> = round
2160                    .players
2161                    .iter()
2162                    .map(|&i| keys[i as usize].public_key())
2163                    .try_collect()
2164                    .expect("players are unique");
2165
2166                // Compute expected reveals
2167                //
2168                // Note: We use union of no_acks and bad_shares since each (dealer, player) pair
2169                // results in at most one reveal in the protocol, regardless of whether the player
2170                // didn't ack, got a bad share, or both.
2171                let mut expected_reveals: BTreeMap<ed25519::PublicKey, u32> = BTreeMap::new();
2172                for &(dealer_idx, player_key_idx) in round.no_acks.union(&round.bad_shares) {
2173                    if !selected_dealers.contains(&dealer_idx) {
2174                        continue;
2175                    }
2176                    let pk = keys[player_key_idx as usize].public_key();
2177                    if selected_players.position(&pk).is_none() {
2178                        continue;
2179                    }
2180                    *expected_reveals.entry(pk).or_insert(0) += 1;
2181                }
2182
2183                // Verify each player's revealed status
2184                let max_faults = selected_players.max_faults::<N3f1>();
2185                for player in player_set.iter() {
2186                    let expected = expected_reveals.get(player).copied().unwrap_or(0) > max_faults;
2187                    let actual = observer_output.revealed().position(player).is_some();
2188                    assert_eq!(expected, actual, "Unexpected outcome for player {player:?} (expected={expected}, actual={actual})");
2189                }
2190
2191                // Finalize each player
2192                for (player_pk, player) in players.into_iter() {
2193                    let (player_output, share) = player
2194                        .finalize::<N3f1>(dealer_logs.clone(), &Sequential)
2195                        .expect("Player finalize should succeed");
2196
2197                    assert_eq!(
2198                        player_output, observer_output,
2199                        "Player output should match observer output"
2200                    );
2201
2202                    // Verify share matches public polynomial
2203                    let expected_public = observer_output
2204                        .public
2205                        .partial_public(share.index)
2206                        .expect("share index should be valid");
2207                    let actual_public = share.public::<V>();
2208                    assert_eq!(
2209                        expected_public, actual_public,
2210                        "Share should match public polynomial"
2211                    );
2212
2213                    shares.insert(player_pk.clone(), share);
2214                }
2215
2216                // Initialize or verify threshold public key
2217                let current_public = *observer_output.public().public();
2218                match threshold_public_key {
2219                    None => threshold_public_key = Some(current_public),
2220                    Some(tpk) => {
2221                        assert_eq!(
2222                            tpk, current_public,
2223                            "Public key should remain constant across reshares"
2224                        );
2225                    }
2226                }
2227
2228                // Generate and verify threshold signature
2229                let test_message = format!("test message round {i_round}").into_bytes();
2230                let namespace = b"test";
2231
2232                let mut partial_sigs = Vec::new();
2233                for &i_player in &round.players {
2234                    let share = &shares[&keys[i_player as usize].public_key()];
2235                    let partial_sig = threshold::sign_message::<V>(share, namespace, &test_message);
2236
2237                    threshold::verify_message::<V>(
2238                        &observer_output.public,
2239                        namespace,
2240                        &test_message,
2241                        &partial_sig,
2242                    )
2243                    .expect("Partial signature verification should succeed");
2244
2245                    partial_sigs.push(partial_sig);
2246                }
2247
2248                let threshold = observer_output.quorum::<N3f1>();
2249                let threshold_sig = threshold::recover::<V, _, N3f1>(
2250                    &observer_output.public,
2251                    &partial_sigs[0..threshold as usize],
2252                    &Sequential,
2253                )
2254                .expect("Should recover threshold signature");
2255
2256                // Verify against the saved public key
2257                ops::verify_message::<V>(
2258                    threshold_public_key.as_ref().unwrap(),
2259                    namespace,
2260                    &test_message,
2261                    &threshold_sig,
2262                )
2263                .expect("Threshold signature verification should succeed");
2264
2265                // Update state for next round
2266                previous_output = Some(observer_output);
2267            }
2268            Ok(())
2269        }
2270    }
2271
2272    #[cfg(feature = "arbitrary")]
2273    mod impl_arbitrary {
2274        use super::*;
2275        use arbitrary::{Arbitrary, Unstructured};
2276        use core::ops::ControlFlow;
2277
2278        const MAX_NUM_PARTICIPANTS: u32 = 20;
2279        const MAX_ROUNDS: u32 = 10;
2280
2281        fn arbitrary_masks<'a>(u: &mut Unstructured<'a>) -> arbitrary::Result<Masks> {
2282            Ok(Masks {
2283                info_summary: Arbitrary::arbitrary(u)?,
2284                dealer: Arbitrary::arbitrary(u)?,
2285                pub_msg: Arbitrary::arbitrary(u)?,
2286                log: Arbitrary::arbitrary(u)?,
2287            })
2288        }
2289
2290        /// Pick at most `num` elements at random from `data`, returning them.
2291        ///
2292        /// This needs mutable access to perform a shuffle.
2293        ///
2294        fn pick<'a, T>(
2295            u: &mut Unstructured<'a>,
2296            num: usize,
2297            mut data: Vec<T>,
2298        ) -> arbitrary::Result<Vec<T>> {
2299            let len = data.len();
2300            let num = num.min(len);
2301            // Invariant: 0..start is a random subset of data.
2302            for start in 0..num {
2303                data.swap(start, u.int_in_range(start..=len - 1)?);
2304            }
2305            data.truncate(num);
2306            Ok(data)
2307        }
2308
2309        fn arbitrary_round<'a>(
2310            u: &mut Unstructured<'a>,
2311            num_participants: u32,
2312            last_successful_players: Option<&Set<u32>>,
2313        ) -> arbitrary::Result<Round> {
2314            let dealers = if let Some(players) = last_successful_players {
2315                let to_pick = u.int_in_range(players.quorum::<N3f1>() as usize..=players.len())?;
2316                pick(u, to_pick, players.into_iter().copied().collect())?
2317            } else {
2318                let to_pick = u.int_in_range(1..=num_participants as usize)?;
2319                pick(u, to_pick, (0..num_participants).collect())?
2320            };
2321            let players = {
2322                let to_pick = u.int_in_range(1..=num_participants as usize)?;
2323                pick(u, to_pick, (0..num_participants).collect())?
2324            };
2325            let pairs = dealers
2326                .iter()
2327                .flat_map(|d| players.iter().map(|p| (*d, *p)))
2328                .collect::<Vec<_>>();
2329            let pick_pair_set = |u: &mut Unstructured<'a>| {
2330                let num = u.int_in_range(0..=pairs.len())?;
2331                if num == 0 {
2332                    return Ok(BTreeSet::new());
2333                }
2334                Ok(pick(u, num, pairs.clone())?.into_iter().collect())
2335            };
2336            let pick_dealer_set = |u: &mut Unstructured<'a>| {
2337                let num = u.int_in_range(0..=dealers.len())?;
2338                if num == 0 {
2339                    return Ok(BTreeSet::new());
2340                }
2341                Ok(pick(u, num, dealers.clone())?.into_iter().collect())
2342            };
2343            let round = Round {
2344                no_acks: pick_pair_set(u)?,
2345                bad_shares: pick_pair_set(u)?,
2346                bad_player_sigs: {
2347                    let indices = pick_pair_set(u)?;
2348                    indices
2349                        .into_iter()
2350                        .map(|k| Ok((k, arbitrary_masks(u)?)))
2351                        .collect::<arbitrary::Result<_>>()?
2352                },
2353                bad_reveals: pick_pair_set(u)?,
2354                bad_dealer_sigs: {
2355                    let indices = pick_dealer_set(u)?;
2356                    indices
2357                        .into_iter()
2358                        .map(|k| Ok((k, arbitrary_masks(u)?)))
2359                        .collect::<arbitrary::Result<_>>()?
2360                },
2361                replace_shares: pick_dealer_set(u)?,
2362                shift_degrees: {
2363                    let indices = pick_dealer_set(u)?;
2364                    indices
2365                        .into_iter()
2366                        .map(|k| {
2367                            let expected = N3f1::quorum(players.len()) as i32 - 1;
2368                            let shift = u.int_in_range(1..=expected.max(1))?;
2369                            let shift = if bool::arbitrary(u)? { -shift } else { shift };
2370                            Ok((k, NonZeroI32::new(shift).expect("checked to not be zero")))
2371                        })
2372                        .collect::<arbitrary::Result<_>>()?
2373                },
2374                dealers,
2375                players,
2376            };
2377            Ok(round)
2378        }
2379
2380        impl<'a> Arbitrary<'a> for Plan {
2381            fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
2382                let num_participants = u.int_in_range(1..=MAX_NUM_PARTICIPANTS)?;
2383                let mut rounds = Vec::new();
2384                let mut last_successful_players: Option<Set<u32>> = None;
2385                u.arbitrary_loop(None, Some(MAX_ROUNDS), |u| {
2386                    let round =
2387                        arbitrary_round(u, num_participants, last_successful_players.as_ref())?;
2388                    if !round
2389                        .expect_failure(last_successful_players.as_ref().map(|x| x.len() as u32))
2390                    {
2391                        last_successful_players =
2392                            Some(round.players.iter().copied().try_collect().unwrap());
2393                    }
2394                    rounds.push(round);
2395                    Ok(ControlFlow::Continue(()))
2396                })?;
2397                let plan = Self {
2398                    num_participants: NZU32!(num_participants),
2399                    rounds,
2400                };
2401                plan.validate()
2402                    .map_err(|_| arbitrary::Error::IncorrectFormat)?;
2403                Ok(plan)
2404            }
2405        }
2406    }
2407}
2408
2409#[cfg(feature = "arbitrary")]
2410pub use test_plan::Plan as FuzzPlan;
2411
2412#[cfg(test)]
2413mod test {
2414    use super::{test_plan::*, *};
2415    use crate::{bls12381::primitives::variant::MinPk, ed25519};
2416    use anyhow::anyhow;
2417    use commonware_math::algebra::Random;
2418    use commonware_utils::{test_rng, N3f1};
2419    use core::num::NonZeroI32;
2420
2421    #[test]
2422    fn single_round() -> anyhow::Result<()> {
2423        Plan::new(NZU32!(4))
2424            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2425            .run::<MinPk>(0)
2426    }
2427
2428    #[test]
2429    fn multiple_rounds() -> anyhow::Result<()> {
2430        Plan::new(NZU32!(4))
2431            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2432            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2433            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2434            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2435            .run::<MinPk>(0)
2436    }
2437
2438    #[test]
2439    fn changing_committee() -> anyhow::Result<()> {
2440        Plan::new(NonZeroU32::new(5).unwrap())
2441            .with(Round::new(vec![0, 1, 2], vec![1, 2, 3]))
2442            .with(Round::new(vec![1, 2, 3], vec![2, 3, 4]))
2443            .with(Round::new(vec![2, 3, 4], vec![3, 4, 0]))
2444            .with(Round::new(vec![3, 4, 0], vec![4, 0, 1]))
2445            .run::<MinPk>(0)
2446    }
2447
2448    #[test]
2449    fn missing_ack() -> anyhow::Result<()> {
2450        // With 4 players, max_faults = 1, so 1 missing ack per dealer is OK
2451        Plan::new(NonZeroU32::new(4).unwrap())
2452            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 0))
2453            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 1))
2454            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 2))
2455            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 3))
2456            .run::<MinPk>(0)
2457    }
2458
2459    #[test]
2460    fn increasing_decreasing_committee() -> anyhow::Result<()> {
2461        Plan::new(NonZeroU32::new(5).unwrap())
2462            .with(Round::new(vec![0, 1], vec![0, 1, 2]))
2463            .with(Round::new(vec![0, 1, 2], vec![0, 1, 2, 3]))
2464            .with(Round::new(vec![0, 1, 2], vec![0, 1]))
2465            .with(Round::new(vec![0, 1], vec![0, 1, 2, 3, 4]))
2466            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1]))
2467            .run::<MinPk>(0)
2468    }
2469
2470    #[test]
2471    fn bad_reveal_fails() -> anyhow::Result<()> {
2472        Plan::new(NonZeroU32::new(4).unwrap())
2473            .with(Round::new(vec![0], vec![0, 1, 2, 3]).bad_reveal(0, 1))
2474            .run::<MinPk>(0)
2475    }
2476
2477    #[test]
2478    fn bad_share() -> anyhow::Result<()> {
2479        Plan::new(NonZeroU32::new(4).unwrap())
2480            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).bad_share(0, 1))
2481            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).bad_share(0, 2))
2482            .run::<MinPk>(0)
2483    }
2484
2485    #[test]
2486    fn shift_degree_fails() -> anyhow::Result<()> {
2487        Plan::new(NonZeroU32::new(4).unwrap())
2488            .with(Round::new(vec![0], vec![0, 1, 2, 3]).shift_degree(
2489                0,
2490                NonZeroI32::new(1).ok_or_else(|| anyhow!("invalid NZI32"))?,
2491            ))
2492            .run::<MinPk>(0)
2493    }
2494
2495    #[test]
2496    fn replace_share_fails() -> anyhow::Result<()> {
2497        Plan::new(NonZeroU32::new(4).unwrap())
2498            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2499            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).replace_share(0))
2500            .run::<MinPk>(0)
2501    }
2502
2503    #[test]
2504    fn too_many_reveals_dealer() -> anyhow::Result<()> {
2505        Plan::new(NonZeroU32::new(4).unwrap())
2506            .with(
2507                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
2508                    .no_ack(0, 0)
2509                    .no_ack(0, 1),
2510            )
2511            .run::<MinPk>(0)
2512    }
2513
2514    #[test]
2515    fn too_many_reveals_player() -> anyhow::Result<()> {
2516        Plan::new(NonZeroU32::new(4).unwrap())
2517            .with(
2518                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
2519                    .no_ack(0, 0)
2520                    .no_ack(1, 0)
2521                    .no_ack(3, 0),
2522            )
2523            .run::<MinPk>(0)
2524    }
2525
2526    #[test]
2527    fn bad_sigs() -> anyhow::Result<()> {
2528        Plan::new(NonZeroU32::new(4).unwrap())
2529            .with(
2530                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
2531                    .bad_dealer_sig(
2532                        0,
2533                        Masks {
2534                            log: vec![0xFF; 8],
2535                            ..Default::default()
2536                        },
2537                    )
2538                    .bad_player_sig(
2539                        0,
2540                        1,
2541                        Masks {
2542                            pub_msg: vec![0xFF; 8],
2543                            ..Default::default()
2544                        },
2545                    ),
2546            )
2547            .run::<MinPk>(0)
2548    }
2549
2550    #[test]
2551    fn issue_2745_regression() -> anyhow::Result<()> {
2552        Plan::new(NonZeroU32::new(6).unwrap())
2553            .with(
2554                Round::new(vec![0], vec![5, 1, 3, 0, 4])
2555                    .no_ack(0, 5)
2556                    .bad_share(0, 5),
2557            )
2558            .with(Round::new(vec![0, 1, 3, 4], vec![0]))
2559            .with(Round::new(vec![0], vec![0]))
2560            .run::<MinPk>(0)
2561    }
2562
2563    #[test]
2564    fn signed_dealer_log_commitment() -> Result<(), Error> {
2565        let sk = ed25519::PrivateKey::from_seed(0);
2566        let pk = sk.public_key();
2567        let info = Info::<MinPk, _>::new::<N3f1>(
2568            b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
2569            0,
2570            None,
2571            Default::default(),
2572            vec![sk.public_key()].try_into().unwrap(),
2573            vec![sk.public_key()].try_into().unwrap(),
2574        )?;
2575        let mut log0 = {
2576            let (dealer, _, _) =
2577                Dealer::start::<N3f1>(&mut test_rng(), info.clone(), sk.clone(), None)?;
2578            dealer.finalize::<N3f1>()
2579        };
2580        let mut log1 = {
2581            let (mut dealer, pub_msg, priv_msgs) =
2582                Dealer::start::<N3f1>(&mut test_rng(), info.clone(), sk.clone(), None)?;
2583            let mut player = Player::new(info.clone(), sk)?;
2584            let ack = player
2585                .dealer_message::<N3f1>(pk.clone(), pub_msg, priv_msgs[0].1.clone())
2586                .unwrap();
2587            dealer.receive_player_ack(pk, ack)?;
2588            dealer.finalize::<N3f1>()
2589        };
2590        std::mem::swap(&mut log0.log, &mut log1.log);
2591        assert!(log0.check(&info).is_none());
2592        assert!(log1.check(&info).is_none());
2593
2594        Ok(())
2595    }
2596
2597    #[test]
2598    fn test_dealer_priv_msg_redacted() {
2599        let mut rng = test_rng();
2600        let msg = DealerPrivMsg::new(Scalar::random(&mut rng));
2601        let debug = format!("{:?}", msg);
2602        assert!(debug.contains("REDACTED"));
2603    }
2604
2605    #[cfg(feature = "arbitrary")]
2606    mod conformance {
2607        use super::*;
2608        use commonware_codec::conformance::CodecConformance;
2609
2610        commonware_conformance::conformance_tests! {
2611            CodecConformance<Output<MinPk, ed25519::PublicKey>>,
2612            CodecConformance<DealerPubMsg<MinPk>>,
2613            CodecConformance<DealerPrivMsg>,
2614            CodecConformance<PlayerAck<ed25519::PublicKey>>,
2615            CodecConformance<AckOrReveal<ed25519::PublicKey>>,
2616            CodecConformance<DealerResult<ed25519::PublicKey>>,
2617            CodecConformance<DealerLog<MinPk, ed25519::PublicKey>>,
2618            CodecConformance<SignedDealerLog<MinPk, ed25519::PrivateKey>>,
2619        }
2620    }
2621}