commonware_cryptography/bls12381/dkg/
mod.rs

1//! Distributed Key Generation (DKG) and Resharing protocol for the BLS12-381 curve.
2//!
3//! This crate 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 has three types of participants: arbiters, dealers, and players. The arbiter
17//! serves as an orchestrator that collects commitments, acknowledgements, and reveals from
18//! dealers/players and replicates them to all dealers/players. The arbiter can be implemented as
19//! a standalone process or by some consensus protocol. Dealers generate commitments/shares and collect
20//! acknowledgements from players. Players receive shares from dealers, validate them, and send acknowledgements
21//! back to dealers. It is possible to be both a dealer and a player in the protocol.
22//!
23//! Whether or not the protocol succeeds, the dealers that did not post valid commitments/acks/reveals are
24//! identified and returned. If the protocol succeeds, any dealers that did not post valid commitments/acks/reveals
25//! are identified (and still returned). It is expected that the set of participants would punish/exclude
26//! "bad" dealers prior to a future round (to eventually make progress).
27//!
28//! # Specification
29//!
30//! ## Assumptions
31//!
32//! * Let `t` be the maximum amount of time it takes for a message to be sent between any two participants.
33//! * Each participant has an encrypted channel to every other participant.
34//! * There exist `3f + 1` participants and at most `f` static Byzantine faults.
35//!
36//! ## [Arbiter] Step 0: Start Round
37//!
38//! Send a message to all participants to start a round. If this is a reshare, include the group polynomial from
39//! the last successful round.
40//!
41//! ## [Dealer] Step 1: Generate Commitment and Dealings
42//!
43//! Upon receiving start message from arbiter, generate commitment and dealings. If it is a DKG, the commitment is
44//! a random polynomial of degree `2f`. If it is a reshare, the commitment must be consistent with the
45//! previous group polynomial.
46//!
47//! ## [Dealer] Step 2: Distribute Commitment and Dealings
48//!
49//! Distribute generated commitment and corresponding dealings to each player over an encrypted channel.
50//!
51//! ## [Player] Step 3: Verify Dealing and Send Acknowledgement
52//!
53//! Verify incoming dealing against provided commitment (additionally comparing the commitment to the previous group
54//! polynomial, if reshare). If the dealing is valid, send an acknowledgement back to the dealer.
55//!
56//! To protect against a dealer sending different commitments to different players, players must sign this
57//! acknowledgement over `(dealer, commitment)`.
58//!
59//! ## [Dealer] Step 4: Collect Acknowledgements and Send to Arbiter
60//!
61//! Collect acknowledgements from players. After `2t` has elapsed since Step 1 (up to `3t` from Step 0), check to
62//! see if at least `2f + 1` acknowledgements have been received (including self, if a player as well). If so, send the
63//! commitment, acknowledgements, and unencrypted dealings of players that did not send an acknowledgement to the
64//! arbiter. If not, exit.
65//!
66//! ## [Arbiter] Step 5: Select Commitments and Forward Reveals
67//!
68//! Select the first `2f + 1` commitments with at most `f` reveals. Forward these `2f + 1` commitments
69//! (and any reveals associated with each) to all players. If there do not exist `2f + 1` commitments with
70//! at most `f` reveals by time `4t`, exit.
71//!
72//! ## [Player] Step 6: Recover Group Polynomial and Derive Share
73//!
74//! If the round is successful, each player will receive `2f + 1` commitments and any dealings associated with said
75//! commitments they did not acknowledge (or that the dealer said they didn't acknowledge). With this, they can recover
76//! the new group polynomial and derive their share of the secret. If this distribution is not received by time `5t`, exit.
77//!
78//! # Caveats
79//!
80//! ## Synchrony Assumption
81//!
82//! Under synchrony (where `t` is the maximum amount of time it takes for a message to be sent between any two participants),
83//! this construction can be used to maintain a shared secret where at least `f + 1` honest players must participate to
84//! recover the shared secret (`2f + 1` threshold where at most `f` players are Byzantine). To see how this is true,
85//! first consider that in any successful round there must exist `2f + 1` commitments with at most `f` reveals. This implies
86//! that all players must have acknowledged or have access to a reveal for each of the `2f + 1` selected commitments (allowing
87//! them to derive their share). Next, consider that when the network is synchronous that all `2f + 1` honest players send
88//! acknowledgements to honest dealers before `2t`. Because `2f + 1` commitments must be chosen, at least `f + 1` commitments
89//! must be from honest dealers (where no honest player dealing is revealed). Even if the remaining `f` commitments are from
90//! Byzantine dealers, there will not be enough dealings to recover the derived share of any honest player (at most `f` of
91//! `2f + 1` dealings publicly revealed). Given all `2f + 1` honest players have access to their shares and it is not possible
92//! for a Byzantine player to derive any honest player's share, this claim holds.
93//!
94//! If the network is not synchronous, however, Byzantine players can collude to recover a shared secret with the
95//! participation of a single honest player (rather than `f + 1`) and `f + 1` honest players will each be able to derive
96//! the shared secret (if the Byzantine players reveal their shares). To see how this could be, consider a network where
97//! `f` honest participants are in one partition and (`f + 1` honest and `f` Byzantine participants) are in another. All
98//! `f` Byzantine players acknowledge dealings from the `f + 1` honest dealers. Participants in the second partition will
99//! complete a round and all the reveals will belong to the same set of `f` honest players (that are in the first partition).
100//! A colluding Byzantine adversary will then have access to their acknowledged `f` shares and the revealed `f` shares
101//! (requiring only the participation of a single honest player that was in their partition to recover the shared secret).
102//! If the Byzantine adversary reveals all of their (still private) shares at this time, each of the `f + 1` honest players
103//! that were in the second partition will be able to derive the shared secret without collusion (using their private share
104//! and the `2f` public shares). It will not be possible for any external observer, however, to recover the shared secret.
105//!
106//! ### Future Work: Dropping the Synchrony Assumption?
107//!
108//! It is possible to design a DKG/Resharing scheme that maintains a shared secret where at least `f + 1` honest players
109//! must participate to recover the shared secret that doesn't require a synchrony assumption (`2f + 1` threshold
110//! where at most `f` players are Byzantine). However, known constructions that satisfy this requirement require both
111//! broadcasting encrypted dealings publicly and employing Zero-Knowledge Proofs (ZKPs) to attest that encrypted dealings
112//! were generated correctly ([Groth21](https://eprint.iacr.org/2021/339), [Kate23](https://eprint.iacr.org/2023/451)).
113//!
114//! As of January 2025, these constructions are still considered novel (2-3 years in production), require stronger
115//! cryptographic assumptions, don't scale to hundreds of participants (unless dealers have powerful hardware), and provide
116//! observers the opportunity to brute force decrypt shares (even if honest players are online).
117//!
118//! ## Tracking Complaints
119//!
120//! This crate does not provide an integrated mechanism for tracking complaints from players (of malicious dealers). However, it is
121//! possible to implement your own mechanism and to manually disqualify dealers from a given round in the arbiter. This decision was made
122//! because the mechanism for communicating commitments/shares/acknowledgements is highly dependent on the context in which this
123//! construction is used.
124//!
125//! ## Non-Uniform Distribution
126//!
127//! The Joint-Feldman DKG protocol does not guarantee a uniformly random secret key is generated. An adversary
128//! can introduce `O(lg N)` bits of bias into the key with `O(poly(N))` amount of computation. For uses
129//! like signing, threshold encryption, where the security of the scheme reduces to that of
130//! the underlying assumption that cryptographic constructions using the curve are secure (i.e.
131//! that the Discrete Logarithm Problem, or stronger variants, are hard), then this caveat does
132//! not affect the security of the scheme. This must be taken into account when integrating this
133//! component into more esoteric schemes.
134//!
135//! This choice was explicitly made, because the best known protocols guaranteeing a uniform output
136//! require an extra round of broadcast ([GJKR02](https://www.researchgate.net/publication/2558744_Revisiting_the_Distributed_Key_Generation_for_Discrete-Log_Based_Cryptosystems),
137//! [BK25](https://eprint.iacr.org/2025/819)).
138//!
139//! ## Share Reveals
140//!
141//! In order to prevent malicious dealers from withholding shares from players, we
142//! require the dealers reveal the shares for which they did not receive acks.
143//! Because of the synchrony assumption above, this will only happen if either:
144//! - the dealer is malicious, not sending a share, but honestly revealing,
145//! - or, the player is malicious, not sending an ack when they should.
146//!
147//! Thus, for honest players, in the worst case, `f` reveals get created, because
148//! they correctly did not ack the `f` malicious dealers who failed to send them
149//! a share. In that case, their final share remains secret, because it is the linear
150//! combination of at least `f + 1` shares received from dealers.
151//!
152//! # Example
153//!
154//! For a complete example of how to instantiate this crate, check out [commonware-vrf](https://docs.rs/commonware-vrf).
155
156pub mod arbiter;
157pub use arbiter::Arbiter;
158pub mod dealer;
159pub use dealer::Dealer;
160pub mod ops;
161pub mod player;
162pub use player::Player;
163pub mod types;
164
165#[derive(thiserror::Error, Debug)]
166pub enum Error {
167    #[error("unexpected polynomial")]
168    UnexpectedPolynomial,
169    #[error("commitment has wrong degree")]
170    CommitmentWrongDegree,
171    #[error("misdirected share")]
172    MisdirectedShare,
173    #[error("share does not on commitment")]
174    ShareWrongCommitment,
175    #[error("insufficient dealings")]
176    InsufficientDealings,
177    #[error("reshare mismatch")]
178    ReshareMismatch,
179    #[error("share interpolation failed")]
180    ShareInterpolationFailed,
181    #[error("public key interpolation failed")]
182    PublicKeyInterpolationFailed,
183    #[error("dealer is invalid")]
184    DealerInvalid,
185    #[error("player invalid")]
186    PlayerInvalid,
187    #[error("missing share")]
188    MissingShare,
189    #[error("missing commitment")]
190    MissingCommitment,
191    #[error("too many commitments")]
192    TooManyCommitments,
193    #[error("duplicate commitment")]
194    DuplicateCommitment,
195    #[error("duplicate share")]
196    DuplicateShare,
197    #[error("duplicate ack")]
198    DuplicateAck,
199    #[error("mismatched commitment")]
200    MismatchedCommitment,
201    #[error("mismatched share")]
202    MismatchedShare,
203    #[error("too many reveals")]
204    TooManyReveals,
205    #[error("incorrect active")]
206    IncorrectActive,
207    #[error("already active")]
208    AlreadyActive,
209    #[error("invalid commitments")]
210    InvalidCommitments,
211    #[error("dealer disqualified")]
212    DealerDisqualified,
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218    use crate::{
219        bls12381::primitives::{
220            ops::{
221                partial_sign_proof_of_possession, threshold_signature_recover,
222                verify_proof_of_possession,
223            },
224            poly::{self, public},
225            variant::{MinPk, MinSig, Variant},
226        },
227        ed25519::{PrivateKey, PublicKey},
228        PrivateKeyExt as _, Signer as _,
229    };
230    use commonware_utils::{quorum, set::Ordered};
231    use rand::{rngs::StdRng, SeedableRng};
232    use std::collections::{BTreeMap, HashMap};
233
234    #[test]
235    fn test_invalid_commitment() {
236        // Initialize test
237        let n = 5;
238        let mut rng = StdRng::seed_from_u64(0);
239
240        // Create contributors (must be in sorted order)
241        let contributors = (0..n)
242            .map(|i| PrivateKey::from_seed(i as u64).public_key())
243            .collect::<Ordered<_>>();
244
245        // Create dealer
246        let (_, _, shares) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
247
248        // Create unrelated commitment of correct degree
249        let t = quorum(n);
250        let (public, _) = ops::generate_shares::<_, MinSig>(&mut rng, None, n, t);
251
252        // Create player
253        let mut player = Player::<_, MinSig>::new(
254            contributors[0].clone(),
255            None,
256            contributors.clone(),
257            contributors.clone(),
258            1,
259        );
260
261        // Send invalid commitment to player
262        let result = player.share(contributors[0].clone(), public, shares[0].clone());
263        assert!(matches!(result, Err(Error::ShareWrongCommitment)));
264    }
265
266    #[test]
267    fn test_mismatched_commitment() {
268        // Initialize test
269        let n = 5;
270        let mut rng = StdRng::seed_from_u64(0);
271
272        // Create contributors (must be in sorted order)
273        let contributors = (0..n)
274            .map(|i| PrivateKey::from_seed(i as u64).public_key())
275            .collect::<Ordered<_>>();
276
277        // Create dealer
278        let (_, commitment, shares) =
279            Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
280
281        // Create unrelated commitment of correct degree
282        let t = quorum(n);
283        let (other_commitment, _) = ops::generate_shares::<_, MinSig>(&mut rng, None, n, t);
284
285        // Create player
286        let mut player = Player::<_, MinSig>::new(
287            contributors[0].clone(),
288            None,
289            contributors.clone(),
290            contributors.clone(),
291            1,
292        );
293
294        // Send valid commitment to player
295        player
296            .share(contributors[0].clone(), commitment, shares[0].clone())
297            .unwrap();
298
299        // Send alternative commitment to player
300        let result = player.share(contributors[0].clone(), other_commitment, shares[0].clone());
301        assert!(matches!(result, Err(Error::MismatchedCommitment)));
302    }
303
304    #[test]
305    fn test_mismatched_share() {
306        // Initialize test
307        let n = 5;
308        let mut rng = StdRng::seed_from_u64(0);
309
310        // Create contributors (must be in sorted order)
311        let contributors = (0..n)
312            .map(|i| PrivateKey::from_seed(i as u64).public_key())
313            .collect::<Ordered<_>>();
314
315        // Create dealer
316        let (_, commitment, shares) =
317            Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
318
319        // Create unrelated commitment of correct degree
320        let t = quorum(n);
321        let (_, other_shares) = ops::generate_shares::<_, MinSig>(&mut rng, None, n, t);
322
323        // Create player
324        let mut player = Player::<_, MinSig>::new(
325            contributors[0].clone(),
326            None,
327            contributors.clone(),
328            contributors.clone(),
329            1,
330        );
331
332        // Send valid share to player
333        player
334            .share(
335                contributors[0].clone(),
336                commitment.clone(),
337                shares[0].clone(),
338            )
339            .unwrap();
340
341        // Send alternative share to player
342        let result = player.share(contributors[0].clone(), commitment, other_shares[0].clone());
343        assert!(matches!(result, Err(Error::MismatchedShare)));
344    }
345
346    #[test]
347    fn test_duplicate_share() {
348        // Initialize test
349        let n = 5;
350        let mut rng = StdRng::seed_from_u64(0);
351
352        // Create contributors (must be in sorted order)
353        let contributors = (0..n)
354            .map(|i| PrivateKey::from_seed(i as u64).public_key())
355            .collect::<Ordered<_>>();
356
357        // Create dealer
358        let (_, commitment, shares) =
359            Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
360
361        // Create player
362        let mut player = Player::<_, MinSig>::new(
363            contributors[0].clone(),
364            None,
365            contributors.clone(),
366            contributors.clone(),
367            1,
368        );
369
370        // Send valid share to player
371        player
372            .share(
373                contributors[0].clone(),
374                commitment.clone(),
375                shares[0].clone(),
376            )
377            .unwrap();
378
379        // Send alternative share to player
380        let result = player.share(contributors[0].clone(), commitment, shares[0].clone());
381        assert!(matches!(result, Err(Error::DuplicateShare)));
382    }
383
384    #[test]
385    fn test_misdirected_share() {
386        // Initialize test
387        let n = 5;
388        let mut rng = StdRng::seed_from_u64(0);
389
390        // Create contributors (must be in sorted order)
391        let contributors = (0..n)
392            .map(|i| PrivateKey::from_seed(i as u64).public_key())
393            .collect::<Ordered<_>>();
394
395        // Create dealer
396        let (_, commitment, shares) =
397            Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
398
399        // Create player
400        let mut player = Player::<_, MinSig>::new(
401            contributors[0].clone(),
402            None,
403            contributors.clone(),
404            contributors.clone(),
405            1,
406        );
407
408        // Send misdirected share to player
409        let result = player.share(
410            contributors[0].clone(),
411            commitment.clone(),
412            shares[1].clone(),
413        );
414        assert!(matches!(result, Err(Error::MisdirectedShare)));
415    }
416
417    #[test]
418    fn test_invalid_dealer() {
419        // Initialize test
420        let n = 5;
421        let mut rng = StdRng::seed_from_u64(0);
422
423        // Create contributors (must be in sorted order)
424        let contributors = (0..n)
425            .map(|i| PrivateKey::from_seed(i as u64).public_key())
426            .collect::<Ordered<_>>();
427
428        // Create dealer
429        let (_, commitment, shares) =
430            Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
431
432        // Create player
433        let mut player = Player::<_, MinSig>::new(
434            contributors[0].clone(),
435            None,
436            contributors.clone(),
437            contributors.clone(),
438            1,
439        );
440
441        // Send share from invalid dealer
442        let dealer = PrivateKey::from_seed(n as u64).public_key();
443        let result = player.share(dealer.clone(), commitment.clone(), shares[0].clone());
444        assert!(matches!(result, Err(Error::DealerInvalid)));
445
446        // Create arbiter
447        let mut arb =
448            Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
449
450        // Send commitment from invalid dealer
451        let result = arb.commitment(dealer, commitment, vec![0, 1, 2, 3], Vec::new());
452        assert!(matches!(result, Err(Error::DealerInvalid)));
453    }
454
455    #[test]
456    fn test_invalid_commitment_degree() {
457        // Initialize test
458        let n = 5;
459        let t = quorum(n);
460        let mut rng = StdRng::seed_from_u64(0);
461
462        // Create contributors (must be in sorted order)
463        let contributors = (0..n)
464            .map(|i| PrivateKey::from_seed(i as u64).public_key())
465            .collect::<Ordered<_>>();
466
467        // Create invalid commitments
468        let mut commitments = Vec::new();
469        let (public, shares) = ops::generate_shares::<_, MinSig>(&mut rng, None, n, 1);
470        commitments.push((public, shares));
471        let (public, shares) = ops::generate_shares::<_, MinSig>(&mut rng, None, n, t - 1);
472        commitments.push((public, shares));
473        let (public, shares) = ops::generate_shares::<_, MinSig>(&mut rng, None, n, t + 1);
474        commitments.push((public, shares));
475
476        // Check invalid commitments
477        for (public, shares) in commitments {
478            // Send invalid commitment to player
479            let mut player = Player::<_, MinSig>::new(
480                contributors[0].clone(),
481                None,
482                contributors.clone(),
483                contributors.clone(),
484                1,
485            );
486            let result = player.share(contributors[0].clone(), public.clone(), shares[0].clone());
487            assert!(matches!(result, Err(Error::CommitmentWrongDegree)));
488
489            // Create arbiter
490            let mut arb =
491                Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
492            let result = arb.commitment(
493                contributors[0].clone(),
494                public,
495                vec![0, 1, 2, 3, 4],
496                Vec::new(),
497            );
498            assert!(matches!(result, Err(Error::CommitmentWrongDegree)));
499        }
500    }
501
502    #[test]
503    fn test_reveal() {
504        // Initialize test
505        let n = 5;
506        let mut rng = StdRng::seed_from_u64(0);
507
508        // Create contributors (must be in sorted order)
509        let contributors = (0..n)
510            .map(|i| PrivateKey::from_seed(i as u64).public_key())
511            .collect::<Ordered<_>>();
512
513        // Create dealer
514        let (_, commitment, shares) =
515            Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
516
517        // Create arbiter
518        let mut arb =
519            Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
520
521        // Add commitment to arbiter
522        arb.commitment(
523            contributors[0].clone(),
524            commitment,
525            vec![0, 1, 2, 3],
526            vec![shares[4].clone()],
527        )
528        .unwrap();
529    }
530
531    #[test]
532    fn test_arbiter_reveals() {
533        // Initialize test
534        let n = 11;
535        let q = quorum(n as u32) as usize;
536        let mut rng = StdRng::seed_from_u64(0);
537
538        // Create contributors (must be in sorted order)
539        let contributors = (0..n)
540            .map(|i| PrivateKey::from_seed(i as u64).public_key())
541            .collect::<Ordered<_>>();
542
543        // Create arbiter
544        let mut arb =
545            Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
546
547        // Create dealers
548        let mut commitments = Vec::with_capacity(n);
549        let mut reveals = Vec::with_capacity(n);
550        for con in &contributors {
551            // Create dealer
552            let (_, commitment, shares) =
553                Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
554            commitments.push(commitment.clone());
555            reveals.push(shares[q].clone());
556
557            // Add commitment to arbiter
558            let acks: Vec<u32> = (0..q as u32).collect();
559            let reveals = shares[q..n].to_vec();
560            arb.commitment(con.clone(), commitment, acks, reveals)
561                .unwrap();
562        }
563
564        // Finalize arbiter
565        let (result, _) = arb.finalize();
566        let output = result.unwrap();
567
568        // Ensure commitments and reveals are correct
569        assert_eq!(output.commitments.len(), q);
570        for (dealer_idx, commitment) in commitments.iter().enumerate().take(q) {
571            let dealer_idx = dealer_idx as u32;
572            assert_eq!(output.commitments.get(&dealer_idx).unwrap(), commitment);
573            assert_eq!(
574                output.reveals.get(&dealer_idx).unwrap()[0],
575                reveals[dealer_idx as usize]
576            );
577        }
578    }
579
580    #[test]
581    fn test_duplicate_commitment() {
582        // Initialize test
583        let n = 5;
584        let mut rng = StdRng::seed_from_u64(0);
585
586        // Create contributors (must be in sorted order)
587        let contributors = (0..n)
588            .map(|i| PrivateKey::from_seed(i as u64).public_key())
589            .collect::<Ordered<_>>();
590
591        // Create dealer
592        let (_, commitment, shares) =
593            Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
594
595        // Create arbiter
596        let mut arb =
597            Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
598
599        // Add commitment to arbiter
600        arb.commitment(
601            contributors[0].clone(),
602            commitment.clone(),
603            vec![0, 1, 2, 3],
604            vec![shares[4].clone()],
605        )
606        .unwrap();
607
608        // Add commitment to arbiter (again)
609        let result = arb.commitment(
610            contributors[0].clone(),
611            commitment,
612            vec![0, 1, 2, 3],
613            vec![shares[4].clone()],
614        );
615        assert!(matches!(result, Err(Error::DuplicateCommitment)));
616    }
617
618    #[test]
619    fn test_reveal_duplicate_player() {
620        // Initialize test
621        let n = 5;
622        let mut rng = StdRng::seed_from_u64(0);
623
624        // Create contributors (must be in sorted order)
625        let contributors = (0..n)
626            .map(|i| PrivateKey::from_seed(i as u64).public_key())
627            .collect::<Ordered<_>>();
628
629        // Create dealer
630        let (_, commitment, shares) =
631            Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
632
633        // Create arbiter
634        let mut arb =
635            Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
636
637        // Add commitment to arbiter
638        let result = arb.commitment(
639            contributors[0].clone(),
640            commitment,
641            vec![0, 1, 2, 3],
642            vec![shares[3].clone()],
643        );
644        assert!(matches!(result, Err(Error::AlreadyActive)));
645    }
646
647    #[test]
648    fn test_insufficient_active() {
649        // Initialize test
650        let n = 5;
651        let mut rng = StdRng::seed_from_u64(0);
652
653        // Create contributors (must be in sorted order)
654        let contributors = (0..n)
655            .map(|i| PrivateKey::from_seed(i as u64).public_key())
656            .collect::<Ordered<_>>();
657
658        // Create dealer
659        let (_, commitment, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
660
661        // Create arbiter
662        let mut arb =
663            Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
664
665        // Add commitment to arbiter
666        let result = arb.commitment(
667            contributors[0].clone(),
668            commitment.clone(),
669            vec![0, 1, 2, 3],
670            Vec::new(),
671        );
672        assert!(matches!(result, Err(Error::IncorrectActive)));
673
674        // Add valid commitment to arbiter after disqualified
675        let result = arb.commitment(
676            contributors[0].clone(),
677            commitment,
678            vec![0, 1, 2, 3, 4],
679            Vec::new(),
680        );
681        assert!(matches!(result, Err(Error::DealerDisqualified)));
682    }
683
684    #[test]
685    fn test_manual_disqualify() {
686        // Initialize test
687        let n = 5;
688        let mut rng = StdRng::seed_from_u64(0);
689
690        // Create contributors (must be in sorted order)
691        let contributors = (0..n)
692            .map(|i| PrivateKey::from_seed(i as u64).public_key())
693            .collect::<Ordered<_>>();
694
695        // Create dealer
696        let (_, commitment, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
697
698        // Create arbiter
699        let mut arb =
700            Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
701
702        // Disqualify dealer
703        arb.disqualify(contributors[0].clone());
704
705        // Add valid commitment to arbiter after disqualified
706        let result = arb.commitment(
707            contributors[0].clone(),
708            commitment,
709            vec![0, 1, 2, 3, 4],
710            Vec::new(),
711        );
712        assert!(matches!(result, Err(Error::DealerDisqualified)));
713    }
714
715    #[test]
716    fn test_too_many_reveals() {
717        // Initialize test
718        let n = 5;
719        let mut rng = StdRng::seed_from_u64(0);
720
721        // Create contributors (must be in sorted order)
722        let contributors = (0..n)
723            .map(|i| PrivateKey::from_seed(i as u64).public_key())
724            .collect::<Ordered<_>>();
725
726        // Create dealer
727        let (_, commitment, shares) =
728            Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
729
730        // Create arbiter
731        let mut arb =
732            Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
733
734        // Add commitment to arbiter
735        let result = arb.commitment(
736            contributors[0].clone(),
737            commitment,
738            vec![0, 1, 2],
739            vec![shares[3].clone(), shares[4].clone()],
740        );
741        assert!(matches!(result, Err(Error::TooManyReveals)));
742    }
743
744    #[test]
745    fn test_incorrect_reveal() {
746        // Initialize test
747        let n = 5;
748        let mut rng = StdRng::seed_from_u64(0);
749
750        // Create contributors (must be in sorted order)
751        let contributors = (0..n)
752            .map(|i| PrivateKey::from_seed(i as u64).public_key())
753            .collect::<Ordered<_>>();
754
755        // Create dealer
756        let (_, commitment, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
757
758        // Create invalid shares
759        let t = quorum(n);
760        let (_, shares) = ops::generate_shares::<_, MinSig>(&mut rng, None, n, t);
761
762        // Create arbiter
763        let mut arb =
764            Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
765
766        // Add commitment to arbiter
767        let result = arb.commitment(
768            contributors[0].clone(),
769            commitment,
770            vec![0, 1, 2, 3],
771            vec![shares[4].clone()],
772        );
773        assert!(matches!(result, Err(Error::ShareWrongCommitment)));
774    }
775
776    #[test]
777    fn test_reveal_corrupt_share() {
778        // Initialize test
779        let n = 5;
780        let mut rng = StdRng::seed_from_u64(0);
781
782        // Create contributors (must be in sorted order)
783        let contributors = (0..n)
784            .map(|i| PrivateKey::from_seed(i as u64).public_key())
785            .collect::<Ordered<_>>();
786
787        // Create dealer
788        let (_, commitment, shares) =
789            Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
790
791        // Create arbiter
792        let mut arb =
793            Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
794
795        // Swap share value
796        let mut share = shares[3].clone();
797        share.index = 4;
798
799        // Add commitment to arbiter
800        let result = arb.commitment(
801            contributors[0].clone(),
802            commitment,
803            vec![0, 1, 2, 3],
804            vec![share],
805        );
806        assert!(matches!(result, Err(Error::ShareWrongCommitment)));
807    }
808
809    #[test]
810    fn test_reveal_duplicate_ack() {
811        // Initialize test
812        let n = 5;
813        let mut rng = StdRng::seed_from_u64(0);
814
815        // Create contributors (must be in sorted order)
816        let contributors = (0..n)
817            .map(|i| PrivateKey::from_seed(i as u64).public_key())
818            .collect::<Ordered<_>>();
819
820        // Create dealer
821        let (_, commitment, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
822
823        // Create arbiter
824        let mut arb =
825            Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
826
827        // Add commitment to arbiter
828        let result = arb.commitment(
829            contributors[0].clone(),
830            commitment,
831            vec![0, 1, 2, 2],
832            Vec::new(),
833        );
834        assert!(matches!(result, Err(Error::AlreadyActive)));
835    }
836
837    #[test]
838    fn test_reveal_invalid_ack() {
839        // Initialize test
840        let n = 5;
841        let mut rng = StdRng::seed_from_u64(0);
842
843        // Create contributors (must be in sorted order)
844        let contributors = (0..n)
845            .map(|i| PrivateKey::from_seed(i as u64).public_key())
846            .collect::<Ordered<_>>();
847
848        // Create dealer
849        let (_, commitment, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
850
851        // Create arbiter
852        let mut arb =
853            Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
854
855        // Add commitment to arbiter
856        let result = arb.commitment(
857            contributors[0].clone(),
858            commitment,
859            vec![0, 1, 2, 10],
860            Vec::new(),
861        );
862        assert!(matches!(result, Err(Error::PlayerInvalid)));
863    }
864
865    #[test]
866    fn test_reveal_invalid_share() {
867        // Initialize test
868        let n = 5;
869        let mut rng = StdRng::seed_from_u64(0);
870
871        // Create contributors (must be in sorted order)
872        let contributors = (0..n)
873            .map(|i| PrivateKey::from_seed(i as u64).public_key())
874            .collect::<Ordered<_>>();
875
876        // Create dealer
877        let (_, commitment, shares) =
878            Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
879
880        // Create arbiter
881        let mut arb =
882            Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
883
884        // Swap share value
885        let mut share = shares[3].clone();
886        share.index = 10;
887
888        // Add commitment to arbiter
889        let result = arb.commitment(
890            contributors[0].clone(),
891            commitment,
892            vec![0, 1, 2, 3],
893            vec![share],
894        );
895        assert!(matches!(result, Err(Error::PlayerInvalid)));
896    }
897
898    #[test]
899    fn test_dealer_acks() {
900        // Initialize test
901        let n = 5;
902        let mut rng = StdRng::seed_from_u64(0);
903
904        // Create contributors (must be in sorted order)
905        let contributors = (0..n)
906            .map(|i| PrivateKey::from_seed(i as u64).public_key())
907            .collect::<Ordered<_>>();
908
909        // Create dealer
910        let (mut dealer, _, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
911
912        // Ack all players
913        for player in &contributors {
914            dealer.ack(player.clone()).unwrap();
915        }
916
917        // Finalize dealer
918        let output = dealer.finalize().unwrap();
919        assert_eq!(Vec::from(output.active), vec![0, 1, 2, 3, 4]);
920        assert!(output.inactive.is_empty());
921    }
922
923    #[test]
924    fn test_dealer_inactive() {
925        // Initialize test
926        let n = 5;
927        let mut rng = StdRng::seed_from_u64(0);
928
929        // Create contributors (must be in sorted order)
930        let contributors = (0..n)
931            .map(|i| PrivateKey::from_seed(i as u64).public_key())
932            .collect::<Ordered<_>>();
933
934        // Create dealer
935        let (mut dealer, _, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
936
937        // Ack all players
938        for player in contributors.iter().take(4) {
939            dealer.ack(player.clone()).unwrap();
940        }
941
942        // Finalize dealer
943        let output = dealer.finalize().unwrap();
944        assert_eq!(Vec::from(output.active), vec![0, 1, 2, 3]);
945        assert_eq!(Vec::from(output.inactive), vec![4]);
946    }
947
948    #[test]
949    fn test_dealer_insufficient() {
950        // Initialize test
951        let n = 5;
952        let mut rng = StdRng::seed_from_u64(0);
953
954        // Create contributors (must be in sorted order)
955        let contributors = (0..n)
956            .map(|i| PrivateKey::from_seed(i as u64).public_key())
957            .collect::<Ordered<_>>();
958
959        // Create dealer
960        let (mut dealer, _, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
961
962        // Ack all players
963        for player in contributors.iter().take(2) {
964            dealer.ack(player.clone()).unwrap();
965        }
966
967        // Finalize dealer
968        assert!(dealer.finalize().is_none());
969    }
970
971    #[test]
972    fn test_dealer_duplicate_ack() {
973        // Initialize test
974        let n = 5;
975        let mut rng = StdRng::seed_from_u64(0);
976
977        // Create contributors (must be in sorted order)
978        let contributors = (0..n)
979            .map(|i| PrivateKey::from_seed(i as u64).public_key())
980            .collect::<Ordered<_>>();
981
982        // Create dealer
983        let (mut dealer, _, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
984
985        // Ack player
986        let player = contributors[0].clone();
987        dealer.ack(player.clone()).unwrap();
988
989        // Ack player (again)
990        let result = dealer.ack(player);
991        assert!(matches!(result, Err(Error::DuplicateAck)));
992    }
993
994    #[test]
995    fn test_dealer_invalid_player() {
996        // Initialize test
997        let n = 5;
998        let mut rng = StdRng::seed_from_u64(0);
999
1000        // Create contributors (must be in sorted order)
1001        let contributors = (0..n)
1002            .map(|i| PrivateKey::from_seed(i as u64).public_key())
1003            .collect::<Ordered<_>>();
1004
1005        // Create dealer
1006        let (mut dealer, _, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
1007
1008        // Ack invalid player
1009        let player = PrivateKey::from_seed(n as u64).public_key();
1010        let result = dealer.ack(player);
1011        assert!(matches!(result, Err(Error::PlayerInvalid)));
1012    }
1013
1014    #[test]
1015    fn test_player_reveals() {
1016        // Initialize test
1017        let n = 11;
1018        let q = quorum(n as u32) as usize;
1019        let mut rng = StdRng::seed_from_u64(0);
1020
1021        // Create contributors (must be in sorted order)
1022        let contributors = (0..n)
1023            .map(|i| PrivateKey::from_seed(i as u64).public_key())
1024            .collect::<Ordered<_>>();
1025
1026        // Create player
1027        let mut player = Player::<_, MinSig>::new(
1028            contributors[0].clone(),
1029            None,
1030            contributors.clone(),
1031            contributors.clone(),
1032            1,
1033        );
1034
1035        // Send shares to player
1036        let mut commitments = BTreeMap::new();
1037        for (i, con) in contributors.iter().enumerate().take(q - 1) {
1038            let (_, commitment, shares) =
1039                Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
1040            player
1041                .share(con.clone(), commitment.clone(), shares[0].clone())
1042                .unwrap();
1043            commitments.insert(i as u32, commitment);
1044        }
1045
1046        // Finalize player with reveal
1047        let last = (q - 1) as u32;
1048        let (_, commitment, shares) =
1049            Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
1050        commitments.insert(last, commitment);
1051        let mut reveals = BTreeMap::new();
1052        reveals.insert(last, shares[0].clone());
1053        player.finalize(commitments, reveals).unwrap();
1054    }
1055
1056    #[test]
1057    fn test_player_missing_reveal() {
1058        // Initialize test
1059        let n = 11;
1060        let q = quorum(n as u32) as usize;
1061        let mut rng = StdRng::seed_from_u64(0);
1062
1063        // Create contributors (must be in sorted order)
1064        let contributors = (0..n)
1065            .map(|i| PrivateKey::from_seed(i as u64).public_key())
1066            .collect::<Ordered<_>>();
1067
1068        // Create player
1069        let mut player = Player::<_, MinSig>::new(
1070            contributors[0].clone(),
1071            None,
1072            contributors.clone(),
1073            contributors.clone(),
1074            1,
1075        );
1076
1077        // Send shares to player
1078        let mut commitments = BTreeMap::new();
1079        for (i, con) in contributors.iter().enumerate().take(q - 1) {
1080            let (_, commitment, shares) =
1081                Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
1082            player
1083                .share(con.clone(), commitment.clone(), shares[0].clone())
1084                .unwrap();
1085            commitments.insert(i as u32, commitment);
1086        }
1087
1088        // Finalize player with reveal
1089        let last = (q - 1) as u32;
1090        let (_, commitment, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
1091        commitments.insert(last, commitment);
1092        let result = player.finalize(commitments, BTreeMap::new());
1093        assert!(matches!(result, Err(Error::MissingShare)));
1094    }
1095
1096    #[test]
1097    fn test_player_insufficient_commitments() {
1098        // Initialize test
1099        let n = 5;
1100        let mut rng = StdRng::seed_from_u64(0);
1101
1102        // Create contributors (must be in sorted order)
1103        let contributors = (0..n)
1104            .map(|i| PrivateKey::from_seed(i as u64).public_key())
1105            .collect::<Ordered<_>>();
1106
1107        // Create player
1108        let mut player = Player::<_, MinSig>::new(
1109            contributors[0].clone(),
1110            None,
1111            contributors.clone(),
1112            contributors.clone(),
1113            1,
1114        );
1115
1116        // Send shares to player
1117        let mut commitments = BTreeMap::new();
1118        for (i, con) in contributors.iter().enumerate().take(2) {
1119            let (_, commitment, shares) =
1120                Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
1121            player
1122                .share(con.clone(), commitment.clone(), shares[0].clone())
1123                .unwrap();
1124            commitments.insert(i as u32, commitment);
1125        }
1126
1127        // Finalize player with reveal
1128        let result = player.finalize(commitments, BTreeMap::new());
1129        assert!(matches!(result, Err(Error::InvalidCommitments)));
1130    }
1131
1132    #[test]
1133    fn test_player_misdirected_reveal() {
1134        // Initialize test
1135        let n = 11;
1136        let q = quorum(n as u32) as usize;
1137        let mut rng = StdRng::seed_from_u64(0);
1138
1139        // Create contributors (must be in sorted order)
1140        let contributors = (0..n)
1141            .map(|i| PrivateKey::from_seed(i as u64).public_key())
1142            .collect::<Ordered<_>>();
1143
1144        // Create player
1145        let mut player = Player::<_, MinSig>::new(
1146            contributors[0].clone(),
1147            None,
1148            contributors.clone(),
1149            contributors.clone(),
1150            1,
1151        );
1152
1153        // Send shares to player
1154        let mut commitments = BTreeMap::new();
1155        for (i, con) in contributors.iter().enumerate().take(q - 1) {
1156            let (_, commitment, shares) =
1157                Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
1158            player
1159                .share(con.clone(), commitment.clone(), shares[0].clone())
1160                .unwrap();
1161            commitments.insert(i as u32, commitment);
1162        }
1163
1164        // Finalize player with reveal
1165        let last = (q - 1) as u32;
1166        let (_, commitment, shares) =
1167            Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
1168        commitments.insert(last, commitment);
1169        let mut reveals = BTreeMap::new();
1170        reveals.insert(last, shares[1].clone());
1171        let result = player.finalize(commitments, reveals);
1172        assert!(matches!(result, Err(Error::MisdirectedShare)));
1173    }
1174
1175    #[test]
1176    fn test_player_invalid_commitment() {
1177        // Initialize test
1178        let n = 11;
1179        let q = quorum(n as u32) as usize;
1180        let mut rng = StdRng::seed_from_u64(0);
1181
1182        // Create contributors (must be in sorted order)
1183        let contributors = (0..n)
1184            .map(|i| PrivateKey::from_seed(i as u64).public_key())
1185            .collect::<Ordered<_>>();
1186
1187        // Create player
1188        let mut player = Player::<_, MinSig>::new(
1189            contributors[0].clone(),
1190            None,
1191            contributors.clone(),
1192            contributors.clone(),
1193            1,
1194        );
1195
1196        // Send shares to player
1197        let mut commitments = BTreeMap::new();
1198        for (i, con) in contributors.iter().enumerate().take(q - 1) {
1199            let (_, commitment, shares) =
1200                Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
1201            player
1202                .share(con.clone(), commitment.clone(), shares[0].clone())
1203                .unwrap();
1204            commitments.insert(i as u32, commitment);
1205        }
1206
1207        // Finalize player with reveal
1208        let last = (q - 1) as u32;
1209        let (commitment, shares) = ops::generate_shares::<_, MinSig>(&mut rng, None, n as u32, 1);
1210        commitments.insert(last, commitment);
1211        let mut reveals = BTreeMap::new();
1212        reveals.insert(last, shares[0].clone());
1213        let result = player.finalize(commitments, reveals);
1214        assert!(matches!(result, Err(Error::CommitmentWrongDegree)));
1215    }
1216
1217    #[test]
1218    fn test_player_invalid_reveal() {
1219        // Initialize test
1220        let n = 11;
1221        let q = quorum(n as u32) as usize;
1222        let mut rng = StdRng::seed_from_u64(0);
1223
1224        // Create contributors (must be in sorted order)
1225        let contributors = (0..n)
1226            .map(|i| PrivateKey::from_seed(i as u64).public_key())
1227            .collect::<Ordered<_>>();
1228
1229        // Create player
1230        let mut player = Player::<_, MinSig>::new(
1231            contributors[0].clone(),
1232            None,
1233            contributors.clone(),
1234            contributors.clone(),
1235            1,
1236        );
1237
1238        // Send shares to player
1239        let mut commitments = BTreeMap::new();
1240        for (i, con) in contributors.iter().enumerate().take(q - 1) {
1241            let (_, commitment, shares) =
1242                Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
1243            player
1244                .share(con.clone(), commitment.clone(), shares[0].clone())
1245                .unwrap();
1246            commitments.insert(i as u32, commitment);
1247        }
1248
1249        // Finalize player with reveal
1250        let last = (q - 1) as u32;
1251        let (_, commitment, shares) =
1252            Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
1253        commitments.insert(last, commitment);
1254        let mut reveals = BTreeMap::new();
1255        let mut share = shares[1].clone();
1256        share.index = 0;
1257        reveals.insert(last, share);
1258        let result = player.finalize(commitments, reveals);
1259        assert!(matches!(result, Err(Error::ShareWrongCommitment)));
1260    }
1261
1262    #[test]
1263    fn test_player_dealer_equivocation() {
1264        // Initialize test
1265        let n = 11;
1266        let q = quorum(n as u32) as usize;
1267        let mut rng = StdRng::seed_from_u64(0);
1268
1269        // Create contributors (must be in sorted order)
1270        let contributors = (0..n)
1271            .map(|i| PrivateKey::from_seed(i as u64).public_key())
1272            .collect::<Ordered<_>>();
1273
1274        // Create player
1275        let mut player = Player::<_, MinSig>::new(
1276            contributors[0].clone(),
1277            None,
1278            contributors.clone(),
1279            contributors.clone(),
1280            1,
1281        );
1282
1283        // Send shares to player
1284        let mut commitments = BTreeMap::new();
1285        for (i, con) in contributors.iter().enumerate().take(q - 1) {
1286            let (_, commitment, shares) =
1287                Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
1288            player
1289                .share(con.clone(), commitment.clone(), shares[0].clone())
1290                .unwrap();
1291            commitments.insert(i as u32, commitment);
1292        }
1293
1294        // Finalize player with equivocating reveal
1295        let last = (q - 1) as u32;
1296        let (_, commitment, shares) =
1297            Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
1298        commitments.insert(last, commitment);
1299
1300        // Add commitments
1301        let mut public = poly::Public::<MinSig>::zero();
1302        for commitment in commitments.values() {
1303            public.add(commitment);
1304        }
1305
1306        // Finalize player with equivocating reveal
1307        let mut reveals = BTreeMap::new();
1308        reveals.insert(last, shares[0].clone());
1309        let result = player.finalize(commitments, reveals).unwrap();
1310        assert_eq!(result.public, public);
1311    }
1312
1313    #[test]
1314    fn test_player_dealer_equivocation_missing_reveal() {
1315        // Initialize test
1316        let n = 11;
1317        let q = quorum(n as u32) as usize;
1318        let mut rng = StdRng::seed_from_u64(0);
1319
1320        // Create contributors (must be in sorted order)
1321        let contributors = (0..n)
1322            .map(|i| PrivateKey::from_seed(i as u64).public_key())
1323            .collect::<Ordered<_>>();
1324
1325        // Create player
1326        let mut player = Player::<_, MinSig>::new(
1327            contributors[0].clone(),
1328            None,
1329            contributors.clone(),
1330            contributors.clone(),
1331            1,
1332        );
1333
1334        // Send shares to player
1335        let mut commitments = BTreeMap::new();
1336        for (i, con) in contributors.iter().enumerate().take(q - 1) {
1337            let (_, commitment, shares) =
1338                Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
1339            player
1340                .share(con.clone(), commitment.clone(), shares[0].clone())
1341                .unwrap();
1342            commitments.insert(i as u32, commitment);
1343        }
1344
1345        // Finalize player with equivocating reveal
1346        let last = (q - 1) as u32;
1347        let (_, commitment, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
1348        commitments.insert(last, commitment);
1349
1350        // Finalize player with equivocating reveal
1351        let result = player.finalize(commitments, BTreeMap::new());
1352        assert!(matches!(result, Err(Error::MissingShare)));
1353    }
1354
1355    /// Configuration for a single DKG/Resharing round.
1356    #[derive(Clone)]
1357    struct Round {
1358        players: Vec<u64>,
1359        absent_dealers: Vec<u64>,
1360        absent_players: Vec<u64>,
1361    }
1362
1363    impl From<Vec<u64>> for Round {
1364        fn from(players: Vec<u64>) -> Self {
1365            Self {
1366                players,
1367                absent_dealers: Vec::new(),
1368                absent_players: Vec::new(),
1369            }
1370        }
1371    }
1372
1373    impl Round {
1374        fn with_absent_dealers(mut self, absent_dealers: Vec<u64>) -> Self {
1375            self.absent_dealers = absent_dealers;
1376            self
1377        }
1378
1379        fn with_absent_players(mut self, absent_players: Vec<u64>) -> Self {
1380            self.absent_players = absent_players;
1381            self
1382        }
1383    }
1384
1385    /// Configuration for a sequence of DKG/Resharing rounds.
1386    #[derive(Clone)]
1387    struct Plan {
1388        rounds: Vec<Round>,
1389        seed: u64,
1390        concurrency: usize,
1391    }
1392
1393    impl Plan {
1394        fn from(rounds: Vec<Round>) -> Self {
1395            Self {
1396                rounds,
1397                seed: 0,
1398                concurrency: 1,
1399            }
1400        }
1401
1402        fn with_concurrency(mut self, concurrency: usize) -> Self {
1403            self.concurrency = concurrency;
1404            self
1405        }
1406
1407        fn with_seed(mut self, seed: u64) -> Self {
1408            self.seed = seed;
1409            self
1410        }
1411
1412        fn run<V: Variant>(&self) -> V::Public {
1413            // We must have at least one round to execute
1414            assert!(
1415                !self.rounds.is_empty(),
1416                "plan must contain at least one round"
1417            );
1418
1419            // Create seeded RNG (for determinism)
1420            let mut rng = StdRng::seed_from_u64(self.seed);
1421            let mut current_public: Option<poly::Public<V>> = None;
1422            let mut participant_states: HashMap<PublicKey, player::Output<V>> = HashMap::new();
1423            let mut share_holders: Option<Ordered<PublicKey>> = None;
1424
1425            // Process rounds
1426            for (round_idx, round) in self.rounds.iter().enumerate() {
1427                // Materialize dealer/player sets
1428                assert!(
1429                    !round.players.is_empty(),
1430                    "round {round_idx} must include at least one player",
1431                );
1432                let player_set = participants(&round.players);
1433                let dealer_candidates = if let Some(ref registry) = share_holders {
1434                    registry.clone()
1435                } else {
1436                    // If no previous share holders, use all players as dealers
1437                    player_set.clone()
1438                };
1439                assert!(
1440                    !dealer_candidates.is_empty(),
1441                    "round {round_idx} must have at least one dealer",
1442                );
1443
1444                // Configure absent dealers and players
1445                let absent_dealers = participants(&round.absent_dealers);
1446                for absent in absent_dealers.iter() {
1447                    assert!(
1448                        dealer_candidates.position(absent).is_some(),
1449                        "round {round_idx} absent dealer not in committee"
1450                    );
1451                }
1452                let dealer_registry = if let Some(ref registry) = share_holders {
1453                    for dealer in dealer_candidates.iter() {
1454                        assert!(
1455                            registry.position(dealer).is_some(),
1456                            "round {round_idx} dealer not in previous committee",
1457                        );
1458                    }
1459                    registry.clone()
1460                } else {
1461                    dealer_candidates.clone()
1462                };
1463                let mut active_dealers = Vec::new();
1464                for dealer in dealer_candidates.iter() {
1465                    if absent_dealers.position(dealer).is_some() {
1466                        continue;
1467                    }
1468                    active_dealers.push(dealer.clone());
1469                }
1470                let active_len = active_dealers.len();
1471                let min_dealers = match current_public.as_ref() {
1472                    None => quorum(player_set.len() as u32),
1473                    Some(previous) => previous.required(),
1474                } as usize;
1475                assert!(
1476                    active_len >= min_dealers,
1477                    "round {} requires at least {} active dealers for {} players, got {}",
1478                    round_idx,
1479                    min_dealers,
1480                    player_set.len(),
1481                    active_len
1482                );
1483                let absent_players = participants(&round.absent_players);
1484                for absent in absent_players.iter() {
1485                    assert!(
1486                        player_set.position(absent).is_some(),
1487                        "round {round_idx} absent player not in committee"
1488                    );
1489                }
1490
1491                // Setup dealers
1492                let mut dealers = BTreeMap::new();
1493                let mut dealer_outputs = BTreeMap::new();
1494                let mut expected_reveals = BTreeMap::new();
1495                let expected_inactive: Ordered<u32> = absent_players
1496                    .iter()
1497                    .map(|player_pk| player_set.position(player_pk).unwrap() as u32)
1498                    .collect();
1499                for dealer_pk in active_dealers.iter() {
1500                    let previous_share = participant_states
1501                        .get(dealer_pk)
1502                        .map(|out| out.share.clone());
1503                    if current_public.is_some() && previous_share.is_none() {
1504                        panic!("dealer missing share required for reshare in round {round_idx}",);
1505                    }
1506
1507                    let (dealer, commitment, shares) =
1508                        Dealer::<_, V>::new(&mut rng, previous_share, player_set.clone());
1509                    dealers.insert(dealer_pk.clone(), dealer);
1510                    dealer_outputs.insert(dealer_pk.clone(), (commitment, shares));
1511                }
1512
1513                // Setup players
1514                let mut players = BTreeMap::new();
1515                for player_pk in player_set.iter() {
1516                    if absent_players.position(player_pk).is_some() {
1517                        continue;
1518                    }
1519                    let player = Player::<_, V>::new(
1520                        player_pk.clone(),
1521                        current_public.clone(),
1522                        dealer_registry.clone(),
1523                        player_set.clone(),
1524                        self.concurrency,
1525                    );
1526                    players.insert(player_pk.clone(), player);
1527                }
1528
1529                // Setup arbiter
1530                let mut arbiter = Arbiter::<_, V>::new(
1531                    current_public.clone(),
1532                    dealer_registry.clone(),
1533                    player_set.clone(),
1534                    self.concurrency,
1535                );
1536
1537                // Distribute dealings
1538                for dealer_pk in active_dealers.iter() {
1539                    let (commitment, shares) = dealer_outputs
1540                        .get(dealer_pk)
1541                        .expect("missing dealer output");
1542                    let commitment = commitment.clone();
1543                    let shares = shares.clone();
1544                    let mut dealer_reveals = Vec::new();
1545                    {
1546                        let dealer = dealers.get_mut(dealer_pk).expect("missing dealer instance");
1547                        for (idx, player_pk) in player_set.iter().enumerate() {
1548                            let share = shares[idx].clone();
1549                            if absent_players.position(player_pk).is_some() {
1550                                dealer_reveals.push(share);
1551                                continue;
1552                            }
1553                            let player_obj = players
1554                                .get_mut(player_pk)
1555                                .expect("missing player for share delivery");
1556                            if let Err(err) =
1557                                player_obj.share(dealer_pk.clone(), commitment.clone(), share)
1558                            {
1559                                panic!(
1560                                    "failed to deliver share from dealer {dealer_pk:?} to player {player_pk:?}: {err:?}",
1561                                );
1562                            }
1563                            dealer.ack(player_pk.clone()).unwrap();
1564                        }
1565                    }
1566
1567                    let dealer = dealers
1568                        .remove(dealer_pk)
1569                        .expect("missing dealer instance after distribution");
1570                    let dealer_output = dealer.finalize().expect("insufficient acknowledgements");
1571                    assert_eq!(
1572                        dealer_output.inactive, expected_inactive,
1573                        "inactive set mismatch for dealer in round {round_idx}",
1574                    );
1575                    let dealer_pos = dealer_registry.position(dealer_pk).unwrap() as u32;
1576                    if !dealer_reveals.is_empty() {
1577                        expected_reveals.insert(dealer_pos, dealer_reveals.clone());
1578                    }
1579                    arbiter
1580                        .commitment(
1581                            dealer_pk.clone(),
1582                            commitment,
1583                            dealer_output.active.into(),
1584                            dealer_reveals,
1585                        )
1586                        .unwrap();
1587                }
1588
1589                // Finalize arbiter
1590                assert!(arbiter.ready(), "arbiter not ready in round {round_idx}");
1591                let (result, disqualified) = arbiter.finalize();
1592                let expected_disqualified =
1593                    dealer_registry.len().saturating_sub(active_dealers.len());
1594                assert_eq!(
1595                    disqualified.len(),
1596                    expected_disqualified,
1597                    "unexpected disqualified dealers in round {round_idx}",
1598                );
1599                let output = result.unwrap();
1600                for (&dealer_idx, _) in output.commitments.iter() {
1601                    let expected = expected_reveals.remove(&dealer_idx).unwrap_or_default();
1602                    match output.reveals.get(&dealer_idx) {
1603                        Some(reveals) => assert_eq!(
1604                            reveals, &expected,
1605                            "unexpected reveal content for dealer {dealer_idx} in round {round_idx}",
1606                        ),
1607                        None => assert!(
1608                            expected.is_empty(),
1609                            "missing reveals for dealer {dealer_idx} in round {round_idx}",
1610                        ),
1611                    }
1612                }
1613                for dealer_idx in output.reveals.keys() {
1614                    assert!(
1615                        output.commitments.contains_key(dealer_idx),
1616                        "reveals present for unselected dealer {dealer_idx} in round {round_idx}",
1617                    );
1618                }
1619                let expected_commitments = quorum(dealer_registry.len() as u32) as usize;
1620                assert_eq!(
1621                    output.commitments.len(),
1622                    expected_commitments,
1623                    "unexpected number of commitments in round {round_idx}",
1624                );
1625
1626                // Finalize players (that were not revealed)
1627                let mut round_results = Vec::new();
1628                let mut next_states = HashMap::new();
1629                for player_pk in player_set.iter() {
1630                    if absent_players.position(player_pk).is_some() {
1631                        continue;
1632                    }
1633                    let player_obj = players.remove(player_pk).unwrap();
1634                    let result = player_obj
1635                        .finalize(output.commitments.clone(), BTreeMap::new())
1636                        .unwrap();
1637                    assert_eq!(result.public, output.public);
1638                    next_states.insert(player_pk.clone(), result.clone());
1639                    round_results.push(result);
1640                }
1641                assert!(
1642                    !round_results.is_empty(),
1643                    "round {round_idx} produced no outputs",
1644                );
1645
1646                // Ensure public constant is maintained between rounds
1647                let public_key = public::<V>(&round_results[0].public);
1648                if let Some(previous) = current_public.as_ref() {
1649                    assert_eq!(public_key, public::<V>(previous));
1650                }
1651
1652                // Check recovered shares by constructing a proof-of-possession
1653                let threshold = quorum(player_set.len() as u32);
1654                let partials = round_results
1655                    .iter()
1656                    .map(|res| partial_sign_proof_of_possession::<V>(&res.public, &res.share))
1657                    .collect::<Vec<_>>();
1658                let signature = threshold_signature_recover::<V, _>(threshold, &partials)
1659                    .expect("unable to recover threshold signature");
1660                verify_proof_of_possession::<V>(public_key, &signature)
1661                    .expect("invalid proof of possession");
1662
1663                // Update state for next round
1664                current_public = Some(round_results[0].public.clone());
1665                share_holders = Some(player_set);
1666                participant_states = next_states;
1667            }
1668
1669            // Return public constant
1670            *public::<V>(&current_public.expect("plan must produce a public constant"))
1671        }
1672    }
1673
1674    // Compute the participant set from a list of IDs
1675    fn participants(ids: &[u64]) -> Ordered<PublicKey> {
1676        ids.iter()
1677            .map(|id| PrivateKey::from_seed(*id).public_key())
1678            .collect::<Ordered<_>>()
1679    }
1680
1681    #[test]
1682    fn test_dkg() {
1683        let plan = Plan::from(vec![Round::from((0..5).collect::<Vec<_>>())]);
1684        plan.run::<MinPk>();
1685        plan.run::<MinSig>();
1686    }
1687
1688    #[test]
1689    fn test_dkg_with_absent_dealer() {
1690        let plan = Plan::from(vec![
1691            Round::from(vec![0, 1, 2, 3]).with_absent_dealers(vec![3])
1692        ]);
1693        plan.run::<MinPk>();
1694        plan.run::<MinSig>();
1695    }
1696
1697    #[test]
1698    fn test_dkg_with_absent_player() {
1699        let plan = Plan::from(vec![
1700            Round::from(vec![0, 1, 2, 3]).with_absent_players(vec![3])
1701        ]);
1702        plan.run::<MinPk>();
1703        plan.run::<MinSig>();
1704    }
1705
1706    #[test]
1707    fn test_dkg_determinism() {
1708        let plan_template = || Plan::from(vec![Round::from((0..5).collect::<Vec<_>>())]);
1709
1710        let public_a_pk = plan_template().with_seed(1).run::<MinPk>();
1711        assert_eq!(public_a_pk, plan_template().with_seed(1).run::<MinPk>());
1712        let public_b_pk = plan_template().with_seed(2).run::<MinPk>();
1713        assert_eq!(public_b_pk, plan_template().with_seed(2).run::<MinPk>());
1714        assert_ne!(public_a_pk, public_b_pk);
1715
1716        let public_a_sig = plan_template().with_seed(1).run::<MinSig>();
1717        assert_eq!(public_a_sig, plan_template().with_seed(1).run::<MinSig>());
1718        let public_b_sig = plan_template().with_seed(2).run::<MinSig>();
1719        assert_eq!(public_b_sig, plan_template().with_seed(2).run::<MinSig>());
1720        assert_ne!(public_a_sig, public_b_sig);
1721    }
1722
1723    #[test]
1724    fn test_reshare_distinct() {
1725        let plan = Plan::from(vec![
1726            Round::from((0..5).collect::<Vec<_>>()),
1727            Round::from((5..15).collect::<Vec<_>>()),
1728            Round::from((15..30).collect::<Vec<_>>()),
1729        ]);
1730        plan.run::<MinPk>();
1731        plan.run::<MinSig>();
1732    }
1733
1734    #[test]
1735    fn test_reshare_increasing_committee() {
1736        let plan = Plan::from(vec![
1737            Round::from(vec![0, 1, 2]),
1738            Round::from(vec![0, 1, 2, 3]),
1739            Round::from(vec![0, 1, 2, 3, 4]),
1740            Round::from(vec![0, 1, 2, 3, 4, 5]),
1741        ]);
1742        plan.run::<MinPk>();
1743        plan.run::<MinSig>();
1744    }
1745
1746    #[test]
1747    fn test_reshare_decreasing_committee() {
1748        let plan = Plan::from(vec![
1749            Round::from(vec![0, 1, 2, 3, 4, 5]),
1750            Round::from(vec![0, 1, 2, 3, 4]),
1751            Round::from(vec![0, 1, 2, 3]),
1752            Round::from(vec![0, 1, 2]),
1753        ]);
1754        plan.run::<MinPk>();
1755        plan.run::<MinSig>();
1756    }
1757
1758    #[test]
1759    fn test_reshare_with_absent_dealer() {
1760        let plan = Plan::from(vec![
1761            Round::from(vec![0, 1, 2, 3]),
1762            Round::from(vec![4, 5, 6, 7]).with_absent_dealers(vec![3]),
1763        ]);
1764        plan.run::<MinPk>();
1765        plan.run::<MinSig>();
1766    }
1767
1768    #[test]
1769    fn test_reshare_with_absent_player() {
1770        let plan = Plan::from(vec![
1771            Round::from(vec![0, 1, 2, 3]),
1772            Round::from(vec![4, 5, 6, 7]).with_absent_players(vec![4]),
1773            Round::from(vec![8, 9, 10, 11]).with_absent_dealers(vec![4]),
1774        ]);
1775        plan.run::<MinPk>();
1776        plan.run::<MinSig>();
1777    }
1778
1779    #[test]
1780    fn test_reshare_min_active() {
1781        let plan = Plan::from(vec![
1782            Round::from(vec![0, 1, 2, 3]).with_absent_players(vec![3]),
1783            Round::from(vec![4, 5, 6, 7]).with_absent_dealers(vec![3]),
1784        ]);
1785        plan.run::<MinPk>();
1786        plan.run::<MinSig>();
1787    }
1788
1789    #[test]
1790    fn test_reshare_min_active_different_sizes() {
1791        let plan = Plan::from(vec![
1792            Round::from(vec![0, 1, 2, 3]).with_absent_players(vec![3]),
1793            Round::from(vec![4, 5, 6, 7, 8, 9]).with_absent_dealers(vec![3]),
1794        ]);
1795        plan.run::<MinPk>();
1796        plan.run::<MinSig>();
1797    }
1798
1799    #[test]
1800    fn test_reshare_min_active_large() {
1801        let plan = Plan::from(vec![
1802            Round::from((0..20).collect::<Vec<_>>())
1803                .with_absent_dealers((14..20).collect::<Vec<_>>())
1804                .with_absent_players((14..20).collect::<Vec<_>>()),
1805            Round::from((100..200).collect::<Vec<_>>())
1806                .with_absent_dealers((14..20).collect::<Vec<_>>()),
1807        ])
1808        .with_concurrency(4);
1809        plan.run::<MinPk>();
1810        plan.run::<MinSig>();
1811    }
1812
1813    #[test]
1814    fn test_reshare_determinism() {
1815        let plan_template = || {
1816            Plan::from(vec![
1817                Round::from((0..5).collect::<Vec<_>>()),
1818                Round::from((5..10).collect::<Vec<_>>()),
1819            ])
1820        };
1821
1822        let public_a_pk = plan_template().with_seed(1).run::<MinPk>();
1823        assert_eq!(public_a_pk, plan_template().with_seed(1).run::<MinPk>());
1824        let public_b_pk = plan_template().with_seed(2).run::<MinPk>();
1825        assert_eq!(public_b_pk, plan_template().with_seed(2).run::<MinPk>());
1826        assert_ne!(public_a_pk, public_b_pk);
1827
1828        let public_a_sig = plan_template().with_seed(1).run::<MinSig>();
1829        assert_eq!(public_a_sig, plan_template().with_seed(1).run::<MinSig>());
1830        let public_b_sig = plan_template().with_seed(2).run::<MinSig>();
1831        assert_eq!(public_b_sig, plan_template().with_seed(2).run::<MinSig>());
1832        assert_ne!(public_a_sig, public_b_sig);
1833    }
1834}