1use super::primitives::group::{Private, Share};
306use crate::{
307 bls12381::primitives::{
308 group::Scalar,
309 sharing::{Mode, ModeVersion, Sharing},
310 variant::Variant,
311 },
312 transcript::{Summary, Transcript},
313 BatchVerifier, PublicKey, Secret, Signer,
314};
315use commonware_codec::{Encode, EncodeSize, RangeCfg, Read, ReadExt, Write};
316use commonware_math::{
317 algebra::{Additive, CryptoGroup, Random, Ring as _},
318 poly::{Interpolator, Poly},
319};
320use commonware_parallel::{Sequential, Strategy};
321#[cfg(feature = "arbitrary")]
322use commonware_utils::N3f1;
323use commonware_utils::{
324 ordered::{Map, Quorum, Set},
325 Faults, Participant, TryCollect, NZU32,
326};
327use core::num::NonZeroU32;
328use rand_core::CryptoRngCore;
329use std::{borrow::Cow, collections::BTreeMap, marker::PhantomData};
330use thiserror::Error;
331
332const NAMESPACE: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG";
333const SIG_ACK: &[u8] = b"ack";
334const SIG_LOG: &[u8] = b"log";
335const NOISE_PRE_VERIFY: &[u8] = b"pre_verify";
336
337#[derive(Debug, Error)]
347pub enum Error {
348 #[error("missing dealer's share from the previous round")]
349 MissingDealerShare,
350 #[error("player is not present in the list of players")]
351 UnknownPlayer,
352 #[error("dealer is not present in the previous list of players")]
353 UnknownDealer(String),
354 #[error("invalid number of dealers: {0}")]
355 NumDealers(usize),
356 #[error("invalid number of players: {0}")]
357 NumPlayers(usize),
358 #[error("dkg failed for some reason")]
359 DkgFailed,
360 #[error("logs are bound to a different dkg round")]
361 MismatchedLogs,
362 #[error("missing player's dealing")]
370 MissingPlayerDealing,
371}
372
373#[derive(Debug, Clone, PartialEq, Eq)]
375pub struct Output<V: Variant, P> {
376 summary: Summary,
377 public: Sharing<V>,
378 dealers: Set<P>,
379 players: Set<P>,
380 revealed: Set<P>,
381}
382
383impl<V: Variant, P: Ord> Output<V, P> {
384 fn share_commitment(&self, player: &P) -> Option<V::Public> {
385 self.public.partial_public(self.players.index(player)?).ok()
386 }
387
388 pub fn quorum<M: Faults>(&self) -> u32 {
390 self.players.quorum::<M>()
391 }
392
393 pub const fn public(&self) -> &Sharing<V> {
397 &self.public
398 }
399
400 pub const fn dealers(&self) -> &Set<P> {
402 &self.dealers
403 }
404
405 pub const fn players(&self) -> &Set<P> {
407 &self.players
408 }
409
410 pub const fn revealed(&self) -> &Set<P> {
414 &self.revealed
415 }
416}
417
418impl<V: Variant, P: PublicKey> EncodeSize for Output<V, P> {
419 fn encode_size(&self) -> usize {
420 self.summary.encode_size()
421 + self.public.encode_size()
422 + self.dealers.encode_size()
423 + self.players.encode_size()
424 + self.revealed.encode_size()
425 }
426}
427
428impl<V: Variant, P: PublicKey> Write for Output<V, P> {
429 fn write(&self, buf: &mut impl bytes::BufMut) {
430 self.summary.write(buf);
431 self.public.write(buf);
432 self.dealers.write(buf);
433 self.players.write(buf);
434 self.revealed.write(buf);
435 }
436}
437
438impl<V: Variant, P: PublicKey> Read for Output<V, P> {
439 type Cfg = (NonZeroU32, ModeVersion);
440
441 fn read_cfg(
442 buf: &mut impl bytes::Buf,
443 (max_participants, max_supported_mode): &Self::Cfg,
444 ) -> Result<Self, commonware_codec::Error> {
445 let max_participants_usize = max_participants.get() as usize;
446 Ok(Self {
447 summary: ReadExt::read(buf)?,
448 public: Read::read_cfg(buf, &(*max_participants, *max_supported_mode))?,
449 dealers: Read::read_cfg(buf, &(RangeCfg::new(1..=max_participants_usize), ()))?, players: Read::read_cfg(buf, &(RangeCfg::new(1..=max_participants_usize), ()))?, revealed: Read::read_cfg(buf, &(RangeCfg::new(0..=max_participants_usize), ()))?, })
453 }
454}
455
456#[cfg(feature = "arbitrary")]
457impl<P: PublicKey, V: Variant> arbitrary::Arbitrary<'_> for Output<V, P>
458where
459 P: for<'a> arbitrary::Arbitrary<'a> + Ord,
460 V::Public: for<'a> arbitrary::Arbitrary<'a>,
461{
462 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
463 let summary = u.arbitrary()?;
464 let public: Sharing<V> = u.arbitrary()?;
465 let total = public.total().get() as usize;
466
467 let num_dealers = u.int_in_range(1..=total * 2)?;
468 let dealers = Set::try_from(
469 u.arbitrary_iter::<P>()?
470 .take(num_dealers)
471 .collect::<Result<Vec<_>, _>>()?,
472 )
473 .map_err(|_| arbitrary::Error::IncorrectFormat)?;
474
475 let players = Set::try_from(
476 u.arbitrary_iter::<P>()?
477 .take(total)
478 .collect::<Result<Vec<_>, _>>()?,
479 )
480 .map_err(|_| arbitrary::Error::IncorrectFormat)?;
481
482 let max_revealed = N3f1::max_faults(total) as usize;
483 let revealed = Set::from_iter_dedup(
484 players
485 .iter()
486 .filter(|_| u.arbitrary::<bool>().unwrap_or(false))
487 .take(max_revealed)
488 .cloned(),
489 );
490
491 Ok(Self {
492 summary,
493 public,
494 dealers,
495 players,
496 revealed,
497 })
498 }
499}
500
501#[derive(Debug, Clone)]
506pub struct Info<V: Variant, P: PublicKey> {
507 summary: Summary,
508 round: u64,
509 previous: Option<Output<V, P>>,
510 mode: Mode,
511 dealers: Set<P>,
512 players: Set<P>,
513}
514
515impl<V: Variant, P: PublicKey> PartialEq for Info<V, P> {
516 fn eq(&self, other: &Self) -> bool {
517 self.summary == other.summary
518 }
519}
520
521impl<V: Variant, P: PublicKey> Info<V, P> {
522 fn unwrap_or_random_share(
528 &self,
529 mut rng: impl CryptoRngCore,
530 share: Option<Scalar>,
531 ) -> Result<Scalar, Error> {
532 let out = match (self.previous.as_ref(), share) {
533 (None, None) => Scalar::random(&mut rng),
534 (_, Some(x)) => x,
535 (Some(_), None) => return Err(Error::MissingDealerShare),
536 };
537 Ok(out)
538 }
539
540 const fn num_players(&self) -> NonZeroU32 {
541 NZU32!(self.players.len() as u32)
543 }
544
545 fn degree<M: Faults>(&self) -> u32 {
546 self.players.quorum::<M>().saturating_sub(1)
547 }
548
549 fn required_commitments<M: Faults>(&self) -> u32 {
550 let dealer_quorum = self.dealers.quorum::<M>();
551 let prev_quorum = self
552 .previous
553 .as_ref()
554 .map(Output::quorum::<M>)
555 .unwrap_or(u32::MIN);
556 dealer_quorum.max(prev_quorum)
557 }
558
559 fn max_reveals<M: Faults>(&self) -> u32 {
560 self.players.max_faults::<M>()
561 }
562
563 fn player_index(&self, player: &P) -> Result<Participant, Error> {
564 self.players.index(player).ok_or(Error::UnknownPlayer)
565 }
566
567 fn dealer_index(&self, dealer: &P) -> Result<Participant, Error> {
568 self.dealers
569 .index(dealer)
570 .ok_or(Error::UnknownDealer(format!("{dealer:?}")))
571 }
572
573 fn player_scalar(&self, player: &P) -> Result<Scalar, Error> {
574 Ok(self
575 .mode
576 .scalar(self.num_players(), self.player_index(player)?)
577 .expect("player index should be < num_players"))
578 }
579
580 #[must_use]
581 fn check_dealer_pub_msg<M: Faults>(&self, dealer: &P, pub_msg: &DealerPubMsg<V>) -> bool {
582 if self.degree::<M>() != pub_msg.commitment.degree_exact() {
583 return false;
584 }
585 if let Some(previous) = self.previous.as_ref() {
586 let Some(share_commitment) = previous.share_commitment(dealer) else {
587 return false;
588 };
589 if *pub_msg.commitment.constant() != share_commitment {
590 return false;
591 }
592 }
593 true
594 }
595
596 #[must_use]
597 fn check_dealer_priv_msg(
598 &self,
599 player: &P,
600 pub_msg: &DealerPubMsg<V>,
601 priv_msg: &DealerPrivMsg,
602 ) -> bool {
603 let Ok(scalar) = self.player_scalar(player) else {
604 return false;
605 };
606 let expected = pub_msg.commitment.eval_msm(&scalar, &Sequential);
607 priv_msg
608 .share
609 .expose(|share| expected == V::Public::generator() * share)
610 }
611
612 #[must_use]
613 fn check_dealer_log<M: Faults, B: BatchVerifier<PublicKey = P>>(
614 &self,
615 rng: &mut impl CryptoRngCore,
616 strategy: &impl Strategy,
617 round_transcript: &Transcript,
618 dealer: &P,
619 log: &DealerLog<V, P>,
620 ) -> bool {
621 if self.dealer_index(dealer).is_err() {
622 return false;
623 }
624 if !self.check_dealer_pub_msg::<M>(dealer, &log.pub_msg) {
625 return false;
626 }
627 let Some(results_iter) = log.zip_players(&self.players) else {
628 return false;
629 };
630 let ack_summary = transcript_for_ack(round_transcript, dealer, &log.pub_msg).summarize();
631 let mut ack_batch = B::new();
632 let mut reveal_count = 0;
633 let max_reveals = self.max_reveals::<M>();
634 let mut reveal_eval_points = Vec::new();
635 let mut reveal_sum = Scalar::zero();
636 for (player, result) in results_iter {
637 match result {
638 AckOrReveal::Ack(ack) => {
639 if !ack_summary.add_to_batch(&mut ack_batch, player, &ack.sig) {
640 return false;
641 }
642 }
643 AckOrReveal::Reveal(priv_msg) => {
644 reveal_count += 1;
645 if reveal_count > max_reveals {
646 return false;
647 }
648 let Ok(player_scalar) = self.player_scalar(player) else {
649 return false;
650 };
651 let coeff = if reveal_count == 1 {
652 Scalar::one()
653 } else {
654 Scalar::random(&mut *rng)
655 };
656 reveal_eval_points.push((coeff.clone(), player_scalar));
657 priv_msg
658 .share
659 .expose(|share| reveal_sum += &(coeff * share));
660 }
661 }
662 }
663 if !ack_batch.verify(&mut *rng) {
664 return false;
665 }
666 let lhs = log.pub_msg.commitment.lin_comb_eval(
667 reveal_eval_points
668 .into_iter()
669 .map(|(coeff, point)| (coeff, Cow::Owned(point))),
670 strategy,
671 );
672 lhs == V::Public::generator() * &reveal_sum
673 }
674}
675
676impl<V: Variant, P: PublicKey> Info<V, P> {
677 pub fn new<M: Faults>(
687 namespace: &[u8],
688 round: u64,
689 previous: Option<Output<V, P>>,
690 mode: Mode,
691 dealers: Set<P>,
692 players: Set<P>,
693 ) -> Result<Self, Error> {
694 let participant_range = 1..u32::MAX as usize;
695 if !participant_range.contains(&dealers.len()) {
696 return Err(Error::NumDealers(dealers.len()));
697 }
698 if !participant_range.contains(&players.len()) {
699 return Err(Error::NumPlayers(players.len()));
700 }
701 if let Some(previous) = previous.as_ref() {
702 if let Some(unknown) = dealers
703 .iter()
704 .find(|d| previous.players.position(d).is_none())
705 {
706 return Err(Error::UnknownDealer(format!("{unknown:?}")));
707 }
708 if dealers.len() < previous.quorum::<M>() as usize {
709 return Err(Error::NumDealers(dealers.len()));
710 }
711 }
712 let summary = {
713 let mut transcript = Transcript::new(NAMESPACE);
714 transcript
715 .commit(namespace)
716 .commit(round.encode())
717 .commit(previous.encode())
718 .commit(dealers.encode())
719 .commit(players.encode());
720 if mode != Mode::default() {
724 transcript.commit([mode as u8].as_slice());
725 }
726 transcript.summarize()
727 };
728 Ok(Self {
729 summary,
730 round,
731 previous,
732 mode,
733 dealers,
734 players,
735 })
736 }
737
738 pub const fn round(&self) -> u64 {
742 self.round
743 }
744}
745
746#[derive(Clone, Debug)]
747pub struct DealerPubMsg<V: Variant> {
748 commitment: Poly<V::Public>,
749}
750
751impl<V: Variant> PartialEq for DealerPubMsg<V> {
752 fn eq(&self, other: &Self) -> bool {
753 self.commitment == other.commitment
754 }
755}
756
757impl<V: Variant> Eq for DealerPubMsg<V> {}
758
759impl<V: Variant> EncodeSize for DealerPubMsg<V> {
760 fn encode_size(&self) -> usize {
761 self.commitment.encode_size()
762 }
763}
764
765impl<V: Variant> Write for DealerPubMsg<V> {
766 fn write(&self, buf: &mut impl bytes::BufMut) {
767 self.commitment.write(buf);
768 }
769}
770
771impl<V: Variant> Read for DealerPubMsg<V> {
772 type Cfg = NonZeroU32;
773
774 fn read_cfg(
775 buf: &mut impl bytes::Buf,
776 &max_size: &Self::Cfg,
777 ) -> Result<Self, commonware_codec::Error> {
778 Ok(Self {
779 commitment: Read::read_cfg(buf, &(RangeCfg::from(NZU32!(1)..=max_size), ()))?,
780 })
781 }
782}
783
784#[cfg(feature = "arbitrary")]
785impl<V: Variant> arbitrary::Arbitrary<'_> for DealerPubMsg<V>
786where
787 V::Public: for<'a> arbitrary::Arbitrary<'a>,
788{
789 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
790 let commitment = u.arbitrary()?;
791 Ok(Self { commitment })
792 }
793}
794
795#[derive(Clone, Debug, PartialEq, Eq)]
796pub struct DealerPrivMsg {
797 share: Secret<Scalar>,
798}
799
800impl DealerPrivMsg {
801 pub const fn new(share: Scalar) -> Self {
803 Self {
804 share: Secret::new(share),
805 }
806 }
807}
808
809impl EncodeSize for DealerPrivMsg {
810 fn encode_size(&self) -> usize {
811 self.share.expose(|share| share.encode_size())
812 }
813}
814
815impl Write for DealerPrivMsg {
816 fn write(&self, buf: &mut impl bytes::BufMut) {
817 self.share.expose(|share| share.write(buf));
818 }
819}
820
821impl Read for DealerPrivMsg {
822 type Cfg = ();
823
824 fn read_cfg(
825 buf: &mut impl bytes::Buf,
826 _cfg: &Self::Cfg,
827 ) -> Result<Self, commonware_codec::Error> {
828 Ok(Self::new(ReadExt::read(buf)?))
829 }
830}
831
832#[cfg(feature = "arbitrary")]
833impl arbitrary::Arbitrary<'_> for DealerPrivMsg {
834 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
835 Ok(Self::new(u.arbitrary()?))
836 }
837}
838
839#[derive(Clone, Debug)]
840pub struct PlayerAck<P: PublicKey> {
841 sig: P::Signature,
842}
843
844impl<P: PublicKey> PartialEq for PlayerAck<P> {
845 fn eq(&self, other: &Self) -> bool {
846 self.sig == other.sig
847 }
848}
849
850impl<P: PublicKey> EncodeSize for PlayerAck<P> {
851 fn encode_size(&self) -> usize {
852 self.sig.encode_size()
853 }
854}
855
856impl<P: PublicKey> Write for PlayerAck<P> {
857 fn write(&self, buf: &mut impl bytes::BufMut) {
858 self.sig.write(buf);
859 }
860}
861
862impl<P: PublicKey> Read for PlayerAck<P> {
863 type Cfg = ();
864
865 fn read_cfg(
866 buf: &mut impl bytes::Buf,
867 _cfg: &Self::Cfg,
868 ) -> Result<Self, commonware_codec::Error> {
869 Ok(Self {
870 sig: ReadExt::read(buf)?,
871 })
872 }
873}
874
875#[cfg(feature = "arbitrary")]
876impl<P: PublicKey> arbitrary::Arbitrary<'_> for PlayerAck<P>
877where
878 P::Signature: for<'a> arbitrary::Arbitrary<'a>,
879{
880 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
881 let sig = u.arbitrary()?;
882 Ok(Self { sig })
883 }
884}
885
886#[derive(Clone, PartialEq)]
887enum AckOrReveal<P: PublicKey> {
888 Ack(PlayerAck<P>),
889 Reveal(DealerPrivMsg),
890}
891
892impl<P: PublicKey> AckOrReveal<P> {
893 const fn is_reveal(&self) -> bool {
894 matches!(*self, Self::Reveal(_))
895 }
896}
897
898impl<P: PublicKey> std::fmt::Debug for AckOrReveal<P> {
899 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
900 match self {
901 Self::Ack(x) => write!(f, "Ack({x:?})"),
902 Self::Reveal(_) => write!(f, "Reveal(REDACTED)"),
903 }
904 }
905}
906
907impl<P: PublicKey> EncodeSize for AckOrReveal<P> {
908 fn encode_size(&self) -> usize {
909 1 + match self {
910 Self::Ack(x) => x.encode_size(),
911 Self::Reveal(x) => x.encode_size(),
912 }
913 }
914}
915
916impl<P: PublicKey> Write for AckOrReveal<P> {
917 fn write(&self, buf: &mut impl bytes::BufMut) {
918 match self {
919 Self::Ack(x) => {
920 0u8.write(buf);
921 x.write(buf);
922 }
923 Self::Reveal(x) => {
924 1u8.write(buf);
925 x.write(buf);
926 }
927 }
928 }
929}
930
931impl<P: PublicKey> Read for AckOrReveal<P> {
932 type Cfg = ();
933
934 fn read_cfg(
935 buf: &mut impl bytes::Buf,
936 _cfg: &Self::Cfg,
937 ) -> Result<Self, commonware_codec::Error> {
938 let tag = u8::read(buf)?;
939 match tag {
940 0 => Ok(Self::Ack(ReadExt::read(buf)?)),
941 1 => Ok(Self::Reveal(ReadExt::read(buf)?)),
942 x => Err(commonware_codec::Error::InvalidEnum(x)),
943 }
944 }
945}
946
947#[cfg(feature = "arbitrary")]
948impl<P: PublicKey> arbitrary::Arbitrary<'_> for AckOrReveal<P>
949where
950 P: for<'a> arbitrary::Arbitrary<'a>,
951 P::Signature: for<'a> arbitrary::Arbitrary<'a>,
952{
953 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
954 let choice = u.int_in_range(0..=1)?;
955 match choice {
956 0 => {
957 let ack = u.arbitrary()?;
958 Ok(Self::Ack(ack))
959 }
960 1 => {
961 let reveal = u.arbitrary()?;
962 Ok(Self::Reveal(reveal))
963 }
964 _ => unreachable!(),
965 }
966 }
967}
968
969#[derive(Clone, Debug)]
970enum DealerResult<P: PublicKey> {
971 Ok(Map<P, AckOrReveal<P>>),
972 TooManyReveals,
973}
974
975impl<P: PublicKey> PartialEq for DealerResult<P> {
976 fn eq(&self, other: &Self) -> bool {
977 match (self, other) {
978 (Self::Ok(x), Self::Ok(y)) => x == y,
979 (Self::TooManyReveals, Self::TooManyReveals) => true,
980 _ => false,
981 }
982 }
983}
984
985impl<P: PublicKey> EncodeSize for DealerResult<P> {
986 fn encode_size(&self) -> usize {
987 1 + match self {
988 Self::Ok(r) => r.encode_size(),
989 Self::TooManyReveals => 0,
990 }
991 }
992}
993
994impl<P: PublicKey> Write for DealerResult<P> {
995 fn write(&self, buf: &mut impl bytes::BufMut) {
996 match self {
997 Self::Ok(r) => {
998 0u8.write(buf);
999 r.write(buf);
1000 }
1001 Self::TooManyReveals => {
1002 1u8.write(buf);
1003 }
1004 }
1005 }
1006}
1007
1008impl<P: PublicKey> Read for DealerResult<P> {
1009 type Cfg = NonZeroU32;
1010
1011 fn read_cfg(
1012 buf: &mut impl bytes::Buf,
1013 &max_players: &Self::Cfg,
1014 ) -> Result<Self, commonware_codec::Error> {
1015 let tag = u8::read(buf)?;
1016 match tag {
1017 0 => Ok(Self::Ok(Read::read_cfg(
1018 buf,
1019 &(RangeCfg::from(0..=max_players.get() as usize), (), ()),
1020 )?)),
1021 1 => Ok(Self::TooManyReveals),
1022 x => Err(commonware_codec::Error::InvalidEnum(x)),
1023 }
1024 }
1025}
1026
1027#[cfg(feature = "arbitrary")]
1028impl<P: PublicKey> arbitrary::Arbitrary<'_> for DealerResult<P>
1029where
1030 P: for<'a> arbitrary::Arbitrary<'a>,
1031 P::Signature: for<'a> arbitrary::Arbitrary<'a>,
1032{
1033 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1034 let choice = u.int_in_range(0..=1)?;
1035 match choice {
1036 0 => {
1037 use commonware_utils::TryFromIterator;
1038 use std::collections::HashMap;
1039
1040 let base: HashMap<P, AckOrReveal<P>> = u.arbitrary()?;
1041 let map =
1042 Map::try_from_iter(base).map_err(|_| arbitrary::Error::IncorrectFormat)?;
1043
1044 Ok(Self::Ok(map))
1045 }
1046 1 => Ok(Self::TooManyReveals),
1047 _ => unreachable!(),
1048 }
1049 }
1050}
1051
1052#[derive(Clone, Debug)]
1053pub struct DealerLog<V: Variant, P: PublicKey> {
1054 pub_msg: DealerPubMsg<V>,
1055 results: DealerResult<P>,
1056}
1057
1058impl<V: Variant, P: PublicKey> PartialEq for DealerLog<V, P> {
1059 fn eq(&self, other: &Self) -> bool {
1060 self.pub_msg == other.pub_msg && self.results == other.results
1061 }
1062}
1063
1064impl<V: Variant, P: PublicKey> EncodeSize for DealerLog<V, P> {
1065 fn encode_size(&self) -> usize {
1066 self.pub_msg.encode_size() + self.results.encode_size()
1067 }
1068}
1069
1070impl<V: Variant, P: PublicKey> Write for DealerLog<V, P> {
1071 fn write(&self, buf: &mut impl bytes::BufMut) {
1072 self.pub_msg.write(buf);
1073 self.results.write(buf);
1074 }
1075}
1076
1077impl<V: Variant, P: PublicKey> Read for DealerLog<V, P> {
1078 type Cfg = NonZeroU32;
1079
1080 fn read_cfg(
1081 buf: &mut impl bytes::Buf,
1082 cfg: &Self::Cfg,
1083 ) -> Result<Self, commonware_codec::Error> {
1084 Ok(Self {
1085 pub_msg: Read::read_cfg(buf, cfg)?,
1086 results: Read::read_cfg(buf, cfg)?,
1087 })
1088 }
1089}
1090
1091impl<V: Variant, P: PublicKey> DealerLog<V, P> {
1092 fn get_ack(&self, player: &P) -> Option<&PlayerAck<P>> {
1093 let DealerResult::Ok(results) = &self.results else {
1094 return None;
1095 };
1096 match results.get_value(player) {
1097 Some(AckOrReveal::Ack(ack)) => Some(ack),
1098 _ => None,
1099 }
1100 }
1101
1102 fn get_reveal(&self, player: &P) -> Option<&DealerPrivMsg> {
1103 let DealerResult::Ok(results) = &self.results else {
1104 return None;
1105 };
1106 match results.get_value(player) {
1107 Some(AckOrReveal::Reveal(priv_msg)) => Some(priv_msg),
1108 _ => None,
1109 }
1110 }
1111
1112 fn zip_players<'a, 'b>(
1113 &'a self,
1114 players: &'b Set<P>,
1115 ) -> Option<impl Iterator<Item = (&'b P, &'a AckOrReveal<P>)>> {
1116 match &self.results {
1117 DealerResult::TooManyReveals => None,
1118 DealerResult::Ok(results) => {
1119 if results.keys() != players {
1121 return None;
1122 }
1123 Some(players.iter().zip(results.values().iter()))
1124 }
1125 }
1126 }
1127
1128 pub fn summary(&self) -> DealerLogSummary<P> {
1132 match &self.results {
1133 DealerResult::TooManyReveals => DealerLogSummary::TooManyReveals,
1134 DealerResult::Ok(map) => {
1135 let (reveals, acks): (Vec<_>, Vec<_>) =
1136 map.iter_pairs().partition(|(_, a_r)| a_r.is_reveal());
1137 DealerLogSummary::Ok {
1138 acks: acks
1139 .into_iter()
1140 .map(|(p, _)| p.clone())
1141 .try_collect()
1142 .expect("map keys are deduped"),
1143 reveals: reveals
1144 .into_iter()
1145 .map(|(p, _)| p.clone())
1146 .try_collect()
1147 .expect("map keys are deduped"),
1148 }
1149 }
1150 }
1151 }
1152}
1153
1154#[derive(Clone, Debug)]
1158pub enum DealerLogSummary<P> {
1159 TooManyReveals,
1162 Ok { acks: Set<P>, reveals: Set<P> },
1164}
1165
1166#[cfg(feature = "arbitrary")]
1167impl<V: Variant, P: PublicKey> arbitrary::Arbitrary<'_> for DealerLog<V, P>
1168where
1169 P: for<'a> arbitrary::Arbitrary<'a>,
1170 V::Public: for<'a> arbitrary::Arbitrary<'a>,
1171 P::Signature: for<'a> arbitrary::Arbitrary<'a>,
1172{
1173 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1174 let pub_msg = u.arbitrary()?;
1175 let results = u.arbitrary()?;
1176 Ok(Self { pub_msg, results })
1177 }
1178}
1179
1180#[derive(Clone, Debug)]
1188pub struct SignedDealerLog<V: Variant, S: Signer> {
1189 dealer: S::PublicKey,
1190 log: DealerLog<V, S::PublicKey>,
1191 sig: S::Signature,
1192}
1193
1194impl<V: Variant, S: Signer> PartialEq for SignedDealerLog<V, S> {
1195 fn eq(&self, other: &Self) -> bool {
1196 self.dealer == other.dealer && self.log == other.log && self.sig == other.sig
1197 }
1198}
1199
1200impl<V: Variant, S: Signer> SignedDealerLog<V, S> {
1201 fn sign(sk: &S, info: &Info<V, S::PublicKey>, log: DealerLog<V, S::PublicKey>) -> Self {
1202 let sig = transcript_for_log(info, &log).sign(sk);
1203 Self {
1204 dealer: sk.public_key(),
1205 log,
1206 sig,
1207 }
1208 }
1209
1210 #[allow(clippy::type_complexity)]
1217 pub fn check(
1218 self,
1219 info: &Info<V, S::PublicKey>,
1220 ) -> Option<(S::PublicKey, DealerLog<V, S::PublicKey>)> {
1221 if !transcript_for_log(info, &self.log).verify(&self.dealer, &self.sig) {
1222 return None;
1223 }
1224 Some((self.dealer, self.log))
1225 }
1226}
1227
1228impl<V: Variant, S: Signer> EncodeSize for SignedDealerLog<V, S> {
1229 fn encode_size(&self) -> usize {
1230 self.dealer.encode_size() + self.log.encode_size() + self.sig.encode_size()
1231 }
1232}
1233
1234impl<V: Variant, S: Signer> Write for SignedDealerLog<V, S> {
1235 fn write(&self, buf: &mut impl bytes::BufMut) {
1236 self.dealer.write(buf);
1237 self.log.write(buf);
1238 self.sig.write(buf);
1239 }
1240}
1241
1242impl<V: Variant, S: Signer> Read for SignedDealerLog<V, S> {
1243 type Cfg = NonZeroU32;
1244
1245 fn read_cfg(
1246 buf: &mut impl bytes::Buf,
1247 cfg: &Self::Cfg,
1248 ) -> Result<Self, commonware_codec::Error> {
1249 Ok(Self {
1250 dealer: ReadExt::read(buf)?,
1251 log: Read::read_cfg(buf, cfg)?,
1252 sig: ReadExt::read(buf)?,
1253 })
1254 }
1255}
1256
1257#[cfg(feature = "arbitrary")]
1258impl<V: Variant, S: Signer> arbitrary::Arbitrary<'_> for SignedDealerLog<V, S>
1259where
1260 S::PublicKey: for<'a> arbitrary::Arbitrary<'a>,
1261 V::Public: for<'a> arbitrary::Arbitrary<'a>,
1262 S::Signature: for<'a> arbitrary::Arbitrary<'a>,
1263{
1264 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1265 let dealer = u.arbitrary()?;
1266 let log = u.arbitrary()?;
1267 let sig = u.arbitrary()?;
1268 Ok(Self { dealer, log, sig })
1269 }
1270}
1271
1272fn transcript_for_round<V: Variant, P: PublicKey>(info: &Info<V, P>) -> Transcript {
1273 Transcript::resume(info.summary)
1274}
1275
1276fn transcript_for_ack<V: Variant, P: PublicKey>(
1277 transcript: &Transcript,
1278 dealer: &P,
1279 pub_msg: &DealerPubMsg<V>,
1280) -> Transcript {
1281 let mut out = transcript.fork(SIG_ACK);
1282 out.commit(dealer.encode());
1283 out.commit(pub_msg.encode());
1284 out
1285}
1286
1287fn transcript_for_log<V: Variant, P: PublicKey>(
1288 info: &Info<V, P>,
1289 log: &DealerLog<V, P>,
1290) -> Transcript {
1291 let mut out = transcript_for_round(info).fork(SIG_LOG);
1292 out.commit(log.encode());
1293 out
1294}
1295
1296#[derive(Clone)]
1299pub struct Logs<V: Variant, P: PublicKey, M: Faults> {
1300 info: Info<V, P>,
1301 logs: BTreeMap<P, DealerLog<V, P>>,
1302 known: BTreeMap<P, bool>,
1303 phantom_m: PhantomData<M>,
1304}
1305
1306type SelectedLogs<V, P> = (Info<V, P>, Map<P, DealerLog<V, P>>);
1309
1310impl<V: Variant, P: PublicKey, M: Faults> Logs<V, P, M> {
1311 pub fn new(info: Info<V, P>) -> Self {
1313 Self {
1314 info,
1315 logs: Default::default(),
1316 known: Default::default(),
1317 phantom_m: Default::default(),
1318 }
1319 }
1320
1321 fn check_dealers<B: BatchVerifier<PublicKey = P>>(
1322 rng: &mut impl CryptoRngCore,
1323 info: &Info<V, P>,
1324 strategy: &impl Strategy,
1325 transcript: &Transcript,
1326 dealers: &[(&P, &DealerLog<V, P>)],
1327 ) -> Vec<(P, bool)> {
1328 let checks: Vec<_> = dealers
1329 .iter()
1330 .map(|&(dealer, log)| {
1331 let seed = Summary::random(&mut *rng);
1332 ((*dealer).clone(), log, seed)
1333 })
1334 .collect();
1335
1336 strategy.map_collect_vec(checks, |(dealer, log, seed)| {
1342 let mut local_rng = Transcript::resume(seed).noise(NOISE_PRE_VERIFY);
1343 let valid =
1344 info.check_dealer_log::<M, B>(&mut local_rng, strategy, transcript, &dealer, log);
1345 (dealer, valid)
1346 })
1347 }
1348
1349 pub fn record(&mut self, dealer: P, log: DealerLog<V, P>) -> bool {
1354 self.known.remove(&dealer);
1355 self.logs.insert(dealer, log).is_some()
1356 }
1357
1358 pub fn pre_verify<B: BatchVerifier<PublicKey = P>>(
1367 &mut self,
1368 rng: &mut impl CryptoRngCore,
1369 strategy: &impl Strategy,
1370 ) {
1371 let required_commitments = self.info.required_commitments::<M>() as usize;
1372 let transcript = transcript_for_round(&self.info);
1373
1374 let mut need = required_commitments;
1378 let mut pending = Vec::new();
1379 let mut iter = self.logs.iter();
1380 while need > 0 {
1381 let Some((dealer, log)) = iter.next() else {
1382 break;
1383 };
1384 match self.known.get(dealer) {
1385 Some(true) => need -= 1,
1386 Some(false) => {}
1387 None => {
1388 need -= 1;
1389 pending.push((dealer, log));
1390 }
1391 }
1392 }
1393
1394 let pending_results =
1396 Self::check_dealers::<B>(rng, &self.info, strategy, &transcript, &pending);
1397 let mut all_pending_valid = true;
1398 for (dealer, is_valid) in pending_results {
1399 self.known.insert(dealer, is_valid);
1400 all_pending_valid &= is_valid;
1401 }
1402 if all_pending_valid {
1403 return;
1404 }
1405
1406 let remaining: Vec<_> = iter
1414 .filter(|(dealer, _)| !self.known.contains_key(*dealer))
1415 .collect();
1416 let results = Self::check_dealers::<B>(rng, &self.info, strategy, &transcript, &remaining);
1417 for (dealer, is_valid) in results {
1418 self.known.insert(dealer, is_valid);
1419 }
1420 }
1421
1422 fn select<B: BatchVerifier<PublicKey = P>>(
1426 mut self,
1427 rng: &mut impl CryptoRngCore,
1428 strategy: &impl Strategy,
1429 ) -> Result<SelectedLogs<V, P>, Error> {
1430 self.pre_verify::<B>(rng, strategy);
1431 let required_commitments = self.info.required_commitments::<M>() as usize;
1432 let out: Map<_, _> = self
1433 .logs
1434 .into_iter()
1435 .filter(|(dealer, _)| matches!(self.known.get(dealer), Some(true)))
1436 .take(required_commitments)
1437 .try_collect()
1438 .expect("dealers should be unique");
1439 if out.len() < required_commitments {
1440 return Err(Error::DkgFailed);
1441 }
1442 Ok((self.info, out))
1443 }
1444}
1445
1446pub struct Dealer<V: Variant, S: Signer> {
1447 me: S,
1448 info: Info<V, S::PublicKey>,
1449 pub_msg: DealerPubMsg<V>,
1450 results: Map<S::PublicKey, AckOrReveal<S::PublicKey>>,
1451 transcript: Transcript,
1452}
1453
1454impl<V: Variant, S: Signer> Dealer<V, S> {
1455 #[allow(clippy::type_complexity)]
1477 pub fn start<M: Faults>(
1478 mut rng: impl CryptoRngCore,
1479 info: Info<V, S::PublicKey>,
1480 me: S,
1481 share: Option<Share>,
1482 ) -> Result<(Self, DealerPubMsg<V>, Vec<(S::PublicKey, DealerPrivMsg)>), Error> {
1483 info.dealer_index(&me.public_key())?;
1485 let share = info.unwrap_or_random_share(
1486 &mut rng,
1487 share.map(|x| x.private.expose_unwrap()),
1492 )?;
1493 let my_poly = Poly::new_with_constant(&mut rng, info.degree::<M>(), share);
1494 let priv_msgs = info
1495 .players
1496 .iter()
1497 .map(|pk| {
1498 (
1499 pk.clone(),
1500 DealerPrivMsg::new(my_poly.eval_msm(
1501 &info.player_scalar(pk).expect("player should exist"),
1502 &Sequential,
1503 )),
1504 )
1505 })
1506 .collect::<Vec<_>>();
1507 let results: Map<_, _> = priv_msgs
1508 .clone()
1509 .into_iter()
1510 .map(|(pk, priv_msg)| (pk, AckOrReveal::Reveal(priv_msg)))
1511 .try_collect()
1512 .expect("players are unique");
1513 let commitment = Poly::commit(my_poly);
1514 let pub_msg = DealerPubMsg { commitment };
1515 let transcript = {
1516 let t = transcript_for_round(&info);
1517 transcript_for_ack(&t, &me.public_key(), &pub_msg)
1518 };
1519 let this = Self {
1520 me,
1521 info,
1522 pub_msg: pub_msg.clone(),
1523 results,
1524 transcript,
1525 };
1526 Ok((this, pub_msg, priv_msgs))
1527 }
1528
1529 pub fn receive_player_ack(
1534 &mut self,
1535 player: S::PublicKey,
1536 ack: PlayerAck<S::PublicKey>,
1537 ) -> Result<(), Error> {
1538 let res_mut = self
1539 .results
1540 .get_value_mut(&player)
1541 .ok_or(Error::UnknownPlayer)?;
1542 if self.transcript.verify(&player, &ack.sig) {
1543 *res_mut = AckOrReveal::Ack(ack);
1544 }
1545 Ok(())
1546 }
1547
1548 pub fn finalize<M: Faults>(self) -> SignedDealerLog<V, S> {
1552 let reveals = self
1553 .results
1554 .values()
1555 .iter()
1556 .filter(|x| x.is_reveal())
1557 .count() as u32;
1558 let results = if reveals > self.info.max_reveals::<M>() {
1560 DealerResult::TooManyReveals
1561 } else {
1562 DealerResult::Ok(self.results)
1563 };
1564 let log = DealerLog {
1565 pub_msg: self.pub_msg,
1566 results,
1567 };
1568 SignedDealerLog::sign(&self.me, &self.info, log)
1569 }
1570}
1571
1572struct ObserveInner<V: Variant, P: PublicKey> {
1573 output: Output<V, P>,
1574 weights: Option<Interpolator<P, Scalar>>,
1575}
1576
1577impl<V: Variant, P: PublicKey> ObserveInner<V, P> {
1578 fn reckon<M: Faults>(
1579 info: Info<V, P>,
1580 selected: Map<P, DealerLog<V, P>>,
1581 strategy: &impl Strategy,
1582 ) -> Result<Self, Error> {
1583 let max_faults = info.players.max_faults::<M>();
1585 let mut reveal_counts: BTreeMap<P, u32> = BTreeMap::new();
1586 let mut revealed = Vec::new();
1587 for log in selected.values() {
1588 let Some(iter) = log.zip_players(&info.players) else {
1589 continue;
1590 };
1591 for (player, result) in iter {
1592 if !result.is_reveal() {
1593 continue;
1594 }
1595 let count = reveal_counts.entry(player.clone()).or_insert(0);
1596 *count += 1;
1597 if *count == max_faults + 1 {
1598 revealed.push(player.clone());
1599 }
1600 }
1601 }
1602 let revealed: Set<P> = revealed
1603 .into_iter()
1604 .try_collect()
1605 .expect("players are unique");
1606
1607 let dealers: Set<P> = selected
1609 .keys()
1610 .iter()
1611 .cloned()
1612 .try_collect()
1613 .expect("selected dealers are unique");
1614
1615 let (public, weights) = if let Some(previous) = info.previous.as_ref() {
1617 let weights = previous
1618 .public()
1619 .mode()
1620 .subset_interpolator(previous.players(), selected.keys())
1621 .expect("the result of select should produce a valid subset");
1622 let commitments = selected
1623 .into_iter()
1624 .map(|(dealer, log)| (dealer, log.pub_msg.commitment))
1625 .try_collect::<Map<_, _>>()
1626 .expect("Map should have unique keys");
1627 let public = weights
1628 .interpolate(&commitments, strategy)
1629 .expect("select checks that enough points have been provided");
1630 if previous.public().public() != public.constant() {
1631 return Err(Error::DkgFailed);
1632 }
1633 (public, Some(weights))
1634 } else {
1635 let mut public = Poly::zero();
1636 for log in selected.values() {
1637 public += &log.pub_msg.commitment;
1638 }
1639 (public, None)
1640 };
1641 let n = info.players.len() as u32;
1642 let output = Output {
1643 summary: info.summary,
1644 public: Sharing::new(info.mode, NZU32!(n), public),
1645 dealers,
1646 players: info.players,
1647 revealed,
1648 };
1649 Ok(Self { output, weights })
1650 }
1651}
1652
1653pub fn observe<V: Variant, P: PublicKey, M: Faults, B: BatchVerifier<PublicKey = P>>(
1662 rng: &mut impl CryptoRngCore,
1663 logs: Logs<V, P, M>,
1664 strategy: &impl Strategy,
1665) -> Result<Output<V, P>, Error> {
1666 let (info, selected) = logs.select::<B>(rng, strategy)?;
1667 ObserveInner::<V, P>::reckon::<M>(info, selected, strategy).map(|x| x.output)
1668}
1669
1670pub struct Player<V: Variant, S: Signer> {
1676 me: S,
1677 me_pub: S::PublicKey,
1678 info: Info<V, S::PublicKey>,
1679 index: Participant,
1680 transcript: Transcript,
1681 view: BTreeMap<S::PublicKey, (DealerPubMsg<V>, DealerPrivMsg)>,
1682}
1683
1684impl<V: Variant, S: Signer> Player<V, S> {
1685 pub fn new(info: Info<V, S::PublicKey>, me: S) -> Result<Self, Error> {
1689 let me_pub = me.public_key();
1690 Ok(Self {
1691 index: info.player_index(&me_pub)?,
1692 me,
1693 me_pub,
1694 transcript: transcript_for_round(&info),
1695 info,
1696 view: BTreeMap::new(),
1697 })
1698 }
1699
1700 #[allow(clippy::type_complexity)]
1726 pub fn resume<M: Faults>(
1727 info: Info<V, S::PublicKey>,
1728 me: S,
1729 logs: &BTreeMap<S::PublicKey, DealerLog<V, S::PublicKey>>,
1730 msgs: impl IntoIterator<Item = (S::PublicKey, DealerPubMsg<V>, DealerPrivMsg)>,
1731 ) -> Result<(Self, BTreeMap<S::PublicKey, PlayerAck<S::PublicKey>>), Error> {
1732 let mut this = Self::new(info, me)?;
1734 let mut acks = BTreeMap::new();
1735 for (dealer, pub_msg, priv_msg) in msgs {
1736 if let Some(ack) = this.dealer_message::<M>(dealer.clone(), pub_msg, priv_msg) {
1737 acks.insert(dealer, ack);
1738 }
1739 }
1740
1741 if logs.iter().any(|(dealer, log)| {
1744 let Some(ack) = log.get_ack(&this.me_pub) else {
1745 return false;
1746 };
1747 transcript_for_ack(&this.transcript, dealer, &log.pub_msg)
1749 .verify(&this.me_pub, &ack.sig)
1750 && !acks.contains_key(dealer)
1751 }) {
1752 return Err(Error::MissingPlayerDealing);
1755 }
1756
1757 Ok((this, acks))
1758 }
1759
1760 pub fn dealer_message<M: Faults>(
1767 &mut self,
1768 dealer: S::PublicKey,
1769 pub_msg: DealerPubMsg<V>,
1770 priv_msg: DealerPrivMsg,
1771 ) -> Option<PlayerAck<S::PublicKey>> {
1772 if self.view.contains_key(&dealer) {
1773 return None;
1774 }
1775 self.info.dealer_index(&dealer).ok()?;
1776 if !self.info.check_dealer_pub_msg::<M>(&dealer, &pub_msg) {
1777 return None;
1778 }
1779 if !self
1780 .info
1781 .check_dealer_priv_msg(&self.me_pub, &pub_msg, &priv_msg)
1782 {
1783 return None;
1784 }
1785 let sig = transcript_for_ack(&self.transcript, &dealer, &pub_msg).sign(&self.me);
1786 self.view.insert(dealer, (pub_msg, priv_msg));
1787 Some(PlayerAck { sig })
1788 }
1789
1790 pub fn finalize<M: Faults, B: BatchVerifier<PublicKey = S::PublicKey>>(
1805 self,
1806 rng: &mut impl CryptoRngCore,
1807 logs: Logs<V, S::PublicKey, M>,
1808 strategy: &impl Strategy,
1809 ) -> Result<(Output<V, S::PublicKey>, Share), Error> {
1810 if logs.info != self.info {
1815 return Err(Error::MismatchedLogs);
1816 }
1817 let (_, selected) = logs.select::<B>(rng, strategy)?;
1818 if selected
1819 .iter_pairs()
1820 .any(|(d, l)| l.get_ack(&self.me_pub).is_some() && !self.view.contains_key(d))
1821 {
1822 return Err(Error::MissingPlayerDealing);
1823 }
1824
1825 let dealings = selected
1831 .iter_pairs()
1832 .map(|(dealer, log)| {
1833 let share = self
1834 .view
1835 .get(dealer)
1836 .map(|(_, priv_msg)| priv_msg.share.clone().expose_unwrap())
1837 .unwrap_or_else(|| {
1838 log.get_reveal(&self.me_pub).map_or_else(
1839 || {
1840 unreachable!(
1841 "Logs::select didn't check dealer reveal, or we're not a player?"
1842 )
1843 },
1844 |priv_msg| priv_msg.share.clone().expose_unwrap(),
1845 )
1846 });
1847 (dealer.clone(), share)
1848 })
1849 .try_collect::<Map<_, _>>()
1850 .expect("Logs::select produces at most one entry per dealer");
1851 let ObserveInner { output, weights } =
1852 ObserveInner::<V, S::PublicKey>::reckon::<M>(self.info, selected, strategy)?;
1853 let private = weights.map_or_else(
1854 || {
1855 let mut out = <Scalar as Additive>::zero();
1856 for s in dealings.values() {
1857 out += s;
1858 }
1859 out
1860 },
1861 |weights| {
1862 weights
1863 .interpolate(&dealings, strategy)
1864 .expect("Logs::select ensures that we can recover")
1865 },
1866 );
1867 let share = Share::new(self.index, Private::new(private));
1868 Ok((output, share))
1869 }
1870}
1871
1872pub type DealResult<V, P> = Result<(Output<V, P>, Map<P, Share>), Error>;
1874
1875pub fn deal<V: Variant, P: Clone + Ord, M: Faults>(
1877 mut rng: impl CryptoRngCore,
1878 mode: Mode,
1879 players: Set<P>,
1880) -> DealResult<V, P> {
1881 if players.is_empty() {
1882 return Err(Error::NumPlayers(0));
1883 }
1884 let n = NZU32!(players.len() as u32);
1885 let t = players.quorum::<M>();
1886 let private = Poly::new(&mut rng, t - 1);
1887 let shares: Map<_, _> = players
1888 .iter()
1889 .enumerate()
1890 .map(|(i, p)| {
1891 let participant = Participant::from_usize(i);
1892 let eval = private.eval_msm(
1893 &mode
1894 .scalar(n, participant)
1895 .expect("player index should be valid"),
1896 &Sequential,
1897 );
1898 let share = Share::new(participant, Private::new(eval));
1899 (p.clone(), share)
1900 })
1901 .try_collect()
1902 .expect("players are unique");
1903 let output = Output {
1904 summary: Summary::random(&mut rng),
1905 public: Sharing::new(mode, n, Poly::commit(private)),
1906 dealers: players.clone(),
1907 players,
1908 revealed: Set::default(),
1909 };
1910 Ok((output, shares))
1911}
1912
1913pub fn deal_anonymous<V: Variant, M: Faults>(
1919 rng: impl CryptoRngCore,
1920 mode: Mode,
1921 n: NonZeroU32,
1922) -> (Sharing<V>, Vec<Share>) {
1923 let players = (0..n.get()).try_collect().unwrap();
1924 let (output, shares) = deal::<V, _, M>(rng, mode, players).unwrap();
1925 (output.public().clone(), shares.values().to_vec())
1926}
1927
1928#[cfg(any(feature = "arbitrary", test))]
1929mod test_plan {
1930 use super::*;
1931 use crate::{
1932 bls12381::primitives::{
1933 ops::{self, threshold},
1934 variant::Variant,
1935 },
1936 ed25519, PublicKey,
1937 };
1938 use anyhow::anyhow;
1939 use bytes::BytesMut;
1940 use commonware_utils::{Faults, N3f1, TryCollect};
1941 use core::num::NonZeroI32;
1942 use rand::{rngs::StdRng, SeedableRng as _};
1943 use std::collections::BTreeSet;
1944
1945 fn apply_mask(bytes: &mut BytesMut, mask: &[u8]) -> bool {
1947 let mut modified = false;
1948 for (l, &r) in bytes.iter_mut().zip(mask.iter()) {
1949 modified |= r != 0;
1950 *l ^= r;
1951 }
1952 modified
1953 }
1954
1955 #[derive(Clone, Default, Debug)]
1956 pub struct Masks {
1957 pub info_summary: Vec<u8>,
1958 pub dealer: Vec<u8>,
1959 pub pub_msg: Vec<u8>,
1960 pub log: Vec<u8>,
1961 }
1962
1963 impl Masks {
1964 fn modifies_player_ack(&self) -> bool {
1965 self.info_summary.iter().any(|&b| b != 0)
1966 || self.dealer.iter().any(|&b| b != 0)
1967 || self.pub_msg.iter().any(|&b| b != 0)
1968 }
1969
1970 fn transcript_for_round<V: Variant, P: PublicKey>(
1971 &self,
1972 info: &Info<V, P>,
1973 ) -> anyhow::Result<(bool, Transcript)> {
1974 let mut summary_bs = info.summary.encode_mut();
1975 let modified = apply_mask(&mut summary_bs, &self.info_summary);
1976 let summary = Summary::read(&mut summary_bs)?;
1977 Ok((modified, Transcript::resume(summary)))
1978 }
1979
1980 fn transcript_for_player_ack<V: Variant, P: PublicKey>(
1981 &self,
1982 info: &Info<V, P>,
1983 dealer: &P,
1984 pub_msg: &DealerPubMsg<V>,
1985 ) -> anyhow::Result<(bool, Transcript)> {
1986 let (mut modified, transcript) = self.transcript_for_round(info)?;
1987 let mut transcript = transcript.fork(SIG_ACK);
1988
1989 let mut dealer_bs = dealer.encode_mut();
1990 modified |= apply_mask(&mut dealer_bs, &self.dealer);
1991 transcript.commit(&mut dealer_bs);
1992
1993 let mut pub_msg_bs = pub_msg.encode_mut();
1994 modified |= apply_mask(&mut pub_msg_bs, &self.pub_msg);
1995 transcript.commit(&mut pub_msg_bs);
1996
1997 Ok((modified, transcript))
1998 }
1999
2000 fn transcript_for_signed_dealer_log<V: Variant, P: PublicKey>(
2001 &self,
2002 info: &Info<V, P>,
2003 log: &DealerLog<V, P>,
2004 ) -> anyhow::Result<(bool, Transcript)> {
2005 let (mut modified, transcript) = self.transcript_for_round(info)?;
2006 let mut transcript = transcript.fork(SIG_LOG);
2007
2008 let mut log_bs = log.encode_mut();
2009 modified |= apply_mask(&mut log_bs, &self.log);
2010 transcript.commit(&mut log_bs);
2011
2012 Ok((modified, transcript))
2013 }
2014 }
2015
2016 #[derive(Debug, Default)]
2018 pub struct Round {
2019 dealers: Vec<u32>,
2020 players: Vec<u32>,
2021 crash_resume_players: BTreeSet<(u32, u32)>,
2022 resume_missing_dealer_msg_fails: BTreeSet<(u32, u32)>,
2023 finalize_missing_dealer_msg_fails: BTreeSet<u32>,
2024 no_acks: BTreeSet<(u32, u32)>,
2025 bad_shares: BTreeSet<(u32, u32)>,
2026 bad_player_sigs: BTreeMap<(u32, u32), Masks>,
2027 bad_reveals: BTreeSet<(u32, u32)>,
2028 bad_dealer_sigs: BTreeMap<u32, Masks>,
2029 replace_shares: BTreeSet<u32>,
2030 shift_degrees: BTreeMap<u32, NonZeroI32>,
2031 }
2032
2033 impl Round {
2034 pub fn new(dealers: Vec<u32>, players: Vec<u32>) -> Self {
2035 Self {
2036 dealers,
2037 players,
2038 ..Default::default()
2039 }
2040 }
2041
2042 pub fn no_ack(mut self, dealer: u32, player: u32) -> Self {
2043 self.no_acks.insert((dealer, player));
2044 self
2045 }
2046
2047 pub fn crash_resume_player(mut self, after_dealer: u32, player: u32) -> Self {
2048 self.crash_resume_players.insert((after_dealer, player));
2049 self
2050 }
2051
2052 pub fn resume_missing_dealer_msg_fails(
2053 mut self,
2054 after_dealer: u32,
2055 missing_dealer: u32,
2056 ) -> Self {
2057 self.resume_missing_dealer_msg_fails
2058 .insert((after_dealer, missing_dealer));
2059 self
2060 }
2061
2062 pub fn finalize_missing_dealer_msg_fails(mut self, player: u32) -> Self {
2063 self.finalize_missing_dealer_msg_fails.insert(player);
2064 self
2065 }
2066
2067 pub fn bad_share(mut self, dealer: u32, player: u32) -> Self {
2068 self.bad_shares.insert((dealer, player));
2069 self
2070 }
2071
2072 pub fn bad_player_sig(mut self, dealer: u32, player: u32, masks: Masks) -> Self {
2073 self.bad_player_sigs.insert((dealer, player), masks);
2074 self
2075 }
2076
2077 pub fn bad_reveal(mut self, dealer: u32, player: u32) -> Self {
2078 self.bad_reveals.insert((dealer, player));
2079 self
2080 }
2081
2082 pub fn bad_dealer_sig(mut self, dealer: u32, masks: Masks) -> Self {
2083 self.bad_dealer_sigs.insert(dealer, masks);
2084 self
2085 }
2086
2087 pub fn replace_share(mut self, dealer: u32) -> Self {
2088 self.replace_shares.insert(dealer);
2089 self
2090 }
2091
2092 pub fn shift_degree(mut self, dealer: u32, shift: NonZeroI32) -> Self {
2093 self.shift_degrees.insert(dealer, shift);
2094 self
2095 }
2096
2097 pub fn validate(
2100 &self,
2101 num_participants: u32,
2102 previous_players: Option<&[u32]>,
2103 ) -> anyhow::Result<()> {
2104 if self.dealers.is_empty() {
2105 return Err(anyhow!("dealers is empty"));
2106 }
2107 if self.players.is_empty() {
2108 return Err(anyhow!("players is empty"));
2109 }
2110 for &d in &self.dealers {
2112 if d >= num_participants {
2113 return Err(anyhow!("dealer {d} out of range [1, {num_participants}]"));
2114 }
2115 }
2116 for &p in &self.players {
2117 if p >= num_participants {
2118 return Err(anyhow!("player {p} out of range [1, {num_participants}]"));
2119 }
2120 }
2121 for &(after_dealer, player) in &self.crash_resume_players {
2123 if !self.dealers.contains(&after_dealer) {
2124 return Err(anyhow!("crash_resume dealer {after_dealer} not in round"));
2125 }
2126 if !self.players.contains(&player) {
2127 return Err(anyhow!("crash_resume player {player} not in round"));
2128 }
2129 }
2130 let dealer_positions: BTreeMap<u32, usize> = self
2131 .dealers
2132 .iter()
2133 .enumerate()
2134 .map(|(idx, &dealer)| (dealer, idx))
2135 .collect();
2136 let previous_successful_round = previous_players.is_some();
2137 for &(after_dealer, missing_dealer) in &self.resume_missing_dealer_msg_fails {
2138 if !self.dealers.contains(&after_dealer) {
2139 return Err(anyhow!("resume_missing dealer {after_dealer} not in round"));
2140 }
2141 if !self.dealers.contains(&missing_dealer) {
2142 return Err(anyhow!(
2143 "resume_missing missing_dealer {missing_dealer} not in round"
2144 ));
2145 }
2146 let after_pos = dealer_positions[&after_dealer];
2147 let missing_pos = dealer_positions[&missing_dealer];
2148 if missing_pos > after_pos {
2149 return Err(anyhow!(
2150 "resume_missing missing_dealer {missing_dealer} appears after {after_dealer}"
2151 ));
2152 }
2153 if self.bad(previous_successful_round, missing_dealer) {
2154 return Err(anyhow!(
2155 "resume_missing_dealer_msg_fails requires dealer {missing_dealer} to be good"
2156 ));
2157 }
2158 let any_valid_ack = self.players.iter().any(|&player| {
2159 let ack_corrupted = self.no_acks.contains(&(missing_dealer, player))
2160 || self.bad_shares.contains(&(missing_dealer, player))
2161 || self
2162 .bad_player_sigs
2163 .get(&(missing_dealer, player))
2164 .is_some_and(Masks::modifies_player_ack);
2165 !ack_corrupted
2166 });
2167 if !any_valid_ack {
2168 return Err(anyhow!(
2169 "resume_missing_dealer_msg_fails requires dealer {missing_dealer} to ack at least one player"
2170 ));
2171 }
2172 }
2173 for &player in &self.finalize_missing_dealer_msg_fails {
2174 if !self.players.contains(&player) {
2175 return Err(anyhow!("finalize_missing player {player} not in round"));
2176 }
2177 }
2178
2179 if let Some(prev_players) = previous_players {
2181 for &d in &self.dealers {
2183 if !prev_players.contains(&d) {
2184 return Err(anyhow!("dealer {d} was not a player in previous round"));
2185 }
2186 }
2187 let required = N3f1::quorum(prev_players.len());
2189 if (self.dealers.len() as u32) < required {
2190 return Err(anyhow!(
2191 "not enough dealers: have {}, need {} (quorum of {} previous players)",
2192 self.dealers.len(),
2193 required,
2194 prev_players.len()
2195 ));
2196 }
2197 }
2198
2199 Ok(())
2200 }
2201
2202 fn bad(&self, previous_successful_round: bool, dealer: u32) -> bool {
2203 if self.replace_shares.contains(&dealer) && previous_successful_round {
2204 return true;
2205 }
2206 if let Some(shift) = self.shift_degrees.get(&dealer) {
2207 let degree = N3f1::quorum(self.players.len()) as i32 - 1;
2208 if (degree + shift.get()).max(0) != degree {
2214 return true;
2215 }
2216 }
2217 if self.bad_reveals.iter().any(|&(d, _)| d == dealer) {
2218 return true;
2219 }
2220 let revealed_players = self
2221 .bad_shares
2222 .iter()
2223 .copied()
2224 .chain(self.no_acks.iter().copied())
2225 .filter_map(|(d, p)| if d == dealer { Some(p) } else { None })
2226 .collect::<BTreeSet<_>>();
2227 revealed_players.len() as u32 > N3f1::max_faults(self.players.len())
2228 }
2229
2230 fn expect_failure(&self, previous_successful_round: Option<u32>) -> bool {
2232 let good_dealer_count = self
2233 .dealers
2234 .iter()
2235 .filter(|&&d| !self.bad(previous_successful_round.is_some(), d))
2236 .count();
2237 let required = previous_successful_round
2238 .map(N3f1::quorum)
2239 .unwrap_or_default()
2240 .max(N3f1::quorum(self.dealers.len())) as usize;
2241 good_dealer_count < required
2242 }
2243 }
2244
2245 #[derive(Debug)]
2247 pub struct Plan {
2248 num_participants: NonZeroU32,
2249 rounds: Vec<Round>,
2250 }
2251
2252 impl Plan {
2253 pub const fn new(num_participants: NonZeroU32) -> Self {
2254 Self {
2255 num_participants,
2256 rounds: Vec::new(),
2257 }
2258 }
2259
2260 pub fn with(mut self, round: Round) -> Self {
2261 self.rounds.push(round);
2262 self
2263 }
2264
2265 pub(crate) fn validate(&self) -> anyhow::Result<()> {
2267 let mut last_successful_players: Option<Vec<u32>> = None;
2268
2269 for round in &self.rounds {
2270 round.validate(
2271 self.num_participants.get(),
2272 last_successful_players.as_deref(),
2273 )?;
2274
2275 if !round.expect_failure(last_successful_players.as_ref().map(|x| x.len() as u32)) {
2277 last_successful_players = Some(round.players.clone());
2278 }
2279 }
2280 Ok(())
2281 }
2282
2283 pub fn run<V: Variant>(self, seed: u64) -> anyhow::Result<()> {
2285 self.validate()?;
2286
2287 let mut rng = StdRng::seed_from_u64(seed);
2288
2289 let keys = (0..self.num_participants.get())
2291 .map(|_| ed25519::PrivateKey::random(&mut rng))
2292 .collect::<Vec<_>>();
2293
2294 let pk_to_key_idx: BTreeMap<ed25519::PublicKey, u32> = keys
2297 .iter()
2298 .enumerate()
2299 .map(|(i, k)| (k.public_key(), i as u32))
2300 .collect();
2301
2302 let max_shift = self
2305 .rounds
2306 .iter()
2307 .flat_map(|r| r.shift_degrees.values())
2308 .map(|s| s.get())
2309 .max()
2310 .unwrap_or(0)
2311 .max(0) as u32;
2312 let max_read_size =
2313 NonZeroU32::new(self.num_participants.get() + max_shift).expect("non-zero");
2314
2315 let mut previous_output: Option<Output<V, ed25519::PublicKey>> = None;
2316 let mut shares: BTreeMap<ed25519::PublicKey, Share> = BTreeMap::new();
2317 let mut threshold_public_key: Option<V::Public> = None;
2318
2319 for (i_round, round) in self.rounds.into_iter().enumerate() {
2320 let previous_successful_round =
2321 previous_output.as_ref().map(|o| o.players.len() as u32);
2322
2323 let dealer_set = round
2324 .dealers
2325 .iter()
2326 .map(|&i| keys[i as usize].public_key())
2327 .try_collect::<Set<_>>()
2328 .unwrap();
2329 let player_set: Set<ed25519::PublicKey> = round
2330 .players
2331 .iter()
2332 .map(|&i| keys[i as usize].public_key())
2333 .try_collect()
2334 .unwrap();
2335
2336 let info = Info::new::<N3f1>(
2338 b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
2339 i_round as u64,
2340 previous_output.clone(),
2341 Default::default(),
2342 dealer_set.clone(),
2343 player_set.clone(),
2344 )?;
2345
2346 let mut players: Map<_, _> = round
2347 .players
2348 .iter()
2349 .map(|&i| {
2350 let sk = keys[i as usize].clone();
2351 let pk = sk.public_key();
2352 let player = Player::new(info.clone(), sk)?;
2353 Ok((pk, player))
2354 })
2355 .collect::<anyhow::Result<Vec<_>>>()?
2356 .try_into()
2357 .unwrap();
2358 let mut acked_dealings: BTreeMap<
2359 ed25519::PublicKey,
2360 Vec<(ed25519::PublicKey, DealerPubMsg<V>, DealerPrivMsg)>,
2361 > = player_set
2362 .iter()
2363 .cloned()
2364 .map(|pk| (pk, Vec::new()))
2365 .collect();
2366 let mut crash_resume_by_dealer: BTreeMap<u32, Vec<u32>> = BTreeMap::new();
2367 for &(after_dealer, player) in &round.crash_resume_players {
2368 crash_resume_by_dealer
2369 .entry(after_dealer)
2370 .or_default()
2371 .push(player);
2372 }
2373 let mut resume_missing_msg_by_dealer: BTreeMap<u32, Vec<u32>> = BTreeMap::new();
2374 for &(after_dealer, missing_dealer) in &round.resume_missing_dealer_msg_fails {
2375 resume_missing_msg_by_dealer
2376 .entry(after_dealer)
2377 .or_default()
2378 .push(missing_dealer);
2379 }
2380
2381 let mut dealer_logs = BTreeMap::new();
2383 for &i_dealer in &round.dealers {
2384 let sk = keys[i_dealer as usize].clone();
2385 let pk = sk.public_key();
2386 let share = match (shares.get(&pk), round.replace_shares.contains(&i_dealer)) {
2387 (None, _) => None,
2388 (Some(s), false) => Some(s.clone()),
2389 (Some(_), true) => Some(Share::new(
2390 Participant::new(i_dealer),
2391 Private::random(&mut rng),
2392 )),
2393 };
2394
2395 let (mut dealer, pub_msg, mut priv_msgs) =
2397 if let Some(shift) = round.shift_degrees.get(&i_dealer) {
2398 let degree = u32::try_from(info.degree::<N3f1>() as i32 + shift.get())
2400 .unwrap_or_default();
2401
2402 let share = info
2404 .unwrap_or_random_share(
2405 &mut rng,
2406 share.map(|s| s.private.expose_unwrap()),
2407 )
2408 .expect("Failed to generate dealer share");
2409
2410 let my_poly = Poly::new_with_constant(&mut rng, degree, share);
2411 let priv_msgs = info
2412 .players
2413 .iter()
2414 .map(|pk| {
2415 (
2416 pk.clone(),
2417 DealerPrivMsg::new(my_poly.eval_msm(
2418 &info.player_scalar(pk).expect("player should exist"),
2419 &Sequential,
2420 )),
2421 )
2422 })
2423 .collect::<Vec<_>>();
2424 let results: Map<_, _> = priv_msgs
2425 .iter()
2426 .map(|(pk, pm)| (pk.clone(), AckOrReveal::Reveal(pm.clone())))
2427 .try_collect()
2428 .unwrap();
2429 let commitment = Poly::commit(my_poly);
2430 let pub_msg = DealerPubMsg { commitment };
2431 let transcript = {
2432 let t = transcript_for_round(&info);
2433 transcript_for_ack(&t, &pk, &pub_msg)
2434 };
2435 let dealer = Dealer {
2436 me: sk.clone(),
2437 info: info.clone(),
2438 pub_msg: pub_msg.clone(),
2439 results,
2440 transcript,
2441 };
2442 (dealer, pub_msg, priv_msgs)
2443 } else {
2444 Dealer::start::<N3f1>(&mut rng, info.clone(), sk.clone(), share)?
2445 };
2446
2447 for (player, priv_msg) in &mut priv_msgs {
2449 let player_key_idx = pk_to_key_idx[player];
2450 if round.bad_shares.contains(&(i_dealer, player_key_idx)) {
2451 *priv_msg = DealerPrivMsg::new(Scalar::random(&mut rng));
2452 }
2453 }
2454 assert_eq!(priv_msgs.len(), players.len());
2455
2456 let mut num_reveals = players.len() as u32;
2458 for (player_pk, priv_msg) in priv_msgs {
2459 assert_eq!(priv_msg, ReadExt::read(&mut priv_msg.encode())?);
2461
2462 let i_player = players
2463 .index(&player_pk)
2464 .ok_or_else(|| anyhow!("unknown player: {:?}", &player_pk))?;
2465 let player_key_idx = pk_to_key_idx[&player_pk];
2466 let player = &mut players.values_mut()[usize::from(i_player)];
2467 let persisted = priv_msg.clone();
2468
2469 let ack =
2470 player.dealer_message::<N3f1>(pk.clone(), pub_msg.clone(), priv_msg);
2471 assert_eq!(ack, ReadExt::read(&mut ack.encode())?);
2472 if let Some(ack) = ack {
2473 acked_dealings
2474 .get_mut(&player_pk)
2475 .expect("player should be present")
2476 .push((pk.clone(), pub_msg.clone(), persisted));
2477 let masks = round
2478 .bad_player_sigs
2479 .get(&(i_dealer, player_key_idx))
2480 .cloned()
2481 .unwrap_or_default();
2482 let (modified, transcript) =
2483 masks.transcript_for_player_ack(&info, &pk, &pub_msg)?;
2484 assert_eq!(transcript.verify(&player_pk, &ack.sig), !modified);
2485
2486 if !round.no_acks.contains(&(i_dealer, player_key_idx)) {
2488 dealer.receive_player_ack(player_pk, ack)?;
2489 num_reveals -= 1;
2490 }
2491 } else {
2492 assert!(
2493 round.bad_shares.contains(&(i_dealer, player_key_idx))
2494 || round.bad(previous_successful_round.is_some(), i_dealer)
2495 );
2496 }
2497 }
2498
2499 let signed_log = dealer.finalize::<N3f1>();
2501 assert_eq!(
2502 signed_log,
2503 Read::read_cfg(&mut signed_log.encode(), &max_read_size)?
2504 );
2505
2506 let masks = round
2508 .bad_dealer_sigs
2509 .get(&i_dealer)
2510 .cloned()
2511 .unwrap_or_default();
2512 let (modified, transcript) =
2513 masks.transcript_for_signed_dealer_log(&info, &signed_log.log)?;
2514 assert_eq!(transcript.verify(&pk, &signed_log.sig), !modified);
2515 let (found_pk, mut log) = signed_log
2516 .check(&info)
2517 .ok_or_else(|| anyhow!("signed log should verify"))?;
2518 assert_eq!(pk, found_pk);
2519 match &mut log.results {
2521 DealerResult::TooManyReveals => {
2522 assert!(num_reveals > info.max_reveals::<N3f1>());
2523 }
2524 DealerResult::Ok(results) => {
2525 assert_eq!(results.len(), players.len());
2526 for &i_player in &round.players {
2527 if !round.bad_reveals.contains(&(i_dealer, i_player)) {
2528 continue;
2529 }
2530 let player_pk = keys[i_player as usize].public_key();
2531 *results
2532 .get_value_mut(&player_pk)
2533 .ok_or_else(|| anyhow!("unknown player: {:?}", &player_pk))? =
2534 AckOrReveal::Reveal(DealerPrivMsg::new(Scalar::random(
2535 &mut rng,
2536 )));
2537 }
2538 }
2539 }
2540 dealer_logs.insert(pk, log);
2541
2542 for &missing_dealer in resume_missing_msg_by_dealer
2545 .get(&i_dealer)
2546 .into_iter()
2547 .flatten()
2548 {
2549 assert!(
2550 !round.bad(previous_successful_round.is_some(), missing_dealer),
2551 "resume_missing_dealer_msg_fails requires dealer {missing_dealer} to be good"
2552 );
2553 let missing_pk = keys[missing_dealer as usize].public_key();
2554 let missing_log = dealer_logs
2555 .get(&missing_pk)
2556 .unwrap_or_else(|| panic!("missing dealer log for {:?}", &missing_pk));
2557 for &i_player in &round.players {
2558 let player_pk = keys[i_player as usize].public_key();
2559 let was_acked = missing_log.get_ack(&player_pk).is_some();
2560
2561 let replay = acked_dealings
2562 .get(&player_pk)
2563 .cloned()
2564 .expect("player should be present");
2565 let replay_without = replay
2566 .into_iter()
2567 .filter(|(dealer, _, _)| dealer != &missing_pk);
2568 let player_sk = keys[i_player as usize].clone();
2569 let resumed = Player::resume::<N3f1>(
2570 info.clone(),
2571 player_sk,
2572 &dealer_logs,
2573 replay_without,
2574 );
2575 if was_acked {
2576 assert!(
2577 matches!(resumed, Err(Error::MissingPlayerDealing)),
2578 "resume without dealer {missing_dealer} message should report MissingPlayerDealing for player {i_player}"
2579 );
2580 } else {
2581 assert!(
2582 resumed.is_ok(),
2583 "resume without dealer {missing_dealer} message should succeed for unacked player {i_player}"
2584 );
2585 }
2586 }
2587 }
2588
2589 for &i_player in crash_resume_by_dealer.get(&i_dealer).into_iter().flatten() {
2591 let player_pk = keys[i_player as usize].public_key();
2592 let player_sk = keys[i_player as usize].clone();
2593 let replay = acked_dealings
2594 .get(&player_pk)
2595 .cloned()
2596 .expect("player should be present");
2597 let (resumed, _) =
2598 Player::resume::<N3f1>(info.clone(), player_sk, &dealer_logs, replay)
2599 .expect("player resume perturbation should succeed");
2600 *players
2601 .get_value_mut(&player_pk)
2602 .expect("player should be present") = resumed;
2603 }
2604 }
2605
2606 let mut logs = Logs::<_, _, N3f1>::new(info.clone());
2608 for (dealer, log) in &dealer_logs {
2609 logs.record(dealer.clone(), log.clone());
2610 }
2611 let selection = logs.clone().select::<ed25519::Batch>(&mut rng, &Sequential);
2612 if let Ok(ref selection) = selection {
2613 let good_pks = selection
2614 .1
2615 .iter_pairs()
2616 .map(|(pk, _)| pk.clone())
2617 .collect::<BTreeSet<_>>();
2618 for &i_dealer in &round.dealers {
2619 if round.bad(previous_successful_round.is_some(), i_dealer) {
2620 assert!(!good_pks.contains(&keys[i_dealer as usize].public_key()));
2621 }
2622 }
2623 }
2624 let observe_result =
2626 observe::<_, _, N3f1, ed25519::Batch>(&mut rng, logs.clone(), &Sequential);
2627 if round.expect_failure(previous_successful_round) {
2628 assert!(
2629 observe_result.is_err(),
2630 "Round {i_round} should have failed but succeeded",
2631 );
2632 continue;
2633 }
2634 let observer_output = observe_result?;
2635 let selection = selection.expect("select should succeed if observe succeeded");
2636
2637 let required_commitments = info.required_commitments::<N3f1>() as usize;
2640 let expected_dealers: Set<ed25519::PublicKey> = dealer_set
2641 .iter()
2642 .filter(|pk| {
2643 let i = keys.iter().position(|k| &k.public_key() == *pk).unwrap() as u32;
2644 !round.bad(previous_successful_round.is_some(), i)
2645 })
2646 .take(required_commitments)
2647 .cloned()
2648 .try_collect()
2649 .expect("dealers are unique");
2650 let expected_dealer_indices: BTreeSet<u32> = expected_dealers
2651 .iter()
2652 .filter_map(|pk| {
2653 keys.iter()
2654 .position(|k| &k.public_key() == pk)
2655 .map(|i| i as u32)
2656 })
2657 .collect();
2658 assert_eq!(
2659 observer_output.dealers(),
2660 &expected_dealers,
2661 "Output dealers should match expected good dealers"
2662 );
2663
2664 let selected_dealers: BTreeSet<u32> = selection
2666 .1
2667 .keys()
2668 .iter()
2669 .filter_map(|pk| {
2670 keys.iter()
2671 .position(|k| &k.public_key() == pk)
2672 .map(|i| i as u32)
2673 })
2674 .collect();
2675 assert_eq!(
2676 selected_dealers, expected_dealer_indices,
2677 "Selection should match expected dealers"
2678 );
2679 let selected_players: Set<ed25519::PublicKey> = round
2680 .players
2681 .iter()
2682 .map(|&i| keys[i as usize].public_key())
2683 .try_collect()
2684 .expect("players are unique");
2685 for &i_player in &round.finalize_missing_dealer_msg_fails {
2686 let player_pk = keys[i_player as usize].public_key();
2687 let player_sk = keys[i_player as usize].clone();
2688 let mut tested = 0u32;
2689 for &dealer_idx in &selected_dealers {
2690 if round.bad(previous_successful_round.is_some(), dealer_idx) {
2691 continue;
2692 }
2693 let dealer_pk = keys[dealer_idx as usize].public_key();
2694 let dealer_log = dealer_logs
2695 .get(&dealer_pk)
2696 .unwrap_or_else(|| panic!("missing dealer log for {:?}", &dealer_pk));
2697 if dealer_log.get_ack(&player_pk).is_none() {
2698 continue;
2699 }
2700 let replay = acked_dealings
2701 .get(&player_pk)
2702 .cloned()
2703 .expect("player should be present");
2704 let replay_without = replay
2705 .into_iter()
2706 .filter(|(dealer, _, _)| dealer != &dealer_pk);
2707 let resume_logs: BTreeMap<_, _> = dealer_logs
2708 .iter()
2709 .filter(|(dealer, _)| *dealer != &dealer_pk)
2710 .map(|(dealer, log)| (dealer.clone(), log.clone()))
2711 .collect();
2712 let (resumed, _) = Player::resume::<N3f1>(
2713 info.clone(),
2714 player_sk.clone(),
2715 &resume_logs,
2716 replay_without,
2717 )
2718 .expect("resume should succeed with stale logs");
2719 let finalize_res = resumed.finalize::<N3f1, ed25519::Batch>(
2720 &mut rng,
2721 logs.clone(),
2722 &Sequential,
2723 );
2724 assert!(
2725 matches!(finalize_res, Err(Error::MissingPlayerDealing)),
2726 "finalize without dealer {dealer_idx} message should return MissingPlayerDealing for player {i_player}"
2727 );
2728 tested += 1;
2729 }
2730 assert!(
2731 tested > 0,
2732 "finalize_missing_dealer_msg_fails for player {i_player} tested no dealers"
2733 );
2734 }
2735
2736 let mut expected_reveals: BTreeMap<ed25519::PublicKey, u32> = BTreeMap::new();
2742 for &(dealer_idx, player_key_idx) in round.no_acks.union(&round.bad_shares) {
2743 if !selected_dealers.contains(&dealer_idx) {
2744 continue;
2745 }
2746 let pk = keys[player_key_idx as usize].public_key();
2747 if selected_players.position(&pk).is_none() {
2748 continue;
2749 }
2750 *expected_reveals.entry(pk).or_insert(0) += 1;
2751 }
2752
2753 let max_faults = selected_players.max_faults::<N3f1>();
2755 for player in player_set.iter() {
2756 let expected = expected_reveals.get(player).copied().unwrap_or(0) > max_faults;
2757 let actual = observer_output.revealed().position(player).is_some();
2758 assert_eq!(expected, actual, "Unexpected outcome for player {player:?} (expected={expected}, actual={actual})");
2759 }
2760
2761 for (player_pk, player) in players.into_iter() {
2763 let (player_output, share) = player
2764 .finalize::<N3f1, ed25519::Batch>(&mut rng, logs.clone(), &Sequential)
2765 .expect("Player finalize should succeed");
2766
2767 assert_eq!(
2768 player_output, observer_output,
2769 "Player output should match observer output"
2770 );
2771
2772 let expected_public = observer_output
2774 .public
2775 .partial_public(share.index)
2776 .expect("share index should be valid");
2777 let actual_public = share.public::<V>();
2778 assert_eq!(
2779 expected_public, actual_public,
2780 "Share should match public polynomial"
2781 );
2782
2783 shares.insert(player_pk.clone(), share);
2784 }
2785
2786 let current_public = *observer_output.public().public();
2788 match threshold_public_key {
2789 None => threshold_public_key = Some(current_public),
2790 Some(tpk) => {
2791 assert_eq!(
2792 tpk, current_public,
2793 "Public key should remain constant across reshares"
2794 );
2795 }
2796 }
2797
2798 let test_message = format!("test message round {i_round}").into_bytes();
2800 let namespace = b"test";
2801
2802 let mut partial_sigs = Vec::new();
2803 for &i_player in &round.players {
2804 let share = &shares[&keys[i_player as usize].public_key()];
2805 let partial_sig = threshold::sign_message::<V>(share, namespace, &test_message);
2806
2807 threshold::verify_message::<V>(
2808 &observer_output.public,
2809 namespace,
2810 &test_message,
2811 &partial_sig,
2812 )
2813 .expect("Partial signature verification should succeed");
2814
2815 partial_sigs.push(partial_sig);
2816 }
2817
2818 let threshold = observer_output.quorum::<N3f1>();
2819 let threshold_sig = threshold::recover::<V, _, N3f1>(
2820 &observer_output.public,
2821 &partial_sigs[0..threshold as usize],
2822 &Sequential,
2823 )
2824 .expect("Should recover threshold signature");
2825
2826 ops::verify_message::<V>(
2828 threshold_public_key.as_ref().unwrap(),
2829 namespace,
2830 &test_message,
2831 &threshold_sig,
2832 )
2833 .expect("Threshold signature verification should succeed");
2834
2835 previous_output = Some(observer_output);
2837 }
2838 Ok(())
2839 }
2840 }
2841
2842 #[cfg(feature = "arbitrary")]
2843 mod impl_arbitrary {
2844 use super::*;
2845 use arbitrary::{Arbitrary, Unstructured};
2846 use core::ops::ControlFlow;
2847
2848 const MAX_NUM_PARTICIPANTS: u32 = 20;
2849 const MAX_ROUNDS: u32 = 10;
2850
2851 fn arbitrary_masks<'a>(u: &mut Unstructured<'a>) -> arbitrary::Result<Masks> {
2852 Ok(Masks {
2853 info_summary: Arbitrary::arbitrary(u)?,
2854 dealer: Arbitrary::arbitrary(u)?,
2855 pub_msg: Arbitrary::arbitrary(u)?,
2856 log: Arbitrary::arbitrary(u)?,
2857 })
2858 }
2859
2860 fn pick<'a, T>(
2865 u: &mut Unstructured<'a>,
2866 num: usize,
2867 mut data: Vec<T>,
2868 ) -> arbitrary::Result<Vec<T>> {
2869 let len = data.len();
2870 let num = num.min(len);
2871 for start in 0..num {
2873 data.swap(start, u.int_in_range(start..=len - 1)?);
2874 }
2875 data.truncate(num);
2876 Ok(data)
2877 }
2878
2879 fn arbitrary_round<'a>(
2880 u: &mut Unstructured<'a>,
2881 num_participants: u32,
2882 last_successful_players: Option<&Set<u32>>,
2883 ) -> arbitrary::Result<Round> {
2884 let dealers = if let Some(players) = last_successful_players {
2885 let to_pick = u.int_in_range(players.quorum::<N3f1>() as usize..=players.len())?;
2886 pick(u, to_pick, players.into_iter().copied().collect())?
2887 } else {
2888 let to_pick = u.int_in_range(1..=num_participants as usize)?;
2889 pick(u, to_pick, (0..num_participants).collect())?
2890 };
2891 let players = {
2892 let to_pick = u.int_in_range(1..=num_participants as usize)?;
2893 pick(u, to_pick, (0..num_participants).collect())?
2894 };
2895 let pairs = dealers
2896 .iter()
2897 .flat_map(|d| players.iter().map(|p| (*d, *p)))
2898 .collect::<Vec<_>>();
2899 let pick_pair_set = |u: &mut Unstructured<'a>| {
2900 let num = u.int_in_range(0..=pairs.len())?;
2901 if num == 0 {
2902 return Ok(BTreeSet::new());
2903 }
2904 Ok(pick(u, num, pairs.clone())?.into_iter().collect())
2905 };
2906 let pick_dealer_set = |u: &mut Unstructured<'a>| {
2907 let num = u.int_in_range(0..=dealers.len())?;
2908 if num == 0 {
2909 return Ok(BTreeSet::new());
2910 }
2911 Ok(pick(u, num, dealers.clone())?.into_iter().collect())
2912 };
2913 let round = Round {
2914 crash_resume_players: BTreeSet::new(),
2915 resume_missing_dealer_msg_fails: BTreeSet::new(),
2916 finalize_missing_dealer_msg_fails: BTreeSet::new(),
2917 no_acks: pick_pair_set(u)?,
2918 bad_shares: pick_pair_set(u)?,
2919 bad_player_sigs: {
2920 let indices = pick_pair_set(u)?;
2921 indices
2922 .into_iter()
2923 .map(|k| Ok((k, arbitrary_masks(u)?)))
2924 .collect::<arbitrary::Result<_>>()?
2925 },
2926 bad_reveals: pick_pair_set(u)?,
2927 bad_dealer_sigs: {
2928 let indices = pick_dealer_set(u)?;
2929 indices
2930 .into_iter()
2931 .map(|k| Ok((k, arbitrary_masks(u)?)))
2932 .collect::<arbitrary::Result<_>>()?
2933 },
2934 replace_shares: pick_dealer_set(u)?,
2935 shift_degrees: {
2936 let indices = pick_dealer_set(u)?;
2937 indices
2938 .into_iter()
2939 .map(|k| {
2940 let expected = N3f1::quorum(players.len()) as i32 - 1;
2941 let shift = u.int_in_range(1..=expected.max(1))?;
2942 let shift = if bool::arbitrary(u)? { -shift } else { shift };
2943 Ok((k, NonZeroI32::new(shift).expect("checked to not be zero")))
2944 })
2945 .collect::<arbitrary::Result<_>>()?
2946 },
2947 dealers,
2948 players,
2949 };
2950 Ok(round)
2951 }
2952
2953 impl<'a> Arbitrary<'a> for Plan {
2954 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
2955 let num_participants = u.int_in_range(1..=MAX_NUM_PARTICIPANTS)?;
2956 let mut rounds = Vec::new();
2957 let mut last_successful_players: Option<Set<u32>> = None;
2958 u.arbitrary_loop(None, Some(MAX_ROUNDS), |u| {
2959 let round =
2960 arbitrary_round(u, num_participants, last_successful_players.as_ref())?;
2961 if !round
2962 .expect_failure(last_successful_players.as_ref().map(|x| x.len() as u32))
2963 {
2964 last_successful_players =
2965 Some(round.players.iter().copied().try_collect().unwrap());
2966 }
2967 rounds.push(round);
2968 Ok(ControlFlow::Continue(()))
2969 })?;
2970 let plan = Self {
2971 num_participants: NZU32!(num_participants),
2972 rounds,
2973 };
2974 plan.validate()
2975 .map_err(|_| arbitrary::Error::IncorrectFormat)?;
2976 Ok(plan)
2977 }
2978 }
2979 }
2980}
2981
2982#[cfg(feature = "arbitrary")]
2983pub use test_plan::Plan as FuzzPlan;
2984
2985#[cfg(test)]
2986mod test {
2987 use super::{test_plan::*, *};
2988 use crate::{bls12381::primitives::variant::MinPk, ed25519};
2989 use anyhow::anyhow;
2990 use arbitrary::{Arbitrary, Unstructured};
2991 use commonware_invariants::minifuzz;
2992 use commonware_math::algebra::Random;
2993 use commonware_utils::{test_rng, test_rng_seeded, Faults, N3f1};
2994 use core::num::NonZeroI32;
2995
2996 const PRE_VERIFY_DEALERS: usize = 8;
2997 type PreVerifyLog = DealerLog<MinPk, ed25519::PublicKey>;
2998 type PreVerifyLogs = Logs<MinPk, ed25519::PublicKey, QuorumTwo>;
2999
3000 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
3001 struct QuorumTwo;
3002
3003 impl Faults for QuorumTwo {
3004 fn max_faults(n: impl num_traits::ToPrimitive) -> u32 {
3005 let n = n
3006 .to_u32()
3007 .expect("n must be a non-negative integer that fits in u32");
3008 assert!(n >= 2, "n must be at least 2");
3009 n - 2
3010 }
3011 }
3012
3013 struct PreVerifyDealer {
3014 key: ed25519::PublicKey,
3015 valid: PreVerifyLog,
3016 invalid: PreVerifyLog,
3017 }
3018
3019 struct PreVerifyFixture {
3020 info: Info<MinPk, ed25519::PublicKey>,
3021 wrong_info: Info<MinPk, ed25519::PublicKey>,
3022 dealers: Vec<PreVerifyDealer>,
3023 }
3024
3025 impl PreVerifyFixture {
3026 fn new() -> Self {
3027 fn pre_verify_test_keys() -> Vec<ed25519::PrivateKey> {
3028 (0..PRE_VERIFY_DEALERS as u64)
3029 .map(ed25519::PrivateKey::from_seed)
3030 .collect()
3031 }
3032
3033 fn pre_verify_test_info(
3034 keys: &[ed25519::PrivateKey],
3035 round: u64,
3036 ) -> Info<MinPk, ed25519::PublicKey> {
3037 let dealers: Set<_> = keys
3038 .iter()
3039 .map(|sk| sk.public_key())
3040 .try_collect()
3041 .expect("dealers must be unique");
3042 Info::<MinPk, _>::new::<QuorumTwo>(
3043 b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3044 round,
3045 None,
3046 Default::default(),
3047 dealers.clone(),
3048 dealers,
3049 )
3050 .expect("info must be valid")
3051 }
3052
3053 fn generate_dealer_log(
3054 info: &Info<MinPk, ed25519::PublicKey>,
3055 keys: &[ed25519::PrivateKey],
3056 dealer_index: usize,
3057 seed: u64,
3058 ) -> DealerLog<MinPk, ed25519::PublicKey> {
3059 let mut players: BTreeMap<_, _> = keys
3060 .iter()
3061 .cloned()
3062 .map(|sk| {
3063 let pk = sk.public_key();
3064 (
3065 pk,
3066 Player::<MinPk, _>::new(info.clone(), sk)
3067 .expect("player initialization must succeed"),
3068 )
3069 })
3070 .collect();
3071
3072 let dealer_sk = keys[dealer_index].clone();
3073 let dealer_pk = dealer_sk.public_key();
3074 let mut rng = test_rng_seeded(seed);
3075 let (mut dealer, pub_msg, priv_msgs) =
3076 Dealer::start::<QuorumTwo>(&mut rng, info.clone(), dealer_sk, None)
3077 .expect("dealer initialization must succeed");
3078 for (player_pk, priv_msg) in priv_msgs {
3079 let ack = players
3080 .get_mut(&player_pk)
3081 .expect("player should exist")
3082 .dealer_message::<QuorumTwo>(dealer_pk.clone(), pub_msg.clone(), priv_msg)
3083 .expect("dealer message must succeed");
3084 dealer
3085 .receive_player_ack(player_pk, ack)
3086 .expect("ack handling must succeed");
3087 }
3088 dealer
3089 .finalize::<QuorumTwo>()
3090 .check(info)
3091 .expect("signed dealer log must verify against its own info")
3092 .1
3093 }
3094
3095 let keys = pre_verify_test_keys();
3096 let info = pre_verify_test_info(&keys, 0);
3097 let wrong_info = pre_verify_test_info(&keys, 1);
3098 let mut logs_by_key: BTreeMap<_, _> = keys
3099 .iter()
3100 .enumerate()
3101 .map(|(dealer_index, sk)| {
3102 let key = sk.public_key();
3103 let seed = dealer_index as u64;
3104 let valid = generate_dealer_log(&info, &keys, dealer_index, seed);
3105 let invalid = generate_dealer_log(&wrong_info, &keys, dealer_index, seed);
3106 assert_eq!(
3107 valid.pub_msg, invalid.pub_msg,
3108 "wrong-info log generation should only change transcript-bound signatures"
3109 );
3110 (key, (valid, invalid))
3111 })
3112 .collect();
3113 let dealers = info
3114 .dealers
3115 .iter()
3116 .cloned()
3117 .map(|key| {
3118 let (valid, invalid) = logs_by_key
3119 .remove(&key)
3120 .expect("fixture should include every dealer");
3121 PreVerifyDealer {
3122 key,
3123 valid,
3124 invalid,
3125 }
3126 })
3127 .collect();
3128 Self {
3129 info,
3130 wrong_info,
3131 dealers,
3132 }
3133 }
3134
3135 fn required_commitments(&self) -> usize {
3136 self.info.required_commitments::<QuorumTwo>() as usize
3137 }
3138
3139 fn expected(&self, valid: &[bool]) -> Set<ed25519::PublicKey> {
3140 assert_eq!(
3141 valid.len(),
3142 self.dealers.len(),
3143 "fixture size should match case"
3144 );
3145 self.dealers
3146 .iter()
3147 .zip(valid.iter().copied())
3148 .filter(|(_, is_valid)| *is_valid)
3149 .take(self.required_commitments())
3150 .map(|(dealer, _)| dealer.key.clone())
3151 .try_collect()
3152 .expect("dealers must be unique")
3153 }
3154
3155 fn record(&self, logs: &mut PreVerifyLogs, dealer_index: usize, is_valid: bool) {
3156 let dealer = &self.dealers[dealer_index];
3157 let log = if is_valid {
3158 dealer.valid.clone()
3159 } else {
3160 dealer.invalid.clone()
3161 };
3162 logs.record(dealer.key.clone(), log);
3163 }
3164
3165 fn logs_for(
3166 &self,
3167 info: &Info<MinPk, ed25519::PublicKey>,
3168 valid: &[bool],
3169 ) -> PreVerifyLogs {
3170 assert_eq!(
3171 valid.len(),
3172 self.dealers.len(),
3173 "fixture size should match case"
3174 );
3175 let mut logs = PreVerifyLogs::new(info.clone());
3176 for (dealer_index, &is_valid) in valid.iter().enumerate() {
3177 self.record(&mut logs, dealer_index, is_valid);
3178 }
3179 logs
3180 }
3181 }
3182
3183 #[derive(Debug)]
3184 struct IncrementalPreVerifyCase {
3185 valid: [bool; PRE_VERIFY_DEALERS],
3186 batches: Vec<Vec<usize>>,
3187 }
3188
3189 impl<'a> Arbitrary<'a> for IncrementalPreVerifyCase {
3190 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
3191 let mut valid = [false; PRE_VERIFY_DEALERS];
3192 for is_valid in &mut valid {
3193 *is_valid = u.arbitrary()?;
3194 }
3195
3196 let mut order: Vec<_> = (0..valid.len()).collect();
3197 for index in 0..valid.len() {
3198 let last = order.len() - 1;
3199 order.swap(index, u.int_in_range(index..=last)?);
3200 }
3201
3202 let mut batches = Vec::new();
3203 let mut start = 0;
3204 while start < order.len() {
3205 let batch_size = u.int_in_range(1..=order.len() - start)?;
3206 batches.push(order[start..start + batch_size].to_vec());
3207 start += batch_size;
3208 }
3209
3210 Ok(Self { valid, batches })
3211 }
3212 }
3213
3214 impl IncrementalPreVerifyCase {
3215 fn run(self, fixture: &PreVerifyFixture) -> arbitrary::Result<()> {
3216 let required_commitments = fixture.required_commitments();
3217 let expected = fixture.expected(&self.valid);
3218 let fresh = fixture.logs_for(&fixture.info, &self.valid);
3219
3220 let mut incremental = PreVerifyLogs::new(fixture.info.clone());
3221 let mut incremental_rng = test_rng();
3222 for batch in &self.batches {
3223 for &dealer_index in batch {
3224 fixture.record(&mut incremental, dealer_index, self.valid[dealer_index]);
3225 }
3226 incremental.pre_verify::<ed25519::Batch>(&mut incremental_rng, &Sequential);
3227 }
3228
3229 let mut fresh_rng = test_rng();
3230 let fresh_selected = fresh
3231 .select::<ed25519::Batch>(&mut fresh_rng, &Sequential)
3232 .map(|(_, selection)| selection.keys().clone());
3233 let incremental_selected = incremental
3234 .select::<ed25519::Batch>(&mut incremental_rng, &Sequential)
3235 .map(|(_, selection)| selection.keys().clone());
3236
3237 match &fresh_selected {
3238 Ok(selected) => assert_eq!(
3239 selected, &expected,
3240 "all-at-once selection disagreed with validity mask: {:?}",
3241 self
3242 ),
3243 Err(_) => assert!(
3244 expected.len() < required_commitments,
3245 "all-at-once selection failed despite quorum-sized expected set: {:?}",
3246 self
3247 ),
3248 }
3249
3250 match (fresh_selected, incremental_selected) {
3251 (Err(_), Err(_)) => {}
3252 (Ok(fresh_selected), Ok(incremental_selected)) => assert_eq!(
3253 incremental_selected, fresh_selected,
3254 "incremental selection disagreed with all-at-once selection: {:?}",
3255 self
3256 ),
3257 (Ok(fresh_selected), Err(err)) => panic!(
3258 "incremental selection failed with {err:?} but all-at-once selected {fresh_selected:?}: {self:?}"
3259 ),
3260 (Err(err), Ok(incremental_selected)) => panic!(
3261 "incremental selection returned {incremental_selected:?} but all-at-once failed with {err:?}: {self:?}"
3262 ),
3263 }
3264
3265 Ok(())
3266 }
3267 }
3268
3269 #[test]
3270 fn incremental_pre_verify_preserves_dealer_order() {
3271 let fixture = PreVerifyFixture::new();
3272 minifuzz::test(move |u| u.arbitrary::<IncrementalPreVerifyCase>()?.run(&fixture));
3273 }
3274
3275 #[test]
3276 fn logs_are_bound_to_constructor_info() {
3277 let fixture = PreVerifyFixture::new();
3278 let mut logs = fixture.logs_for(&fixture.info, &[false; PRE_VERIFY_DEALERS]);
3279 let mut wrong_logs = fixture.logs_for(&fixture.wrong_info, &[false; PRE_VERIFY_DEALERS]);
3280 let mut rng = test_rng();
3281
3282 logs.pre_verify::<ed25519::Batch>(&mut rng, &Sequential);
3283 wrong_logs.pre_verify::<ed25519::Batch>(&mut rng, &Sequential);
3284 assert!(
3285 wrong_logs
3286 .select::<ed25519::Batch>(&mut rng, &Sequential)
3287 .is_ok(),
3288 "control check: logs should verify when bound to the round they were created for"
3289 );
3290 assert!(
3291 matches!(
3292 logs.select::<ed25519::Batch>(&mut rng, &Sequential),
3293 Err(Error::DkgFailed)
3294 ),
3295 "logs bound to a different round must reject these transcript-bound signatures"
3296 );
3297 }
3298
3299 #[test]
3300 fn finalize_rejects_logs_bound_to_different_round() {
3301 let fixture = PreVerifyFixture::new();
3302 let player =
3303 Player::<MinPk, _>::new(fixture.info.clone(), ed25519::PrivateKey::from_seed(0))
3304 .expect("player initialization must succeed");
3305 let wrong_logs = fixture.logs_for(&fixture.wrong_info, &[false; PRE_VERIFY_DEALERS]);
3306
3307 let result =
3308 player.finalize::<QuorumTwo, ed25519::Batch>(&mut test_rng(), wrong_logs, &Sequential);
3309
3310 assert!(
3311 matches!(result, Err(Error::MismatchedLogs)),
3312 "finalize should reject logs bound to a different round"
3313 );
3314 }
3315
3316 #[test]
3317 fn single_round() -> anyhow::Result<()> {
3318 Plan::new(NZU32!(4))
3319 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
3320 .run::<MinPk>(0)
3321 }
3322
3323 #[test]
3324 fn multiple_rounds() -> anyhow::Result<()> {
3325 Plan::new(NZU32!(4))
3326 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
3327 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
3328 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
3329 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
3330 .run::<MinPk>(0)
3331 }
3332
3333 #[test]
3334 fn player_crash_resume_after_dealer() -> anyhow::Result<()> {
3335 Plan::new(NZU32!(4))
3336 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).crash_resume_player(1, 2))
3337 .run::<MinPk>(0)
3338 }
3339
3340 #[test]
3341 fn resume_missing_good_dealer_message_fails_after_checkpoint() -> anyhow::Result<()> {
3342 Plan::new(NZU32!(4))
3343 .with(
3344 Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
3345 .resume_missing_dealer_msg_fails(2, 1),
3346 )
3347 .run::<MinPk>(0)
3348 }
3349
3350 #[test]
3351 fn resume_missing_good_dealer_message_skips_unacked_players() -> anyhow::Result<()> {
3352 Plan::new(NZU32!(4))
3353 .with(
3354 Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
3355 .no_ack(1, 0)
3356 .resume_missing_dealer_msg_fails(2, 1),
3357 )
3358 .run::<MinPk>(0)
3359 }
3360
3361 #[test]
3362 fn finalize_fails_after_resume_without_good_dealer_message() -> anyhow::Result<()> {
3363 Plan::new(NZU32!(4))
3364 .with(
3365 Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
3366 .no_ack(0, 1)
3367 .finalize_missing_dealer_msg_fails(0),
3368 )
3369 .run::<MinPk>(0)
3370 }
3371
3372 #[test]
3373 fn invalid_checkpoint_configs_fail_validation() {
3374 assert!(Plan::new(NZU32!(4))
3375 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).crash_resume_player(4, 2))
3376 .validate()
3377 .is_err());
3378 assert!(Plan::new(NZU32!(4))
3379 .with(
3380 Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
3381 .resume_missing_dealer_msg_fails(1, 2),
3382 )
3383 .validate()
3384 .is_err());
3385 assert!(Plan::new(NZU32!(4))
3386 .with(
3387 Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
3388 .bad_reveal(1, 0)
3389 .resume_missing_dealer_msg_fails(2, 1),
3390 )
3391 .validate()
3392 .is_err());
3393 }
3394
3395 #[test]
3396 fn changing_committee() -> anyhow::Result<()> {
3397 Plan::new(NonZeroU32::new(5).unwrap())
3398 .with(Round::new(vec![0, 1, 2], vec![1, 2, 3]))
3399 .with(Round::new(vec![1, 2, 3], vec![2, 3, 4]))
3400 .with(Round::new(vec![2, 3, 4], vec![3, 4, 0]))
3401 .with(Round::new(vec![3, 4, 0], vec![4, 0, 1]))
3402 .run::<MinPk>(0)
3403 }
3404
3405 #[test]
3406 fn missing_ack() -> anyhow::Result<()> {
3407 Plan::new(NonZeroU32::new(4).unwrap())
3409 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 0))
3410 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 1))
3411 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 2))
3412 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 3))
3413 .run::<MinPk>(0)
3414 }
3415
3416 #[test]
3417 fn increasing_decreasing_committee() -> anyhow::Result<()> {
3418 Plan::new(NonZeroU32::new(5).unwrap())
3419 .with(Round::new(vec![0, 1], vec![0, 1, 2]))
3420 .with(Round::new(vec![0, 1, 2], vec![0, 1, 2, 3]))
3421 .with(Round::new(vec![0, 1, 2], vec![0, 1]))
3422 .with(Round::new(vec![0, 1], vec![0, 1, 2, 3, 4]))
3423 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1]))
3424 .run::<MinPk>(0)
3425 }
3426
3427 #[test]
3428 fn bad_reveal_fails() -> anyhow::Result<()> {
3429 Plan::new(NonZeroU32::new(4).unwrap())
3430 .with(Round::new(vec![0], vec![0, 1, 2, 3]).bad_reveal(0, 1))
3431 .run::<MinPk>(0)
3432 }
3433
3434 #[test]
3435 fn bad_share() -> anyhow::Result<()> {
3436 Plan::new(NonZeroU32::new(4).unwrap())
3437 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).bad_share(0, 1))
3438 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).bad_share(0, 2))
3439 .run::<MinPk>(0)
3440 }
3441
3442 #[test]
3443 fn shift_degree_fails() -> anyhow::Result<()> {
3444 Plan::new(NonZeroU32::new(4).unwrap())
3445 .with(Round::new(vec![0], vec![0, 1, 2, 3]).shift_degree(
3446 0,
3447 NonZeroI32::new(1).ok_or_else(|| anyhow!("invalid NZI32"))?,
3448 ))
3449 .run::<MinPk>(0)
3450 }
3451
3452 #[test]
3453 fn replace_share_fails() -> anyhow::Result<()> {
3454 Plan::new(NonZeroU32::new(4).unwrap())
3455 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
3456 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).replace_share(0))
3457 .run::<MinPk>(0)
3458 }
3459
3460 #[test]
3461 fn too_many_reveals_dealer() -> anyhow::Result<()> {
3462 Plan::new(NonZeroU32::new(4).unwrap())
3463 .with(
3464 Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
3465 .no_ack(0, 0)
3466 .no_ack(0, 1),
3467 )
3468 .run::<MinPk>(0)
3469 }
3470
3471 #[test]
3472 fn too_many_reveals_player() -> anyhow::Result<()> {
3473 Plan::new(NonZeroU32::new(4).unwrap())
3474 .with(
3475 Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
3476 .no_ack(0, 0)
3477 .no_ack(1, 0)
3478 .no_ack(3, 0),
3479 )
3480 .run::<MinPk>(0)
3481 }
3482
3483 #[test]
3484 fn bad_sigs() -> anyhow::Result<()> {
3485 Plan::new(NonZeroU32::new(4).unwrap())
3486 .with(
3487 Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
3488 .bad_dealer_sig(
3489 0,
3490 Masks {
3491 log: vec![0xFF; 8],
3492 ..Default::default()
3493 },
3494 )
3495 .bad_player_sig(
3496 0,
3497 1,
3498 Masks {
3499 pub_msg: vec![0xFF; 8],
3500 ..Default::default()
3501 },
3502 ),
3503 )
3504 .run::<MinPk>(0)
3505 }
3506
3507 #[test]
3508 fn issue_2745_regression() -> anyhow::Result<()> {
3509 Plan::new(NonZeroU32::new(6).unwrap())
3510 .with(
3511 Round::new(vec![0], vec![5, 1, 3, 0, 4])
3512 .no_ack(0, 5)
3513 .bad_share(0, 5),
3514 )
3515 .with(Round::new(vec![0, 1, 3, 4], vec![0]))
3516 .with(Round::new(vec![0], vec![0]))
3517 .run::<MinPk>(0)
3518 }
3519
3520 #[test]
3521 fn signed_dealer_log_commitment() -> Result<(), Error> {
3522 let sk = ed25519::PrivateKey::from_seed(0);
3523 let pk = sk.public_key();
3524 let info = Info::<MinPk, _>::new::<N3f1>(
3525 b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3526 0,
3527 None,
3528 Default::default(),
3529 vec![sk.public_key()].try_into().unwrap(),
3530 vec![sk.public_key()].try_into().unwrap(),
3531 )?;
3532 let mut log0 = {
3533 let (dealer, _, _) =
3534 Dealer::start::<N3f1>(&mut test_rng(), info.clone(), sk.clone(), None)?;
3535 dealer.finalize::<N3f1>()
3536 };
3537 let mut log1 = {
3538 let (mut dealer, pub_msg, priv_msgs) =
3539 Dealer::start::<N3f1>(&mut test_rng(), info.clone(), sk.clone(), None)?;
3540 let mut player = Player::new(info.clone(), sk)?;
3541 let ack = player
3542 .dealer_message::<N3f1>(pk.clone(), pub_msg, priv_msgs[0].1.clone())
3543 .unwrap();
3544 dealer.receive_player_ack(pk, ack)?;
3545 dealer.finalize::<N3f1>()
3546 };
3547 std::mem::swap(&mut log0.log, &mut log1.log);
3548 assert!(log0.check(&info).is_none());
3549 assert!(log1.check(&info).is_none());
3550
3551 Ok(())
3552 }
3553
3554 #[test]
3555 fn info_with_different_mode_is_not_equal() -> Result<(), Error> {
3556 let sk = ed25519::PrivateKey::from_seed(0);
3557 let pk = sk.public_key();
3558 let dealers: Set<ed25519::PublicKey> = vec![pk.clone()].try_into().unwrap();
3559 let players: Set<ed25519::PublicKey> = vec![pk].try_into().unwrap();
3560
3561 let default_mode_info = Info::<MinPk, _>::new::<N3f1>(
3562 b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3563 0,
3564 None,
3565 Mode::default(),
3566 dealers.clone(),
3567 players.clone(),
3568 )?;
3569 let roots_of_unity_mode_info = Info::<MinPk, _>::new::<N3f1>(
3570 b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3571 0,
3572 None,
3573 Mode::RootsOfUnity,
3574 dealers,
3575 players,
3576 )?;
3577
3578 assert_ne!(default_mode_info, roots_of_unity_mode_info);
3579 Ok(())
3580 }
3581
3582 #[test]
3583 fn resume_ignores_invalid_logged_ack_signature() -> Result<(), Error> {
3584 let dealer_sk = ed25519::PrivateKey::from_seed(11);
3585 let dealer_pk = dealer_sk.public_key();
3586 let player_sk = ed25519::PrivateKey::from_seed(22);
3587 let player_pk = player_sk.public_key();
3588 let dealers: Set<ed25519::PublicKey> = vec![dealer_pk.clone()].try_into().unwrap();
3589 let players: Set<ed25519::PublicKey> = vec![player_pk.clone()].try_into().unwrap();
3590 let info = Info::<MinPk, _>::new::<N3f1>(
3591 b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3592 0,
3593 None,
3594 Default::default(),
3595 dealers.clone(),
3596 players.clone(),
3597 )?;
3598 let wrong_round_info = Info::<MinPk, _>::new::<N3f1>(
3599 b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3600 1,
3601 None,
3602 Default::default(),
3603 dealers,
3604 players,
3605 )?;
3606 let (_, pub_msg, _) =
3607 Dealer::start::<N3f1>(&mut test_rng(), info.clone(), dealer_sk, None)?;
3608 let bad_ack = PlayerAck {
3609 sig: transcript_for_ack(
3610 &transcript_for_round(&wrong_round_info),
3611 &dealer_pk,
3612 &pub_msg,
3613 )
3614 .sign(&player_sk),
3615 };
3616 let results: Map<_, _> = vec![(player_pk, AckOrReveal::Ack(bad_ack))]
3617 .into_iter()
3618 .try_collect()
3619 .unwrap();
3620 let mut logs = BTreeMap::new();
3621 logs.insert(
3622 dealer_pk,
3623 DealerLog {
3624 pub_msg,
3625 results: DealerResult::Ok(results),
3626 },
3627 );
3628
3629 let resumed = Player::resume::<N3f1>(info, player_sk, &logs, []);
3630 assert!(resumed.is_ok());
3631 let (_, acks) = resumed.unwrap();
3632 assert!(acks.is_empty());
3633
3634 Ok(())
3635 }
3636
3637 #[test]
3638 fn test_dealer_priv_msg_redacted() {
3639 let mut rng = test_rng();
3640 let msg = DealerPrivMsg::new(Scalar::random(&mut rng));
3641 let debug = format!("{:?}", msg);
3642 assert!(debug.contains("REDACTED"));
3643 }
3644
3645 #[cfg(feature = "arbitrary")]
3646 mod conformance {
3647 use super::*;
3648 use commonware_codec::conformance::CodecConformance;
3649
3650 commonware_conformance::conformance_tests! {
3651 CodecConformance<Output<MinPk, ed25519::PublicKey>>,
3652 CodecConformance<DealerPubMsg<MinPk>>,
3653 CodecConformance<DealerPrivMsg>,
3654 CodecConformance<PlayerAck<ed25519::PublicKey>>,
3655 CodecConformance<AckOrReveal<ed25519::PublicKey>>,
3656 CodecConformance<DealerResult<ed25519::PublicKey>>,
3657 CodecConformance<DealerLog<MinPk, ed25519::PublicKey>>,
3658 CodecConformance<SignedDealerLog<MinPk, ed25519::PrivateKey>>,
3659 }
3660 }
3661}