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