Skip to main content

commonware_cryptography/bls12381/
dkg.rs

1//! Distributed Key Generation (DKG) and Resharing protocol for the BLS12-381 curve.
2//!
3//! This module implements an interactive Distributed Key Generation (DKG) and Resharing protocol
4//! for the BLS12-381 curve. Unlike other constructions, this construction does not require encrypted
5//! shares to be publicly broadcast to complete a DKG/Reshare. Shares, instead, are sent directly
6//! between dealers and players over an encrypted channel (which can be instantiated
7//! with [commonware-p2p](https://docs.rs/commonware-p2p)).
8//!
9//! The DKG is based on the "Joint-Feldman" construction from "Secure Distributed Key
10//! Generation for Discrete-Log Based Cryptosystems" (GJKR99) and Resharing is based
11//! on the construction described in "Redistributing secret shares to new access structures
12//! and its applications" (Desmedt97).
13//!
14//! # Overview
15//!
16//! The protocol involves _dealers_ and _players_. The dealers are trying to jointly create a shared
17//! key, and then distribute it among the players. The dealers may have pre-existing shares of a key
18//! from a previous round, in which case the goal is to re-distribute that key among the players,
19//! with fresh randomness.
20//!
21//! The protocol is also designed such that an external observer can figure out whether the protocol
22//! succeeded or failed, and learn of the public outputs of the protocol. This includes
23//! the participants in the protocol, and the public polynomial committing to the key
24//! and its sharing.
25//!
26//! # Usage
27//!
28//! ## Core Types
29//!
30//! * [`Info`]: Configuration for a DKG/Reshare round, containing the dealers, players, and optional previous output
31//! * [`Output`]: The public result of a successful DKG round, containing the public polynomial and player list
32//! * [`Share`]: A player's 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    /// Return a [`DealerLogSummary`] of the results in this log.
1012    ///
1013    /// This can be useful for observing the progress of the DKG.
1014    pub fn summary(&self) -> DealerLogSummary<P> {
1015        match &self.results {
1016            DealerResult::TooManyReveals => DealerLogSummary::TooManyReveals,
1017            DealerResult::Ok(map) => {
1018                let (reveals, acks): (Vec<_>, Vec<_>) =
1019                    map.iter_pairs().partition(|(_, a_r)| a_r.is_reveal());
1020                DealerLogSummary::Ok {
1021                    acks: acks
1022                        .into_iter()
1023                        .map(|(p, _)| p.clone())
1024                        .try_collect()
1025                        .expect("map keys are deduped"),
1026                    reveals: reveals
1027                        .into_iter()
1028                        .map(|(p, _)| p.clone())
1029                        .try_collect()
1030                        .expect("map keys are deduped"),
1031                }
1032            }
1033        }
1034    }
1035}
1036
1037/// Information about the reveals and acks in a [`DealerLog`].
1038// This exists to have a public interface we're happy maintaining, not leaking
1039// internal details about various things.
1040#[derive(Clone, Debug)]
1041pub enum DealerLogSummary<P> {
1042    /// The dealer is refusing to post any information, because they would have
1043    /// too many reveals otherwise.
1044    TooManyReveals,
1045    /// The dealer has some players who acked, and some players who didn't, that it's revealing.
1046    Ok { acks: Set<P>, reveals: Set<P> },
1047}
1048
1049#[cfg(feature = "arbitrary")]
1050impl<V: Variant, P: PublicKey> arbitrary::Arbitrary<'_> for DealerLog<V, P>
1051where
1052    P: for<'a> arbitrary::Arbitrary<'a>,
1053    V::Public: for<'a> arbitrary::Arbitrary<'a>,
1054    P::Signature: for<'a> arbitrary::Arbitrary<'a>,
1055{
1056    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1057        let pub_msg = u.arbitrary()?;
1058        let results = u.arbitrary()?;
1059        Ok(Self { pub_msg, results })
1060    }
1061}
1062
1063/// A [`DealerLog`], but identified to and signed by a dealer.
1064///
1065/// The [`SignedDealerLog::check`] method allows extracting a public key (the dealer)
1066/// and a [`DealerLog`] from this struct.
1067///
1068/// This avoids having to trust some other party or process for knowing that a
1069/// dealer actually produced a log.
1070#[derive(Clone, Debug)]
1071pub struct SignedDealerLog<V: Variant, S: Signer> {
1072    dealer: S::PublicKey,
1073    log: DealerLog<V, S::PublicKey>,
1074    sig: S::Signature,
1075}
1076
1077impl<V: Variant, S: Signer> PartialEq for SignedDealerLog<V, S> {
1078    fn eq(&self, other: &Self) -> bool {
1079        self.dealer == other.dealer && self.log == other.log && self.sig == other.sig
1080    }
1081}
1082
1083impl<V: Variant, S: Signer> SignedDealerLog<V, S> {
1084    fn sign(sk: &S, info: &Info<V, S::PublicKey>, log: DealerLog<V, S::PublicKey>) -> Self {
1085        let sig = transcript_for_log(info, &log).sign(sk);
1086        Self {
1087            dealer: sk.public_key(),
1088            log,
1089            sig,
1090        }
1091    }
1092
1093    /// Check this log for a particular round.
1094    ///
1095    /// This will produce the public key of the dealer that signed this log,
1096    /// and the underlying log that they signed.
1097    ///
1098    /// This will return [`Option::None`] if the check fails.
1099    #[allow(clippy::type_complexity)]
1100    pub fn check(
1101        self,
1102        info: &Info<V, S::PublicKey>,
1103    ) -> Option<(S::PublicKey, DealerLog<V, S::PublicKey>)> {
1104        if !transcript_for_log(info, &self.log).verify(&self.dealer, &self.sig) {
1105            return None;
1106        }
1107        Some((self.dealer, self.log))
1108    }
1109}
1110
1111impl<V: Variant, S: Signer> EncodeSize for SignedDealerLog<V, S> {
1112    fn encode_size(&self) -> usize {
1113        self.dealer.encode_size() + self.log.encode_size() + self.sig.encode_size()
1114    }
1115}
1116
1117impl<V: Variant, S: Signer> Write for SignedDealerLog<V, S> {
1118    fn write(&self, buf: &mut impl bytes::BufMut) {
1119        self.dealer.write(buf);
1120        self.log.write(buf);
1121        self.sig.write(buf);
1122    }
1123}
1124
1125impl<V: Variant, S: Signer> Read for SignedDealerLog<V, S> {
1126    type Cfg = NonZeroU32;
1127
1128    fn read_cfg(
1129        buf: &mut impl bytes::Buf,
1130        cfg: &Self::Cfg,
1131    ) -> Result<Self, commonware_codec::Error> {
1132        Ok(Self {
1133            dealer: ReadExt::read(buf)?,
1134            log: Read::read_cfg(buf, cfg)?,
1135            sig: ReadExt::read(buf)?,
1136        })
1137    }
1138}
1139
1140#[cfg(feature = "arbitrary")]
1141impl<V: Variant, S: Signer> arbitrary::Arbitrary<'_> for SignedDealerLog<V, S>
1142where
1143    S::PublicKey: for<'a> arbitrary::Arbitrary<'a>,
1144    V::Public: for<'a> arbitrary::Arbitrary<'a>,
1145    S::Signature: for<'a> arbitrary::Arbitrary<'a>,
1146{
1147    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1148        let dealer = u.arbitrary()?;
1149        let log = u.arbitrary()?;
1150        let sig = u.arbitrary()?;
1151        Ok(Self { dealer, log, sig })
1152    }
1153}
1154
1155fn transcript_for_round<V: Variant, P: PublicKey>(info: &Info<V, P>) -> Transcript {
1156    Transcript::resume(info.summary)
1157}
1158
1159fn transcript_for_ack<V: Variant, P: PublicKey>(
1160    transcript: &Transcript,
1161    dealer: &P,
1162    pub_msg: &DealerPubMsg<V>,
1163) -> Transcript {
1164    let mut out = transcript.fork(SIG_ACK);
1165    out.commit(dealer.encode());
1166    out.commit(pub_msg.encode());
1167    out
1168}
1169
1170fn transcript_for_log<V: Variant, P: PublicKey>(
1171    info: &Info<V, P>,
1172    log: &DealerLog<V, P>,
1173) -> Transcript {
1174    let mut out = transcript_for_round(info).fork(SIG_LOG);
1175    out.commit(log.encode());
1176    out
1177}
1178
1179pub struct Dealer<V: Variant, S: Signer> {
1180    me: S,
1181    info: Info<V, S::PublicKey>,
1182    pub_msg: DealerPubMsg<V>,
1183    results: Map<S::PublicKey, AckOrReveal<S::PublicKey>>,
1184    transcript: Transcript,
1185}
1186
1187impl<V: Variant, S: Signer> Dealer<V, S> {
1188    /// Create a [`Dealer`].
1189    ///
1190    /// This needs randomness, to generate a dealing.
1191    ///
1192    /// We also need the dealer's private key, in order to produce the [`SignedDealerLog`].
1193    ///
1194    /// If we're doing a reshare, the dealer should have a share from the previous round.
1195    ///
1196    /// This will produce the [`Dealer`], a [`DealerPubMsg`] to send to every player,
1197    /// and a list of [`DealerPrivMsg`]s, along with which players those need to
1198    /// be sent to.
1199    ///
1200    /// The public message can be sent in the clear, but it's important that players
1201    /// know which dealer sent what public message. You MUST ensure that dealers
1202    /// cannot impersonate each-other when sending this message.
1203    ///
1204    /// The private message MUST be sent encrypted (or, in some other way, privately)
1205    /// to the target player. Similarly, that player MUST be convinced that this dealer
1206    /// sent it that message, without any possibility of impersonation. A simple way
1207    /// to provide both guarantees is through an authenticated channel, e.g. via
1208    /// [crate::handshake], or [commonware-p2p](https://docs.rs/commonware-p2p/latest/commonware_p2p/).
1209    #[allow(clippy::type_complexity)]
1210    pub fn start<M: Faults>(
1211        mut rng: impl CryptoRngCore,
1212        info: Info<V, S::PublicKey>,
1213        me: S,
1214        share: Option<Share>,
1215    ) -> Result<(Self, DealerPubMsg<V>, Vec<(S::PublicKey, DealerPrivMsg)>), Error> {
1216        // Check that this dealer is defined in the round.
1217        info.dealer_index(&me.public_key())?;
1218        let share = info.unwrap_or_random_share(
1219            &mut rng,
1220            // We are extracting the private scalar from `Secret` protection because
1221            // `Poly::new_with_constant` requires an owned value. The extracted scalar is
1222            // scoped to this function and will be zeroized on drop (i.e. the secret is
1223            // only exposed for the duration of this function).
1224            share.map(|x| x.private.expose_unwrap()),
1225        )?;
1226        let my_poly = Poly::new_with_constant(&mut rng, info.degree::<M>(), share);
1227        let priv_msgs = info
1228            .players
1229            .iter()
1230            .map(|pk| {
1231                (
1232                    pk.clone(),
1233                    DealerPrivMsg::new(my_poly.eval_msm(
1234                        &info.player_scalar(pk).expect("player should exist"),
1235                        &Sequential,
1236                    )),
1237                )
1238            })
1239            .collect::<Vec<_>>();
1240        let results: Map<_, _> = priv_msgs
1241            .clone()
1242            .into_iter()
1243            .map(|(pk, priv_msg)| (pk, AckOrReveal::Reveal(priv_msg)))
1244            .try_collect()
1245            .expect("players are unique");
1246        let commitment = Poly::commit(my_poly);
1247        let pub_msg = DealerPubMsg { commitment };
1248        let transcript = {
1249            let t = transcript_for_round(&info);
1250            transcript_for_ack(&t, &me.public_key(), &pub_msg)
1251        };
1252        let this = Self {
1253            me,
1254            info,
1255            pub_msg: pub_msg.clone(),
1256            results,
1257            transcript,
1258        };
1259        Ok((this, pub_msg, priv_msgs))
1260    }
1261
1262    /// Process an acknowledgement from a player.
1263    ///
1264    /// Acknowledgements should really only be processed once per player,
1265    /// but this method is idempotent nonetheless.
1266    pub fn receive_player_ack(
1267        &mut self,
1268        player: S::PublicKey,
1269        ack: PlayerAck<S::PublicKey>,
1270    ) -> Result<(), Error> {
1271        let res_mut = self
1272            .results
1273            .get_value_mut(&player)
1274            .ok_or(Error::UnknownPlayer)?;
1275        if self.transcript.verify(&player, &ack.sig) {
1276            *res_mut = AckOrReveal::Ack(ack);
1277        }
1278        Ok(())
1279    }
1280
1281    /// Finalize the dealer, producing a signed log.
1282    ///
1283    /// This should be called at the point where no more acks will be processed.
1284    pub fn finalize<M: Faults>(self) -> SignedDealerLog<V, S> {
1285        let reveals = self
1286            .results
1287            .values()
1288            .iter()
1289            .filter(|x| x.is_reveal())
1290            .count() as u32;
1291        // Omit results if there are too many reveals.
1292        let results = if reveals > self.info.max_reveals::<M>() {
1293            DealerResult::TooManyReveals
1294        } else {
1295            DealerResult::Ok(self.results)
1296        };
1297        let log = DealerLog {
1298            pub_msg: self.pub_msg,
1299            results,
1300        };
1301        SignedDealerLog::sign(&self.me, &self.info, log)
1302    }
1303}
1304
1305#[allow(clippy::type_complexity)]
1306fn select<V: Variant, P: PublicKey, M: Faults>(
1307    info: &Info<V, P>,
1308    logs: BTreeMap<P, DealerLog<V, P>>,
1309) -> Result<Map<P, DealerLog<V, P>>, Error> {
1310    let required_commitments = info.required_commitments::<M>() as usize;
1311    let transcript = transcript_for_round(info);
1312    let out = logs
1313        .into_iter()
1314        .filter_map(|(dealer, log)| {
1315            info.dealer_index(&dealer).ok()?;
1316            if !info.check_dealer_pub_msg::<M>(&dealer, &log.pub_msg) {
1317                return None;
1318            }
1319            let results_iter = log.zip_players(&info.players)?;
1320            let transcript = transcript_for_ack(&transcript, &dealer, &log.pub_msg);
1321            let mut reveal_count = 0;
1322            let max_reveals = info.max_reveals::<M>();
1323            for (player, result) in results_iter {
1324                match result {
1325                    AckOrReveal::Ack(ack) => {
1326                        if !transcript.verify(player, &ack.sig) {
1327                            return None;
1328                        }
1329                    }
1330                    AckOrReveal::Reveal(priv_msg) => {
1331                        reveal_count += 1;
1332                        if reveal_count > max_reveals {
1333                            return None;
1334                        }
1335                        if !info.check_dealer_priv_msg(player, &log.pub_msg, priv_msg) {
1336                            return None;
1337                        }
1338                    }
1339                }
1340            }
1341            Some((dealer, log))
1342        })
1343        .take(required_commitments)
1344        .try_collect::<Map<_, _>>()
1345        .expect("logs has at most one entry per dealer");
1346    if out.len() < required_commitments {
1347        return Err(Error::DkgFailed);
1348    }
1349    Ok(out)
1350}
1351
1352struct ObserveInner<V: Variant, P: PublicKey> {
1353    output: Output<V, P>,
1354    weights: Option<Interpolator<P, Scalar>>,
1355}
1356
1357impl<V: Variant, P: PublicKey> ObserveInner<V, P> {
1358    fn reckon<M: Faults>(
1359        info: Info<V, P>,
1360        selected: Map<P, DealerLog<V, P>>,
1361        strategy: &impl Strategy,
1362    ) -> Result<Self, Error> {
1363        // Track players with too many reveals
1364        let max_faults = info.players.max_faults::<M>();
1365        let mut reveal_counts: BTreeMap<P, u32> = BTreeMap::new();
1366        let mut revealed = Vec::new();
1367        for log in selected.values() {
1368            let Some(iter) = log.zip_players(&info.players) else {
1369                continue;
1370            };
1371            for (player, result) in iter {
1372                if !result.is_reveal() {
1373                    continue;
1374                }
1375                let count = reveal_counts.entry(player.clone()).or_insert(0);
1376                *count += 1;
1377                if *count == max_faults + 1 {
1378                    revealed.push(player.clone());
1379                }
1380            }
1381        }
1382        let revealed: Set<P> = revealed
1383            .into_iter()
1384            .try_collect()
1385            .expect("players are unique");
1386
1387        // Extract dealers before consuming selected
1388        let dealers: Set<P> = selected
1389            .keys()
1390            .iter()
1391            .cloned()
1392            .try_collect()
1393            .expect("selected dealers are unique");
1394
1395        // Recover the public polynomial
1396        let (public, weights) = if let Some(previous) = info.previous.as_ref() {
1397            let weights = previous
1398                .public()
1399                .mode()
1400                .subset_interpolator(previous.players(), selected.keys())
1401                .expect("the result of select should produce a valid subset");
1402            let commitments = selected
1403                .into_iter()
1404                .map(|(dealer, log)| (dealer, log.pub_msg.commitment))
1405                .try_collect::<Map<_, _>>()
1406                .expect("Map should have unique keys");
1407            let public = weights
1408                .interpolate(&commitments, strategy)
1409                .expect("select checks that enough points have been provided");
1410            if previous.public().public() != public.constant() {
1411                return Err(Error::DkgFailed);
1412            }
1413            (public, Some(weights))
1414        } else {
1415            let mut public = Poly::zero();
1416            for log in selected.values() {
1417                public += &log.pub_msg.commitment;
1418            }
1419            (public, None)
1420        };
1421        let n = info.players.len() as u32;
1422        let output = Output {
1423            summary: info.summary,
1424            public: Sharing::new(info.mode, NZU32!(n), public),
1425            dealers,
1426            players: info.players,
1427            revealed,
1428        };
1429        Ok(Self { output, weights })
1430    }
1431}
1432
1433/// Observe the result of a DKG, using the public results.
1434///
1435/// The log mapping dealers to their log is the shared piece of information
1436/// that the participants (players, observers) of the DKG must all agree on.
1437///
1438/// From this log, we can (potentially, as the DKG can fail) compute the public output.
1439///
1440/// This will only ever return [`Error::DkgFailed`].
1441pub fn observe<V: Variant, P: PublicKey, M: Faults>(
1442    info: Info<V, P>,
1443    logs: BTreeMap<P, DealerLog<V, P>>,
1444    strategy: &impl Strategy,
1445) -> Result<Output<V, P>, Error> {
1446    let selected = select::<V, P, M>(&info, logs)?;
1447    ObserveInner::<V, P>::reckon::<M>(info, selected, strategy).map(|x| x.output)
1448}
1449
1450/// Represents a player in the DKG / reshare process.
1451///
1452/// The player is attempting to get a share of the key.
1453///
1454/// They need not have participated in prior rounds.
1455pub struct Player<V: Variant, S: Signer> {
1456    me: S,
1457    me_pub: S::PublicKey,
1458    info: Info<V, S::PublicKey>,
1459    index: Participant,
1460    transcript: Transcript,
1461    view: BTreeMap<S::PublicKey, (DealerPubMsg<V>, DealerPrivMsg)>,
1462}
1463
1464impl<V: Variant, S: Signer> Player<V, S> {
1465    /// Create a new [`Player`].
1466    ///
1467    /// We need the player's private key in order to sign messages.
1468    pub fn new(info: Info<V, S::PublicKey>, me: S) -> Result<Self, Error> {
1469        let me_pub = me.public_key();
1470        Ok(Self {
1471            index: info.player_index(&me_pub)?,
1472            me,
1473            me_pub,
1474            transcript: transcript_for_round(&info),
1475            info,
1476            view: BTreeMap::new(),
1477        })
1478    }
1479
1480    /// Process a message from a dealer.
1481    ///
1482    /// It's important that nobody can impersonate the dealer, and that the
1483    /// private message was not exposed to anyone else. A convenient way to
1484    /// provide this is by using an authenticated channel, e.g. via
1485    /// [crate::handshake], or [commonware-p2p](https://docs.rs/commonware-p2p/latest/commonware_p2p/).
1486    pub fn dealer_message<M: Faults>(
1487        &mut self,
1488        dealer: S::PublicKey,
1489        pub_msg: DealerPubMsg<V>,
1490        priv_msg: DealerPrivMsg,
1491    ) -> Option<PlayerAck<S::PublicKey>> {
1492        if self.view.contains_key(&dealer) {
1493            return None;
1494        }
1495        self.info.dealer_index(&dealer).ok()?;
1496        if !self.info.check_dealer_pub_msg::<M>(&dealer, &pub_msg) {
1497            return None;
1498        }
1499        if !self
1500            .info
1501            .check_dealer_priv_msg(&self.me_pub, &pub_msg, &priv_msg)
1502        {
1503            return None;
1504        }
1505        let sig = transcript_for_ack(&self.transcript, &dealer, &pub_msg).sign(&self.me);
1506        self.view.insert(dealer, (pub_msg, priv_msg));
1507        Some(PlayerAck { sig })
1508    }
1509
1510    /// Finalize the player, producing an output, and a share.
1511    ///
1512    /// This should agree with [`observe`], in terms of `Ok` vs `Err` and the
1513    /// public output, so long as the logs agree. It's crucial that the players
1514    /// come to agreement, in some way, on exactly which logs they need to use
1515    /// for finalize.
1516    ///
1517    /// This will only ever return [`Error::DkgFailed`].
1518    pub fn finalize<M: Faults>(
1519        self,
1520        logs: BTreeMap<S::PublicKey, DealerLog<V, S::PublicKey>>,
1521        strategy: &impl Strategy,
1522    ) -> Result<(Output<V, S::PublicKey>, Share), Error> {
1523        let selected = select::<V, S::PublicKey, M>(&self.info, logs)?;
1524        // We are extracting the private scalars from `Secret` protection
1525        // because interpolation/summation needs owned scalars for polynomial
1526        // arithmetic. The extracted scalars are scoped to this function and
1527        // will be zeroized on drop (i.e. the secrets are only exposed for the
1528        // duration of this function).
1529        let dealings = selected
1530            .iter_pairs()
1531            .map(|(dealer, log)| {
1532                let share = self
1533                    .view
1534                    .get(dealer)
1535                    .map(|(_, priv_msg)| priv_msg.share.clone().expose_unwrap())
1536                    .unwrap_or_else(|| {
1537                        log.get_reveal(&self.me_pub).map_or_else(
1538                            || {
1539                                unreachable!(
1540                                    "select didn't check dealer reveal, or we're not a player?"
1541                                )
1542                            },
1543                            |priv_msg| priv_msg.share.clone().expose_unwrap(),
1544                        )
1545                    });
1546                (dealer.clone(), share)
1547            })
1548            .try_collect::<Map<_, _>>()
1549            .expect("select produces at most one entry per dealer");
1550        let ObserveInner { output, weights } =
1551            ObserveInner::<V, S::PublicKey>::reckon::<M>(self.info, selected, strategy)?;
1552        let private = weights.map_or_else(
1553            || {
1554                let mut out = <Scalar as Additive>::zero();
1555                for s in dealings.values() {
1556                    out += s;
1557                }
1558                out
1559            },
1560            |weights| {
1561                weights
1562                    .interpolate(&dealings, strategy)
1563                    .expect("select ensures that we can recover")
1564            },
1565        );
1566        let share = Share::new(self.index, Private::new(private));
1567        Ok((output, share))
1568    }
1569}
1570
1571/// The result of dealing shares to players.
1572pub type DealResult<V, P> = Result<(Output<V, P>, Map<P, Share>), Error>;
1573
1574/// Simply distribute shares at random, instead of performing a distributed protocol.
1575pub fn deal<V: Variant, P: Clone + Ord, M: Faults>(
1576    mut rng: impl CryptoRngCore,
1577    mode: Mode,
1578    players: Set<P>,
1579) -> DealResult<V, P> {
1580    if players.is_empty() {
1581        return Err(Error::NumPlayers(0));
1582    }
1583    let n = NZU32!(players.len() as u32);
1584    let t = players.quorum::<M>();
1585    let private = Poly::new(&mut rng, t - 1);
1586    let shares: Map<_, _> = players
1587        .iter()
1588        .enumerate()
1589        .map(|(i, p)| {
1590            let participant = Participant::from_usize(i);
1591            let eval = private.eval_msm(
1592                &mode
1593                    .scalar(n, participant)
1594                    .expect("player index should be valid"),
1595                &Sequential,
1596            );
1597            let share = Share::new(participant, Private::new(eval));
1598            (p.clone(), share)
1599        })
1600        .try_collect()
1601        .expect("players are unique");
1602    let output = Output {
1603        summary: Summary::random(&mut rng),
1604        public: Sharing::new(mode, n, Poly::commit(private)),
1605        dealers: players.clone(),
1606        players,
1607        revealed: Set::default(),
1608    };
1609    Ok((output, shares))
1610}
1611
1612/// Like [`deal`], but without linking the result to specific public keys.
1613///
1614/// This can be more convenient for testing, where you don't want to go through
1615/// the trouble of generating signing keys. The downside is that the result isn't
1616/// compatible with subsequent DKGs, which need an [`Output`].
1617pub fn deal_anonymous<V: Variant, M: Faults>(
1618    rng: impl CryptoRngCore,
1619    mode: Mode,
1620    n: NonZeroU32,
1621) -> (Sharing<V>, Vec<Share>) {
1622    let players = (0..n.get()).try_collect().unwrap();
1623    let (output, shares) = deal::<V, _, M>(rng, mode, players).unwrap();
1624    (output.public().clone(), shares.values().to_vec())
1625}
1626
1627#[cfg(any(feature = "arbitrary", test))]
1628mod test_plan {
1629    use super::*;
1630    use crate::{
1631        bls12381::primitives::{
1632            ops::{self, threshold},
1633            variant::Variant,
1634        },
1635        ed25519, PublicKey,
1636    };
1637    use anyhow::anyhow;
1638    use bytes::BytesMut;
1639    use commonware_utils::{Faults, N3f1, TryCollect};
1640    use core::num::NonZeroI32;
1641    use rand::{rngs::StdRng, SeedableRng as _};
1642    use std::collections::BTreeSet;
1643
1644    /// Apply a mask to some bytes, returning whether or not a modification happened
1645    fn apply_mask(bytes: &mut BytesMut, mask: &[u8]) -> bool {
1646        let mut modified = false;
1647        for (l, &r) in bytes.iter_mut().zip(mask.iter()) {
1648            modified |= r != 0;
1649            *l ^= r;
1650        }
1651        modified
1652    }
1653
1654    #[derive(Clone, Default, Debug)]
1655    pub struct Masks {
1656        pub info_summary: Vec<u8>,
1657        pub dealer: Vec<u8>,
1658        pub pub_msg: Vec<u8>,
1659        pub log: Vec<u8>,
1660    }
1661
1662    impl Masks {
1663        fn transcript_for_round<V: Variant, P: PublicKey>(
1664            &self,
1665            info: &Info<V, P>,
1666        ) -> anyhow::Result<(bool, Transcript)> {
1667            let mut summary_bs = info.summary.encode_mut();
1668            let modified = apply_mask(&mut summary_bs, &self.info_summary);
1669            let summary = Summary::read(&mut summary_bs)?;
1670            Ok((modified, Transcript::resume(summary)))
1671        }
1672
1673        fn transcript_for_player_ack<V: Variant, P: PublicKey>(
1674            &self,
1675            info: &Info<V, P>,
1676            dealer: &P,
1677            pub_msg: &DealerPubMsg<V>,
1678        ) -> anyhow::Result<(bool, Transcript)> {
1679            let (mut modified, transcript) = self.transcript_for_round(info)?;
1680            let mut transcript = transcript.fork(SIG_ACK);
1681
1682            let mut dealer_bs = dealer.encode_mut();
1683            modified |= apply_mask(&mut dealer_bs, &self.dealer);
1684            transcript.commit(&mut dealer_bs);
1685
1686            let mut pub_msg_bs = pub_msg.encode_mut();
1687            modified |= apply_mask(&mut pub_msg_bs, &self.pub_msg);
1688            transcript.commit(&mut pub_msg_bs);
1689
1690            Ok((modified, transcript))
1691        }
1692
1693        fn transcript_for_signed_dealer_log<V: Variant, P: PublicKey>(
1694            &self,
1695            info: &Info<V, P>,
1696            log: &DealerLog<V, P>,
1697        ) -> anyhow::Result<(bool, Transcript)> {
1698            let (mut modified, transcript) = self.transcript_for_round(info)?;
1699            let mut transcript = transcript.fork(SIG_LOG);
1700
1701            let mut log_bs = log.encode_mut();
1702            modified |= apply_mask(&mut log_bs, &self.log);
1703            transcript.commit(&mut log_bs);
1704
1705            Ok((modified, transcript))
1706        }
1707    }
1708
1709    /// A round in the DKG test plan.
1710    #[derive(Debug, Default)]
1711    pub struct Round {
1712        dealers: Vec<u32>,
1713        players: Vec<u32>,
1714        no_acks: BTreeSet<(u32, u32)>,
1715        bad_shares: BTreeSet<(u32, u32)>,
1716        bad_player_sigs: BTreeMap<(u32, u32), Masks>,
1717        bad_reveals: BTreeSet<(u32, u32)>,
1718        bad_dealer_sigs: BTreeMap<u32, Masks>,
1719        replace_shares: BTreeSet<u32>,
1720        shift_degrees: BTreeMap<u32, NonZeroI32>,
1721    }
1722
1723    impl Round {
1724        pub fn new(dealers: Vec<u32>, players: Vec<u32>) -> Self {
1725            Self {
1726                dealers,
1727                players,
1728                ..Default::default()
1729            }
1730        }
1731
1732        pub fn no_ack(mut self, dealer: u32, player: u32) -> Self {
1733            self.no_acks.insert((dealer, player));
1734            self
1735        }
1736
1737        pub fn bad_share(mut self, dealer: u32, player: u32) -> Self {
1738            self.bad_shares.insert((dealer, player));
1739            self
1740        }
1741
1742        pub fn bad_player_sig(mut self, dealer: u32, player: u32, masks: Masks) -> Self {
1743            self.bad_player_sigs.insert((dealer, player), masks);
1744            self
1745        }
1746
1747        pub fn bad_reveal(mut self, dealer: u32, player: u32) -> Self {
1748            self.bad_reveals.insert((dealer, player));
1749            self
1750        }
1751
1752        pub fn bad_dealer_sig(mut self, dealer: u32, masks: Masks) -> Self {
1753            self.bad_dealer_sigs.insert(dealer, masks);
1754            self
1755        }
1756
1757        pub fn replace_share(mut self, dealer: u32) -> Self {
1758            self.replace_shares.insert(dealer);
1759            self
1760        }
1761
1762        pub fn shift_degree(mut self, dealer: u32, shift: NonZeroI32) -> Self {
1763            self.shift_degrees.insert(dealer, shift);
1764            self
1765        }
1766
1767        /// Validate that this round is well-formed given the number of participants
1768        /// and the previous successful round's players.
1769        pub fn validate(
1770            &self,
1771            num_participants: u32,
1772            previous_players: Option<&[u32]>,
1773        ) -> anyhow::Result<()> {
1774            if self.dealers.is_empty() {
1775                return Err(anyhow!("dealers is empty"));
1776            }
1777            if self.players.is_empty() {
1778                return Err(anyhow!("players is empty"));
1779            }
1780            // Check dealer/player ranges
1781            for &d in &self.dealers {
1782                if d >= num_participants {
1783                    return Err(anyhow!("dealer {d} out of range [1, {num_participants}]"));
1784                }
1785            }
1786            for &p in &self.players {
1787                if p >= num_participants {
1788                    return Err(anyhow!("player {p} out of range [1, {num_participants}]"));
1789                }
1790            }
1791
1792            // If there's a previous round, check dealer constraints
1793            if let Some(prev_players) = previous_players {
1794                // Every dealer must have been a player in the previous round
1795                for &d in &self.dealers {
1796                    if !prev_players.contains(&d) {
1797                        return Err(anyhow!("dealer {d} was not a player in previous round"));
1798                    }
1799                }
1800                // Must have >= quorum(prev_players) dealers
1801                let required = N3f1::quorum(prev_players.len());
1802                if (self.dealers.len() as u32) < required {
1803                    return Err(anyhow!(
1804                        "not enough dealers: have {}, need {} (quorum of {} previous players)",
1805                        self.dealers.len(),
1806                        required,
1807                        prev_players.len()
1808                    ));
1809                }
1810            }
1811
1812            Ok(())
1813        }
1814
1815        fn bad(&self, previous_successful_round: bool, dealer: u32) -> bool {
1816            if self.replace_shares.contains(&dealer) && previous_successful_round {
1817                return true;
1818            }
1819            if let Some(shift) = self.shift_degrees.get(&dealer) {
1820                let degree = N3f1::quorum(self.players.len()) as i32 - 1;
1821                // We shift the degree, but saturate at 0, so it's possible
1822                // that the shift isn't actually doing anything.
1823                //
1824                // This is effectively the same as checking degree == 0 && shift < 0,
1825                // but matches what ends up happening a bit better.
1826                if (degree + shift.get()).max(0) != degree {
1827                    return true;
1828                }
1829            }
1830            if self.bad_reveals.iter().any(|&(d, _)| d == dealer) {
1831                return true;
1832            }
1833            let revealed_players = self
1834                .bad_shares
1835                .iter()
1836                .copied()
1837                .chain(self.no_acks.iter().copied())
1838                .filter_map(|(d, p)| if d == dealer { Some(p) } else { None })
1839                .collect::<BTreeSet<_>>();
1840            revealed_players.len() as u32 > N3f1::max_faults(self.players.len())
1841        }
1842
1843        /// Determine if this round is expected to fail.
1844        fn expect_failure(&self, previous_successful_round: Option<u32>) -> bool {
1845            let good_dealer_count = self
1846                .dealers
1847                .iter()
1848                .filter(|&&d| !self.bad(previous_successful_round.is_some(), d))
1849                .count();
1850            let required = previous_successful_round
1851                .map(N3f1::quorum)
1852                .unwrap_or_default()
1853                .max(N3f1::quorum(self.dealers.len())) as usize;
1854            good_dealer_count < required
1855        }
1856    }
1857
1858    /// A DKG test plan consisting of multiple rounds.
1859    #[derive(Debug)]
1860    pub struct Plan {
1861        num_participants: NonZeroU32,
1862        rounds: Vec<Round>,
1863    }
1864
1865    impl Plan {
1866        pub const fn new(num_participants: NonZeroU32) -> Self {
1867            Self {
1868                num_participants,
1869                rounds: Vec::new(),
1870            }
1871        }
1872
1873        pub fn with(mut self, round: Round) -> Self {
1874            self.rounds.push(round);
1875            self
1876        }
1877
1878        /// Validate the entire plan.
1879        fn validate(&self) -> anyhow::Result<()> {
1880            let mut last_successful_players: Option<Vec<u32>> = None;
1881
1882            for round in &self.rounds {
1883                round.validate(
1884                    self.num_participants.get(),
1885                    last_successful_players.as_deref(),
1886                )?;
1887
1888                // If this round is expected to succeed, update last_successful_players
1889                if !round.expect_failure(last_successful_players.as_ref().map(|x| x.len() as u32)) {
1890                    last_successful_players = Some(round.players.clone());
1891                }
1892            }
1893            Ok(())
1894        }
1895
1896        /// Run the test plan with a given seed.
1897        pub fn run<V: Variant>(self, seed: u64) -> anyhow::Result<()> {
1898            self.validate()?;
1899
1900            let mut rng = StdRng::seed_from_u64(seed);
1901
1902            // Generate keys for all participants (1-indexed to num_participants)
1903            let keys = (0..self.num_participants.get())
1904                .map(|_| ed25519::PrivateKey::random(&mut rng))
1905                .collect::<Vec<_>>();
1906
1907            // Precompute mapping from public key to key index to avoid confusion
1908            // between key indices and positions in sorted Sets.
1909            let pk_to_key_idx: BTreeMap<ed25519::PublicKey, u32> = keys
1910                .iter()
1911                .enumerate()
1912                .map(|(i, k)| (k.public_key(), i as u32))
1913                .collect();
1914
1915            // The max_read_size needs to account for shifted polynomial degrees.
1916            // Find the maximum positive shift across all rounds.
1917            let max_shift = self
1918                .rounds
1919                .iter()
1920                .flat_map(|r| r.shift_degrees.values())
1921                .map(|s| s.get())
1922                .max()
1923                .unwrap_or(0)
1924                .max(0) as u32;
1925            let max_read_size =
1926                NonZeroU32::new(self.num_participants.get() + max_shift).expect("non-zero");
1927
1928            let mut previous_output: Option<Output<V, ed25519::PublicKey>> = None;
1929            let mut shares: BTreeMap<ed25519::PublicKey, Share> = BTreeMap::new();
1930            let mut threshold_public_key: Option<V::Public> = None;
1931
1932            for (i_round, round) in self.rounds.into_iter().enumerate() {
1933                let previous_successful_round =
1934                    previous_output.as_ref().map(|o| o.players.len() as u32);
1935
1936                let dealer_set = round
1937                    .dealers
1938                    .iter()
1939                    .map(|&i| keys[i as usize].public_key())
1940                    .try_collect::<Set<_>>()
1941                    .unwrap();
1942                let player_set: Set<ed25519::PublicKey> = round
1943                    .players
1944                    .iter()
1945                    .map(|&i| keys[i as usize].public_key())
1946                    .try_collect()
1947                    .unwrap();
1948
1949                // Create round info
1950                let info = Info::new::<N3f1>(
1951                    b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
1952                    i_round as u64,
1953                    previous_output.clone(),
1954                    Default::default(),
1955                    dealer_set.clone(),
1956                    player_set.clone(),
1957                )?;
1958
1959                let mut players: Map<_, _> = round
1960                    .players
1961                    .iter()
1962                    .map(|&i| {
1963                        let sk = keys[i as usize].clone();
1964                        let pk = sk.public_key();
1965                        let player = Player::new(info.clone(), sk)?;
1966                        Ok((pk, player))
1967                    })
1968                    .collect::<anyhow::Result<Vec<_>>>()?
1969                    .try_into()
1970                    .unwrap();
1971
1972                // Run dealer protocol
1973                let mut dealer_logs = BTreeMap::new();
1974                for &i_dealer in &round.dealers {
1975                    let sk = keys[i_dealer as usize].clone();
1976                    let pk = sk.public_key();
1977                    let share = match (shares.get(&pk), round.replace_shares.contains(&i_dealer)) {
1978                        (None, _) => None,
1979                        (Some(s), false) => Some(s.clone()),
1980                        (Some(_), true) => Some(Share::new(
1981                            Participant::new(i_dealer),
1982                            Private::random(&mut rng),
1983                        )),
1984                    };
1985
1986                    // Start dealer (with potential modifications)
1987                    let (mut dealer, pub_msg, mut priv_msgs) =
1988                        if let Some(shift) = round.shift_degrees.get(&i_dealer) {
1989                            // Create dealer with shifted degree
1990                            let degree = u32::try_from(info.degree::<N3f1>() as i32 + shift.get())
1991                                .unwrap_or_default();
1992
1993                            // Manually create the dealer with adjusted polynomial
1994                            let share = info
1995                                .unwrap_or_random_share(
1996                                    &mut rng,
1997                                    share.map(|s| s.private.expose_unwrap()),
1998                                )
1999                                .expect("Failed to generate dealer share");
2000
2001                            let my_poly = Poly::new_with_constant(&mut rng, degree, share);
2002                            let priv_msgs = info
2003                                .players
2004                                .iter()
2005                                .map(|pk| {
2006                                    (
2007                                        pk.clone(),
2008                                        DealerPrivMsg::new(my_poly.eval_msm(
2009                                            &info.player_scalar(pk).expect("player should exist"),
2010                                            &Sequential,
2011                                        )),
2012                                    )
2013                                })
2014                                .collect::<Vec<_>>();
2015                            let results: Map<_, _> = priv_msgs
2016                                .iter()
2017                                .map(|(pk, pm)| (pk.clone(), AckOrReveal::Reveal(pm.clone())))
2018                                .try_collect()
2019                                .unwrap();
2020                            let commitment = Poly::commit(my_poly);
2021                            let pub_msg = DealerPubMsg { commitment };
2022                            let transcript = {
2023                                let t = transcript_for_round(&info);
2024                                transcript_for_ack(&t, &pk, &pub_msg)
2025                            };
2026                            let dealer = Dealer {
2027                                me: sk.clone(),
2028                                info: info.clone(),
2029                                pub_msg: pub_msg.clone(),
2030                                results,
2031                                transcript,
2032                            };
2033                            (dealer, pub_msg, priv_msgs)
2034                        } else {
2035                            Dealer::start::<N3f1>(&mut rng, info.clone(), sk.clone(), share)?
2036                        };
2037
2038                    // Apply BadShare perturbations
2039                    for (player, priv_msg) in &mut priv_msgs {
2040                        let player_key_idx = pk_to_key_idx[player];
2041                        if round.bad_shares.contains(&(i_dealer, player_key_idx)) {
2042                            *priv_msg = DealerPrivMsg::new(Scalar::random(&mut rng));
2043                        }
2044                    }
2045                    assert_eq!(priv_msgs.len(), players.len());
2046
2047                    // Process player acks
2048                    let mut num_reveals = players.len() as u32;
2049                    for (player_pk, priv_msg) in priv_msgs {
2050                        // Check priv msg encoding.
2051                        assert_eq!(priv_msg, ReadExt::read(&mut priv_msg.encode())?);
2052
2053                        let i_player = players
2054                            .index(&player_pk)
2055                            .ok_or_else(|| anyhow!("unknown player: {:?}", &player_pk))?;
2056                        let player_key_idx = pk_to_key_idx[&player_pk];
2057                        let player = &mut players.values_mut()[usize::from(i_player)];
2058
2059                        let ack =
2060                            player.dealer_message::<N3f1>(pk.clone(), pub_msg.clone(), priv_msg);
2061                        assert_eq!(ack, ReadExt::read(&mut ack.encode())?);
2062                        if let Some(ack) = ack {
2063                            let masks = round
2064                                .bad_player_sigs
2065                                .get(&(i_dealer, player_key_idx))
2066                                .cloned()
2067                                .unwrap_or_default();
2068                            let (modified, transcript) =
2069                                masks.transcript_for_player_ack(&info, &pk, &pub_msg)?;
2070                            assert_eq!(transcript.verify(&player_pk, &ack.sig), !modified);
2071
2072                            // Skip receiving ack if NoAck perturbation
2073                            if !round.no_acks.contains(&(i_dealer, player_key_idx)) {
2074                                dealer.receive_player_ack(player_pk, ack)?;
2075                                num_reveals -= 1;
2076                            }
2077                        } else {
2078                            assert!(
2079                                round.bad_shares.contains(&(i_dealer, player_key_idx))
2080                                    || round.bad(previous_successful_round.is_some(), i_dealer)
2081                            );
2082                        }
2083                    }
2084
2085                    // Finalize dealer
2086                    let signed_log = dealer.finalize::<N3f1>();
2087                    assert_eq!(
2088                        signed_log,
2089                        Read::read_cfg(&mut signed_log.encode(), &max_read_size)?
2090                    );
2091
2092                    // Check for BadDealerSig
2093                    let masks = round
2094                        .bad_dealer_sigs
2095                        .get(&i_dealer)
2096                        .cloned()
2097                        .unwrap_or_default();
2098                    let (modified, transcript) =
2099                        masks.transcript_for_signed_dealer_log(&info, &signed_log.log)?;
2100                    assert_eq!(transcript.verify(&pk, &signed_log.sig), !modified);
2101                    let (found_pk, mut log) = signed_log
2102                        .check(&info)
2103                        .ok_or_else(|| anyhow!("signed log should verify"))?;
2104                    assert_eq!(pk, found_pk);
2105                    // Apply BadReveal perturbations
2106                    match &mut log.results {
2107                        DealerResult::TooManyReveals => {
2108                            assert!(num_reveals > info.max_reveals::<N3f1>());
2109                        }
2110                        DealerResult::Ok(results) => {
2111                            assert_eq!(results.len(), players.len());
2112                            for &i_player in &round.players {
2113                                if !round.bad_reveals.contains(&(i_dealer, i_player)) {
2114                                    continue;
2115                                }
2116                                let player_pk = keys[i_player as usize].public_key();
2117                                *results
2118                                    .get_value_mut(&player_pk)
2119                                    .ok_or_else(|| anyhow!("unknown player: {:?}", &player_pk))? =
2120                                    AckOrReveal::Reveal(DealerPrivMsg::new(Scalar::random(
2121                                        &mut rng,
2122                                    )));
2123                            }
2124                        }
2125                    }
2126                    dealer_logs.insert(pk, log);
2127                }
2128
2129                // Make sure that bad dealers are not selected.
2130                let selection = select::<_, _, N3f1>(&info, dealer_logs.clone());
2131                if let Ok(ref selection) = selection {
2132                    let good_pks = selection
2133                        .iter_pairs()
2134                        .map(|(pk, _)| pk.clone())
2135                        .collect::<BTreeSet<_>>();
2136                    for &i_dealer in &round.dealers {
2137                        if round.bad(previous_successful_round.is_some(), i_dealer) {
2138                            assert!(!good_pks.contains(&keys[i_dealer as usize].public_key()));
2139                        }
2140                    }
2141                }
2142                // Run observer
2143                let observe_result =
2144                    observe::<_, _, N3f1>(info.clone(), dealer_logs.clone(), &Sequential);
2145                if round.expect_failure(previous_successful_round) {
2146                    assert!(
2147                        observe_result.is_err(),
2148                        "Round {i_round} should have failed but succeeded",
2149                    );
2150                    continue;
2151                }
2152                let observer_output = observe_result?;
2153                let selection = selection.expect("select should succeed if observe succeeded");
2154
2155                // Compute expected dealers: good dealers up to required_commitments
2156                // The select function iterates dealer_logs (BTreeMap) in public key order
2157                let required_commitments = info.required_commitments::<N3f1>() as usize;
2158                let expected_dealers: Set<ed25519::PublicKey> = dealer_set
2159                    .iter()
2160                    .filter(|pk| {
2161                        let i = keys.iter().position(|k| &k.public_key() == *pk).unwrap() as u32;
2162                        !round.bad(previous_successful_round.is_some(), i)
2163                    })
2164                    .take(required_commitments)
2165                    .cloned()
2166                    .try_collect()
2167                    .expect("dealers are unique");
2168                let expected_dealer_indices: BTreeSet<u32> = expected_dealers
2169                    .iter()
2170                    .filter_map(|pk| {
2171                        keys.iter()
2172                            .position(|k| &k.public_key() == pk)
2173                            .map(|i| i as u32)
2174                    })
2175                    .collect();
2176                assert_eq!(
2177                    observer_output.dealers(),
2178                    &expected_dealers,
2179                    "Output dealers should match expected good dealers"
2180                );
2181
2182                // Map selected dealers to their key indices (for later use)
2183                let selected_dealers: BTreeSet<u32> = selection
2184                    .keys()
2185                    .iter()
2186                    .filter_map(|pk| {
2187                        keys.iter()
2188                            .position(|k| &k.public_key() == pk)
2189                            .map(|i| i as u32)
2190                    })
2191                    .collect();
2192                assert_eq!(
2193                    selected_dealers, expected_dealer_indices,
2194                    "Selection should match expected dealers"
2195                );
2196                let selected_players: Set<ed25519::PublicKey> = round
2197                    .players
2198                    .iter()
2199                    .map(|&i| keys[i as usize].public_key())
2200                    .try_collect()
2201                    .expect("players are unique");
2202
2203                // Compute expected reveals
2204                //
2205                // Note: We use union of no_acks and bad_shares since each (dealer, player) pair
2206                // results in at most one reveal in the protocol, regardless of whether the player
2207                // didn't ack, got a bad share, or both.
2208                let mut expected_reveals: BTreeMap<ed25519::PublicKey, u32> = BTreeMap::new();
2209                for &(dealer_idx, player_key_idx) in round.no_acks.union(&round.bad_shares) {
2210                    if !selected_dealers.contains(&dealer_idx) {
2211                        continue;
2212                    }
2213                    let pk = keys[player_key_idx as usize].public_key();
2214                    if selected_players.position(&pk).is_none() {
2215                        continue;
2216                    }
2217                    *expected_reveals.entry(pk).or_insert(0) += 1;
2218                }
2219
2220                // Verify each player's revealed status
2221                let max_faults = selected_players.max_faults::<N3f1>();
2222                for player in player_set.iter() {
2223                    let expected = expected_reveals.get(player).copied().unwrap_or(0) > max_faults;
2224                    let actual = observer_output.revealed().position(player).is_some();
2225                    assert_eq!(expected, actual, "Unexpected outcome for player {player:?} (expected={expected}, actual={actual})");
2226                }
2227
2228                // Finalize each player
2229                for (player_pk, player) in players.into_iter() {
2230                    let (player_output, share) = player
2231                        .finalize::<N3f1>(dealer_logs.clone(), &Sequential)
2232                        .expect("Player finalize should succeed");
2233
2234                    assert_eq!(
2235                        player_output, observer_output,
2236                        "Player output should match observer output"
2237                    );
2238
2239                    // Verify share matches public polynomial
2240                    let expected_public = observer_output
2241                        .public
2242                        .partial_public(share.index)
2243                        .expect("share index should be valid");
2244                    let actual_public = share.public::<V>();
2245                    assert_eq!(
2246                        expected_public, actual_public,
2247                        "Share should match public polynomial"
2248                    );
2249
2250                    shares.insert(player_pk.clone(), share);
2251                }
2252
2253                // Initialize or verify threshold public key
2254                let current_public = *observer_output.public().public();
2255                match threshold_public_key {
2256                    None => threshold_public_key = Some(current_public),
2257                    Some(tpk) => {
2258                        assert_eq!(
2259                            tpk, current_public,
2260                            "Public key should remain constant across reshares"
2261                        );
2262                    }
2263                }
2264
2265                // Generate and verify threshold signature
2266                let test_message = format!("test message round {i_round}").into_bytes();
2267                let namespace = b"test";
2268
2269                let mut partial_sigs = Vec::new();
2270                for &i_player in &round.players {
2271                    let share = &shares[&keys[i_player as usize].public_key()];
2272                    let partial_sig = threshold::sign_message::<V>(share, namespace, &test_message);
2273
2274                    threshold::verify_message::<V>(
2275                        &observer_output.public,
2276                        namespace,
2277                        &test_message,
2278                        &partial_sig,
2279                    )
2280                    .expect("Partial signature verification should succeed");
2281
2282                    partial_sigs.push(partial_sig);
2283                }
2284
2285                let threshold = observer_output.quorum::<N3f1>();
2286                let threshold_sig = threshold::recover::<V, _, N3f1>(
2287                    &observer_output.public,
2288                    &partial_sigs[0..threshold as usize],
2289                    &Sequential,
2290                )
2291                .expect("Should recover threshold signature");
2292
2293                // Verify against the saved public key
2294                ops::verify_message::<V>(
2295                    threshold_public_key.as_ref().unwrap(),
2296                    namespace,
2297                    &test_message,
2298                    &threshold_sig,
2299                )
2300                .expect("Threshold signature verification should succeed");
2301
2302                // Update state for next round
2303                previous_output = Some(observer_output);
2304            }
2305            Ok(())
2306        }
2307    }
2308
2309    #[cfg(feature = "arbitrary")]
2310    mod impl_arbitrary {
2311        use super::*;
2312        use arbitrary::{Arbitrary, Unstructured};
2313        use core::ops::ControlFlow;
2314
2315        const MAX_NUM_PARTICIPANTS: u32 = 20;
2316        const MAX_ROUNDS: u32 = 10;
2317
2318        fn arbitrary_masks<'a>(u: &mut Unstructured<'a>) -> arbitrary::Result<Masks> {
2319            Ok(Masks {
2320                info_summary: Arbitrary::arbitrary(u)?,
2321                dealer: Arbitrary::arbitrary(u)?,
2322                pub_msg: Arbitrary::arbitrary(u)?,
2323                log: Arbitrary::arbitrary(u)?,
2324            })
2325        }
2326
2327        /// Pick at most `num` elements at random from `data`, returning them.
2328        ///
2329        /// This needs mutable access to perform a shuffle.
2330        ///
2331        fn pick<'a, T>(
2332            u: &mut Unstructured<'a>,
2333            num: usize,
2334            mut data: Vec<T>,
2335        ) -> arbitrary::Result<Vec<T>> {
2336            let len = data.len();
2337            let num = num.min(len);
2338            // Invariant: 0..start is a random subset of data.
2339            for start in 0..num {
2340                data.swap(start, u.int_in_range(start..=len - 1)?);
2341            }
2342            data.truncate(num);
2343            Ok(data)
2344        }
2345
2346        fn arbitrary_round<'a>(
2347            u: &mut Unstructured<'a>,
2348            num_participants: u32,
2349            last_successful_players: Option<&Set<u32>>,
2350        ) -> arbitrary::Result<Round> {
2351            let dealers = if let Some(players) = last_successful_players {
2352                let to_pick = u.int_in_range(players.quorum::<N3f1>() as usize..=players.len())?;
2353                pick(u, to_pick, players.into_iter().copied().collect())?
2354            } else {
2355                let to_pick = u.int_in_range(1..=num_participants as usize)?;
2356                pick(u, to_pick, (0..num_participants).collect())?
2357            };
2358            let players = {
2359                let to_pick = u.int_in_range(1..=num_participants as usize)?;
2360                pick(u, to_pick, (0..num_participants).collect())?
2361            };
2362            let pairs = dealers
2363                .iter()
2364                .flat_map(|d| players.iter().map(|p| (*d, *p)))
2365                .collect::<Vec<_>>();
2366            let pick_pair_set = |u: &mut Unstructured<'a>| {
2367                let num = u.int_in_range(0..=pairs.len())?;
2368                if num == 0 {
2369                    return Ok(BTreeSet::new());
2370                }
2371                Ok(pick(u, num, pairs.clone())?.into_iter().collect())
2372            };
2373            let pick_dealer_set = |u: &mut Unstructured<'a>| {
2374                let num = u.int_in_range(0..=dealers.len())?;
2375                if num == 0 {
2376                    return Ok(BTreeSet::new());
2377                }
2378                Ok(pick(u, num, dealers.clone())?.into_iter().collect())
2379            };
2380            let round = Round {
2381                no_acks: pick_pair_set(u)?,
2382                bad_shares: pick_pair_set(u)?,
2383                bad_player_sigs: {
2384                    let indices = pick_pair_set(u)?;
2385                    indices
2386                        .into_iter()
2387                        .map(|k| Ok((k, arbitrary_masks(u)?)))
2388                        .collect::<arbitrary::Result<_>>()?
2389                },
2390                bad_reveals: pick_pair_set(u)?,
2391                bad_dealer_sigs: {
2392                    let indices = pick_dealer_set(u)?;
2393                    indices
2394                        .into_iter()
2395                        .map(|k| Ok((k, arbitrary_masks(u)?)))
2396                        .collect::<arbitrary::Result<_>>()?
2397                },
2398                replace_shares: pick_dealer_set(u)?,
2399                shift_degrees: {
2400                    let indices = pick_dealer_set(u)?;
2401                    indices
2402                        .into_iter()
2403                        .map(|k| {
2404                            let expected = N3f1::quorum(players.len()) as i32 - 1;
2405                            let shift = u.int_in_range(1..=expected.max(1))?;
2406                            let shift = if bool::arbitrary(u)? { -shift } else { shift };
2407                            Ok((k, NonZeroI32::new(shift).expect("checked to not be zero")))
2408                        })
2409                        .collect::<arbitrary::Result<_>>()?
2410                },
2411                dealers,
2412                players,
2413            };
2414            Ok(round)
2415        }
2416
2417        impl<'a> Arbitrary<'a> for Plan {
2418            fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
2419                let num_participants = u.int_in_range(1..=MAX_NUM_PARTICIPANTS)?;
2420                let mut rounds = Vec::new();
2421                let mut last_successful_players: Option<Set<u32>> = None;
2422                u.arbitrary_loop(None, Some(MAX_ROUNDS), |u| {
2423                    let round =
2424                        arbitrary_round(u, num_participants, last_successful_players.as_ref())?;
2425                    if !round
2426                        .expect_failure(last_successful_players.as_ref().map(|x| x.len() as u32))
2427                    {
2428                        last_successful_players =
2429                            Some(round.players.iter().copied().try_collect().unwrap());
2430                    }
2431                    rounds.push(round);
2432                    Ok(ControlFlow::Continue(()))
2433                })?;
2434                let plan = Self {
2435                    num_participants: NZU32!(num_participants),
2436                    rounds,
2437                };
2438                plan.validate()
2439                    .map_err(|_| arbitrary::Error::IncorrectFormat)?;
2440                Ok(plan)
2441            }
2442        }
2443    }
2444}
2445
2446#[cfg(feature = "arbitrary")]
2447pub use test_plan::Plan as FuzzPlan;
2448
2449#[cfg(test)]
2450mod test {
2451    use super::{test_plan::*, *};
2452    use crate::{bls12381::primitives::variant::MinPk, ed25519};
2453    use anyhow::anyhow;
2454    use commonware_math::algebra::Random;
2455    use commonware_utils::{test_rng, N3f1};
2456    use core::num::NonZeroI32;
2457
2458    #[test]
2459    fn single_round() -> anyhow::Result<()> {
2460        Plan::new(NZU32!(4))
2461            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2462            .run::<MinPk>(0)
2463    }
2464
2465    #[test]
2466    fn multiple_rounds() -> anyhow::Result<()> {
2467        Plan::new(NZU32!(4))
2468            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2469            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2470            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2471            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2472            .run::<MinPk>(0)
2473    }
2474
2475    #[test]
2476    fn changing_committee() -> anyhow::Result<()> {
2477        Plan::new(NonZeroU32::new(5).unwrap())
2478            .with(Round::new(vec![0, 1, 2], vec![1, 2, 3]))
2479            .with(Round::new(vec![1, 2, 3], vec![2, 3, 4]))
2480            .with(Round::new(vec![2, 3, 4], vec![3, 4, 0]))
2481            .with(Round::new(vec![3, 4, 0], vec![4, 0, 1]))
2482            .run::<MinPk>(0)
2483    }
2484
2485    #[test]
2486    fn missing_ack() -> anyhow::Result<()> {
2487        // With 4 players, max_faults = 1, so 1 missing ack per dealer is OK
2488        Plan::new(NonZeroU32::new(4).unwrap())
2489            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 0))
2490            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 1))
2491            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 2))
2492            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 3))
2493            .run::<MinPk>(0)
2494    }
2495
2496    #[test]
2497    fn increasing_decreasing_committee() -> anyhow::Result<()> {
2498        Plan::new(NonZeroU32::new(5).unwrap())
2499            .with(Round::new(vec![0, 1], vec![0, 1, 2]))
2500            .with(Round::new(vec![0, 1, 2], vec![0, 1, 2, 3]))
2501            .with(Round::new(vec![0, 1, 2], vec![0, 1]))
2502            .with(Round::new(vec![0, 1], vec![0, 1, 2, 3, 4]))
2503            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1]))
2504            .run::<MinPk>(0)
2505    }
2506
2507    #[test]
2508    fn bad_reveal_fails() -> anyhow::Result<()> {
2509        Plan::new(NonZeroU32::new(4).unwrap())
2510            .with(Round::new(vec![0], vec![0, 1, 2, 3]).bad_reveal(0, 1))
2511            .run::<MinPk>(0)
2512    }
2513
2514    #[test]
2515    fn bad_share() -> anyhow::Result<()> {
2516        Plan::new(NonZeroU32::new(4).unwrap())
2517            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).bad_share(0, 1))
2518            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).bad_share(0, 2))
2519            .run::<MinPk>(0)
2520    }
2521
2522    #[test]
2523    fn shift_degree_fails() -> anyhow::Result<()> {
2524        Plan::new(NonZeroU32::new(4).unwrap())
2525            .with(Round::new(vec![0], vec![0, 1, 2, 3]).shift_degree(
2526                0,
2527                NonZeroI32::new(1).ok_or_else(|| anyhow!("invalid NZI32"))?,
2528            ))
2529            .run::<MinPk>(0)
2530    }
2531
2532    #[test]
2533    fn replace_share_fails() -> anyhow::Result<()> {
2534        Plan::new(NonZeroU32::new(4).unwrap())
2535            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
2536            .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).replace_share(0))
2537            .run::<MinPk>(0)
2538    }
2539
2540    #[test]
2541    fn too_many_reveals_dealer() -> anyhow::Result<()> {
2542        Plan::new(NonZeroU32::new(4).unwrap())
2543            .with(
2544                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
2545                    .no_ack(0, 0)
2546                    .no_ack(0, 1),
2547            )
2548            .run::<MinPk>(0)
2549    }
2550
2551    #[test]
2552    fn too_many_reveals_player() -> anyhow::Result<()> {
2553        Plan::new(NonZeroU32::new(4).unwrap())
2554            .with(
2555                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
2556                    .no_ack(0, 0)
2557                    .no_ack(1, 0)
2558                    .no_ack(3, 0),
2559            )
2560            .run::<MinPk>(0)
2561    }
2562
2563    #[test]
2564    fn bad_sigs() -> anyhow::Result<()> {
2565        Plan::new(NonZeroU32::new(4).unwrap())
2566            .with(
2567                Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
2568                    .bad_dealer_sig(
2569                        0,
2570                        Masks {
2571                            log: vec![0xFF; 8],
2572                            ..Default::default()
2573                        },
2574                    )
2575                    .bad_player_sig(
2576                        0,
2577                        1,
2578                        Masks {
2579                            pub_msg: vec![0xFF; 8],
2580                            ..Default::default()
2581                        },
2582                    ),
2583            )
2584            .run::<MinPk>(0)
2585    }
2586
2587    #[test]
2588    fn issue_2745_regression() -> anyhow::Result<()> {
2589        Plan::new(NonZeroU32::new(6).unwrap())
2590            .with(
2591                Round::new(vec![0], vec![5, 1, 3, 0, 4])
2592                    .no_ack(0, 5)
2593                    .bad_share(0, 5),
2594            )
2595            .with(Round::new(vec![0, 1, 3, 4], vec![0]))
2596            .with(Round::new(vec![0], vec![0]))
2597            .run::<MinPk>(0)
2598    }
2599
2600    #[test]
2601    fn signed_dealer_log_commitment() -> Result<(), Error> {
2602        let sk = ed25519::PrivateKey::from_seed(0);
2603        let pk = sk.public_key();
2604        let info = Info::<MinPk, _>::new::<N3f1>(
2605            b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
2606            0,
2607            None,
2608            Default::default(),
2609            vec![sk.public_key()].try_into().unwrap(),
2610            vec![sk.public_key()].try_into().unwrap(),
2611        )?;
2612        let mut log0 = {
2613            let (dealer, _, _) =
2614                Dealer::start::<N3f1>(&mut test_rng(), info.clone(), sk.clone(), None)?;
2615            dealer.finalize::<N3f1>()
2616        };
2617        let mut log1 = {
2618            let (mut dealer, pub_msg, priv_msgs) =
2619                Dealer::start::<N3f1>(&mut test_rng(), info.clone(), sk.clone(), None)?;
2620            let mut player = Player::new(info.clone(), sk)?;
2621            let ack = player
2622                .dealer_message::<N3f1>(pk.clone(), pub_msg, priv_msgs[0].1.clone())
2623                .unwrap();
2624            dealer.receive_player_ack(pk, ack)?;
2625            dealer.finalize::<N3f1>()
2626        };
2627        std::mem::swap(&mut log0.log, &mut log1.log);
2628        assert!(log0.check(&info).is_none());
2629        assert!(log1.check(&info).is_none());
2630
2631        Ok(())
2632    }
2633
2634    #[test]
2635    fn test_dealer_priv_msg_redacted() {
2636        let mut rng = test_rng();
2637        let msg = DealerPrivMsg::new(Scalar::random(&mut rng));
2638        let debug = format!("{:?}", msg);
2639        assert!(debug.contains("REDACTED"));
2640    }
2641
2642    #[cfg(feature = "arbitrary")]
2643    mod conformance {
2644        use super::*;
2645        use commonware_codec::conformance::CodecConformance;
2646
2647        commonware_conformance::conformance_tests! {
2648            CodecConformance<Output<MinPk, ed25519::PublicKey>>,
2649            CodecConformance<DealerPubMsg<MinPk>>,
2650            CodecConformance<DealerPrivMsg>,
2651            CodecConformance<PlayerAck<ed25519::PublicKey>>,
2652            CodecConformance<AckOrReveal<ed25519::PublicKey>>,
2653            CodecConformance<DealerResult<ed25519::PublicKey>>,
2654            CodecConformance<DealerLog<MinPk, ed25519::PublicKey>>,
2655            CodecConformance<SignedDealerLog<MinPk, ed25519::PrivateKey>>,
2656        }
2657    }
2658}