1use crate::{
349 BatchVerifier, PublicKey, Secret, Signer,
350 bls12381::primitives::{
351 group::{Private, Scalar, ScalarReadCfg, Share},
352 sharing::{Mode, ModeVersion, Sharing},
353 variant::Variant,
354 },
355 transcript::{Summary, Transcript, Version},
356};
357use commonware_codec::{
358 Encode, EncodeSize, Mode as CodecMode, RangeCfg, Read, ReadExt, Write, mode, modes,
359};
360use commonware_math::{
361 algebra::{Additive, CryptoGroup, Random, Ring as _},
362 poly::{Interpolator, Poly},
363};
364use commonware_parallel::{Sequential, Strategy};
365#[cfg(feature = "arbitrary")]
366use commonware_utils::N3f1;
367use commonware_utils::{
368 Faults, NZU32, Participant, TryCollect,
369 ordered::{Map, Quorum, Set},
370};
371use core::num::NonZeroU32;
372use rand_core::CryptoRng;
373use std::{borrow::Cow, collections::BTreeMap, marker::PhantomData};
374use thiserror::Error;
375
376const NAMESPACE: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG";
377const SIG_ACK: &[u8] = b"ack";
378const SIG_LOG: &[u8] = b"log";
379const NOISE_PRE_VERIFY: &[u8] = b"pre_verify";
380
381const TRANSCRIPT_VERSION: Version = Version::V0;
384
385#[derive(Clone, Debug, Error)]
387pub enum Error {
388 #[error("missing dealer's share from the previous round")]
389 MissingDealerShare,
390 #[error("player is not present in the list of players")]
391 PlayerNotInRound,
392 #[error("dealer {0} is not present in the round")]
393 DealerNotInRound(String),
394 #[error("invalid number of dealers: {0}")]
395 NumDealers(usize),
396 #[error("invalid number of players: {0}")]
397 NumPlayers(usize),
398 #[error("invalid previous DKG output")]
400 InvalidPreviousOutput,
401 #[error("invalid persisted dealing from dealer {dealer}")]
403 InvalidPersistedDealing {
404 dealer: String,
406 },
407 #[error("logs are bound to a different dkg round")]
409 MismatchedLogs,
410 #[error("missing player's dealing")]
418 MissingPlayerDealing,
419}
420
421#[derive(Clone, Debug, Error)]
423pub enum FaultReason {
424 #[error("dealer log contains an acknowledgement signature that does not verify")]
425 InvalidAck,
426 #[error("dealer reveal does not match its commitment")]
427 InvalidReveal,
428 #[error("invalid dealer commitment degree: expected {expected}, got {actual}")]
429 InvalidCommitmentDegree {
430 expected: u32,
432 actual: u32,
434 },
435 #[error("dealer commitment does not match its previous share")]
436 MismatchedReshareCommitment,
437 #[error("dealer log player set does not match the round")]
438 MismatchedLogPlayers,
439 #[error("dealer log publishes too many reveals")]
444 ExcessiveReveals,
445}
446
447#[derive(Clone, Debug, Error)]
454pub enum DealerMessageError {
455 #[error("participant is not a dealer in this round")]
456 UnexpectedDealer,
457 #[error("invalid dealer commitment degree: expected {expected}, got {actual}")]
458 InvalidCommitmentDegree {
459 expected: u32,
461 actual: u32,
463 },
464 #[error("dealer commitment does not match its previous share")]
465 MismatchedReshareCommitment,
466 #[error("dealer share does not match its commitment")]
467 InvalidDealerShare,
468}
469
470#[derive(Clone, Debug, Error)]
472pub enum PlayerAckError {
473 #[error("participant is not a player in this round")]
474 UnexpectedPlayer,
475 #[error("player acknowledgement signature does not match the dealer transcript")]
476 InvalidAck,
477}
478
479enum DealerPubMsgError {
485 InvalidCommitmentDegree { expected: u32, actual: u32 },
487 MismatchedReshareCommitment,
489}
490
491impl From<DealerPubMsgError> for DealerMessageError {
493 fn from(error: DealerPubMsgError) -> Self {
494 match error {
495 DealerPubMsgError::InvalidCommitmentDegree { expected, actual } => {
496 Self::InvalidCommitmentDegree { expected, actual }
497 }
498 DealerPubMsgError::MismatchedReshareCommitment => Self::MismatchedReshareCommitment,
499 }
500 }
501}
502
503impl From<DealerPubMsgError> for FaultReason {
505 fn from(error: DealerPubMsgError) -> Self {
506 match error {
507 DealerPubMsgError::InvalidCommitmentDegree { expected, actual } => {
508 Self::InvalidCommitmentDegree { expected, actual }
509 }
510 DealerPubMsgError::MismatchedReshareCommitment => Self::MismatchedReshareCommitment,
511 }
512 }
513}
514
515#[derive(Clone, Copy, Debug, Eq, PartialEq)]
517enum DealerLogOutcome {
518 Available,
520 Unavailable,
522}
523
524#[derive(Clone, Debug)]
526enum DealerLogError {
527 UnexpectedDealer,
529 Fault(FaultReason),
531}
532
533#[derive(Debug, Error)]
535pub enum Failure<P> {
536 #[error("insufficient usable dealer logs: required={required}, found={found}")]
538 InsufficientLogs {
539 required: u32,
541 found: u32,
543 faults: Map<P, FaultReason>,
548 unavailable: Set<P>,
553 },
554}
555
556#[derive(Debug, Error)]
558pub enum FinalizeError<P> {
559 #[error(transparent)]
561 Error(#[from] Error),
562 #[error(transparent)]
564 Failure(#[from] Failure<P>),
565}
566
567#[derive(Debug, Clone, PartialEq, Eq)]
569pub struct Output<V: Variant, P> {
570 summary: Summary,
571 public: Sharing<V>,
572 dealers: Set<P>,
573 players: Set<P>,
574 revealed: Set<P>,
575}
576
577impl<V: Variant, P: Ord> Output<V, P> {
578 fn share_commitment(&self, player: &P) -> Option<V::Public> {
579 self.public.partial_public(self.players.index(player)?).ok()
580 }
581
582 pub fn quorum<M: Faults>(&self) -> u32 {
584 self.players.quorum::<M>()
585 }
586
587 pub const fn public(&self) -> &Sharing<V> {
591 &self.public
592 }
593
594 pub const fn dealers(&self) -> &Set<P> {
596 &self.dealers
597 }
598
599 pub const fn players(&self) -> &Set<P> {
601 &self.players
602 }
603
604 pub const fn revealed(&self) -> &Set<P> {
608 &self.revealed
609 }
610}
611
612impl<V: Variant, P: PublicKey> EncodeSize for Output<V, P> {
613 fn encode_size(&self) -> usize {
614 self.summary.encode_size()
615 + self.public.encode_size()
616 + self.dealers.encode_size()
617 + self.players.encode_size()
618 + self.revealed.encode_size()
619 }
620}
621
622impl<V: Variant, P: PublicKey> Write for Output<V, P> {
623 fn write(&self, buf: &mut impl bytes::BufMut) {
624 self.summary.write(buf);
625 self.public.write(buf);
626 self.dealers.write(buf);
627 self.players.write(buf);
628 self.revealed.write(buf);
629 }
630}
631
632impl<V: Variant, P: PublicKey> Read for Output<V, P> {
633 type Cfg = (NonZeroU32, ModeVersion);
634
635 fn read_cfg(
636 buf: &mut impl bytes::Buf,
637 (max_participants, max_supported_mode): &Self::Cfg,
638 ) -> Result<Self, commonware_codec::Error> {
639 let max_participants_usize = max_participants.get() as usize;
640 Ok(Self {
641 summary: ReadExt::read(buf)?,
642 public: Read::read_cfg(buf, &(*max_participants, *max_supported_mode))?,
643 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), ()))?, })
647 }
648}
649
650#[cfg(feature = "arbitrary")]
651impl<P: PublicKey, V: Variant> arbitrary::Arbitrary<'_> for Output<V, P>
652where
653 P: for<'a> arbitrary::Arbitrary<'a> + Ord,
654 V::Public: for<'a> arbitrary::Arbitrary<'a>,
655{
656 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
657 let summary = u.arbitrary()?;
658 let public: Sharing<V> = u.arbitrary()?;
659 let total = public.total().get() as usize;
660
661 let num_dealers = u.int_in_range(1..=total * 2)?;
662 let dealers = Set::try_from(
663 u.arbitrary_iter::<P>()?
664 .take(num_dealers)
665 .collect::<Result<Vec<_>, _>>()?,
666 )
667 .map_err(|_| arbitrary::Error::IncorrectFormat)?;
668
669 let players = Set::try_from(
670 u.arbitrary_iter::<P>()?
671 .take(total)
672 .collect::<Result<Vec<_>, _>>()?,
673 )
674 .map_err(|_| arbitrary::Error::IncorrectFormat)?;
675
676 let max_revealed = N3f1::max_faults(total) as usize;
677 let revealed = Set::from_iter_dedup(
678 players
679 .iter()
680 .filter(|_| u.arbitrary::<bool>().unwrap_or(false))
681 .take(max_revealed)
682 .cloned(),
683 );
684
685 Ok(Self {
686 summary,
687 public,
688 dealers,
689 players,
690 revealed,
691 })
692 }
693}
694
695#[derive(Clone, Copy, Debug, Eq, PartialEq)]
697#[repr(u8)]
698pub enum Reveal {
699 #[deprecated(note = "uses the new player set instead of the contributor set")]
701 V0 = 0,
702 V1 = 1,
704}
705
706#[allow(deprecated)]
707impl From<Reveal> for CodecMode {
708 fn from(reveal: Reveal) -> Self {
709 match reveal {
710 Reveal::V0 => mode!(0),
711 Reveal::V1 => mode!(1),
712 }
713 }
714}
715
716#[derive(Debug, Clone)]
723pub struct Info<V: Variant, P> {
724 summary: Summary,
725 round: u64,
726 previous: Option<Output<V, P>>,
727 mode: Mode,
728 reveal: Reveal,
729 dealers: Set<P>,
730 players: Set<P>,
731}
732
733impl<V: Variant, P: PublicKey> PartialEq for Info<V, P> {
734 fn eq(&self, other: &Self) -> bool {
735 self.summary == other.summary
736 }
737}
738
739impl<V: Variant, P: PublicKey> Info<V, P> {
740 fn unwrap_or_random_share(
746 &self,
747 mut rng: impl CryptoRng,
748 share: Option<Scalar>,
749 ) -> Result<Scalar, Error> {
750 let out = match (self.previous.as_ref(), share) {
751 (None, None) => Scalar::random(&mut rng),
752 (_, Some(x)) => x,
753 (Some(_), None) => return Err(Error::MissingDealerShare),
754 };
755 Ok(out)
756 }
757
758 const fn num_players(&self) -> NonZeroU32 {
759 NZU32!(self.players.len() as u32)
761 }
762
763 fn degree<M: Faults>(&self) -> u32 {
764 self.players.quorum::<M>().saturating_sub(1)
765 }
766
767 fn required_commitments<M: Faults>(&self) -> u32 {
768 let dealer_quorum = self.dealers.quorum::<M>();
769 let prev_quorum = self
770 .previous
771 .as_ref()
772 .map(Output::quorum::<M>)
773 .unwrap_or(u32::MIN);
774 dealer_quorum.max(prev_quorum)
775 }
776
777 fn max_reveals<M: Faults>(&self) -> u32 {
778 self.players.max_faults::<M>()
779 }
780
781 fn reveal_threshold<M: Faults>(&self) -> u32 {
782 #[allow(deprecated)]
783 match self.reveal {
784 Reveal::V0 => self.players.max_faults::<M>() + 1,
785 Reveal::V1 => {
786 let max_faults = self.previous.as_ref().map_or_else(
787 || self.dealers.max_faults::<M>(),
788 |previous| previous.players.max_faults::<M>(),
789 );
790 self.required_commitments::<M>()
791 .checked_sub(max_faults)
792 .expect("a quorum must contain more participants than max_faults")
793 }
794 }
795 }
796
797 fn player_index(&self, player: &P) -> Option<Participant> {
798 self.players.index(player)
799 }
800
801 fn dealer_index(&self, dealer: &P) -> Option<Participant> {
802 self.dealers.index(dealer)
803 }
804
805 fn player_scalar(&self, player: &P) -> Option<Scalar> {
806 self.mode
807 .scalar(self.num_players(), self.player_index(player)?)
808 }
809
810 fn check_dealer_pub_msg<M: Faults>(
811 &self,
812 dealer: &P,
813 pub_msg: &DealerPubMsg<V>,
814 ) -> Result<(), DealerPubMsgError> {
815 let expected = self.degree::<M>();
816 let actual = pub_msg.commitment.degree_exact();
817 if expected != actual {
818 return Err(DealerPubMsgError::InvalidCommitmentDegree { expected, actual });
819 }
820 if let Some(previous) = self.previous.as_ref() {
821 let share_commitment = previous
822 .share_commitment(dealer)
823 .expect("Info::new validates previous output and dealer membership");
824 if *pub_msg.commitment.constant() != share_commitment {
825 return Err(DealerPubMsgError::MismatchedReshareCommitment);
826 }
827 }
828 Ok(())
829 }
830
831 fn check_dealer_priv_msg(
832 &self,
833 player: Participant,
834 pub_msg: &DealerPubMsg<V>,
835 priv_msg: &DealerPrivMsg,
836 ) -> bool {
837 let scalar = self
838 .mode
839 .scalar(self.num_players(), player)
840 .expect("Player::new validates the participant index");
841 let expected = pub_msg.commitment.eval_msm(&scalar, &Sequential);
842 priv_msg
843 .share
844 .expose(|share| expected == V::Public::generator() * share)
845 }
846
847 fn check_dealer_log<M: Faults, B: BatchVerifier<PublicKey = P>>(
848 &self,
849 rng: &mut impl CryptoRng,
850 strategy: &impl Strategy,
851 round_transcript: &Transcript,
852 dealer: &P,
853 log: &DealerLog<V, P>,
854 ) -> Result<DealerLogOutcome, DealerLogError> {
855 if self.dealer_index(dealer).is_none() {
856 return Err(DealerLogError::UnexpectedDealer);
857 }
858 self.check_dealer_pub_msg::<M>(dealer, &log.pub_msg)
859 .map_err(|error| DealerLogError::Fault(error.into()))?;
860 let Some(results_iter) = log
861 .zip_players(&self.players)
862 .map_err(DealerLogError::Fault)?
863 else {
864 return Ok(DealerLogOutcome::Unavailable);
865 };
866 let ack_summary = transcript_for_ack(round_transcript, dealer, &log.pub_msg).summarize();
867 let mut ack_batch = B::new(self.players.len());
868 let mut reveal_count = 0;
869 let max_reveals = self.max_reveals::<M>();
870 let mut reveal_eval_points = Vec::new();
871 let mut reveal_sum = Scalar::zero();
872 for (player, result) in results_iter {
873 match result {
874 AckOrReveal::Ack(ack) => {
875 if !ack_summary.add_to_batch(&mut ack_batch, player, &ack.sig) {
876 return Err(DealerLogError::Fault(FaultReason::InvalidAck));
877 }
878 }
879 AckOrReveal::Reveal(priv_msg) => {
880 reveal_count += 1;
881 if reveal_count > max_reveals {
882 return Err(DealerLogError::Fault(FaultReason::ExcessiveReveals));
883 }
884 let player_scalar = self
885 .player_scalar(player)
886 .expect("log players were matched against the round");
887 let coeff = if reveal_count == 1 {
888 Scalar::one()
889 } else {
890 Scalar::random(&mut *rng)
891 };
892 reveal_eval_points.push((coeff.clone(), player_scalar));
893 priv_msg
894 .share
895 .expose(|share| reveal_sum += &(coeff * share));
896 }
897 }
898 }
899 if !ack_batch.verify(&mut *rng, strategy) {
900 return Err(DealerLogError::Fault(FaultReason::InvalidAck));
901 }
902 let lhs = log.pub_msg.commitment.lin_comb_eval(
903 reveal_eval_points
904 .into_iter()
905 .map(|(coeff, point)| (coeff, Cow::Owned(point))),
906 strategy,
907 );
908 if lhs != V::Public::generator() * &reveal_sum {
909 return Err(DealerLogError::Fault(FaultReason::InvalidReveal));
910 }
911 Ok(DealerLogOutcome::Available)
912 }
913}
914
915impl<V: Variant, P: PublicKey> Info<V, P> {
916 pub fn new<M: Faults>(
930 namespace: &[u8],
931 round: u64,
932 previous: Option<Output<V, P>>,
933 mode: Mode,
934 reveal: Reveal,
935 dealers: Set<P>,
936 players: Set<P>,
937 ) -> Result<Self, Error> {
938 let participant_range = 1..u32::MAX as usize;
939 if !participant_range.contains(&dealers.len()) {
940 return Err(Error::NumDealers(dealers.len()));
941 }
942 if !participant_range.contains(&players.len()) {
943 return Err(Error::NumPlayers(players.len()));
944 }
945 if let Some(previous) = previous.as_ref() {
946 if Some(previous.public.total().get()) != u32::try_from(previous.players.len()).ok()
947 || previous.public.required() != previous.quorum::<M>()
948 {
949 return Err(Error::InvalidPreviousOutput);
950 }
951 if let Some(unknown) = dealers
952 .iter()
953 .find(|d| previous.players.position(d).is_none())
954 {
955 return Err(Error::DealerNotInRound(format!("{unknown:?}")));
956 }
957 if dealers.len() < previous.quorum::<M>() as usize {
958 return Err(Error::NumDealers(dealers.len()));
959 }
960 }
961 let summary = {
962 let mut transcript = Transcript::new(NAMESPACE, TRANSCRIPT_VERSION);
963 transcript
964 .commit(namespace)
965 .commit(round.encode())
966 .commit(previous.encode())
967 .commit(dealers.encode())
968 .commit(players.encode());
969
970 if let Some(modes) = modes![mode, reveal] {
972 transcript.commit(modes.encode());
973 }
974 transcript.summarize()
975 };
976 Ok(Self {
977 summary,
978 round,
979 previous,
980 mode,
981 reveal,
982 dealers,
983 players,
984 })
985 }
986
987 pub const fn round(&self) -> u64 {
991 self.round
992 }
993}
994
995#[derive(Clone, Debug)]
996pub struct DealerPubMsg<V: Variant> {
997 commitment: Poly<V::Public>,
998}
999
1000impl<V: Variant> PartialEq for DealerPubMsg<V> {
1001 fn eq(&self, other: &Self) -> bool {
1002 self.commitment == other.commitment
1003 }
1004}
1005
1006impl<V: Variant> Eq for DealerPubMsg<V> {}
1007
1008impl<V: Variant> EncodeSize for DealerPubMsg<V> {
1009 fn encode_size(&self) -> usize {
1010 self.commitment.encode_size()
1011 }
1012}
1013
1014impl<V: Variant> Write for DealerPubMsg<V> {
1015 fn write(&self, buf: &mut impl bytes::BufMut) {
1016 self.commitment.write(buf);
1017 }
1018}
1019
1020impl<V: Variant> Read for DealerPubMsg<V> {
1021 type Cfg = NonZeroU32;
1022
1023 fn read_cfg(
1024 buf: &mut impl bytes::Buf,
1025 &max_size: &Self::Cfg,
1026 ) -> Result<Self, commonware_codec::Error> {
1027 Ok(Self {
1028 commitment: Read::read_cfg(buf, &(RangeCfg::from(NZU32!(1)..=max_size), ()))?,
1029 })
1030 }
1031}
1032
1033#[cfg(feature = "arbitrary")]
1034impl<V: Variant> arbitrary::Arbitrary<'_> for DealerPubMsg<V>
1035where
1036 V::Public: for<'a> arbitrary::Arbitrary<'a>,
1037{
1038 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1039 let commitment = u.arbitrary()?;
1040 Ok(Self { commitment })
1041 }
1042}
1043
1044#[derive(Clone, Debug, PartialEq, Eq)]
1045pub struct DealerPrivMsg {
1046 share: Secret<Scalar>,
1047}
1048
1049impl DealerPrivMsg {
1050 pub const fn new(share: Scalar) -> Self {
1052 Self {
1053 share: Secret::new(share),
1054 }
1055 }
1056}
1057
1058impl EncodeSize for DealerPrivMsg {
1059 fn encode_size(&self) -> usize {
1060 self.share.expose(|share| share.encode_size())
1061 }
1062}
1063
1064impl Write for DealerPrivMsg {
1065 fn write(&self, buf: &mut impl bytes::BufMut) {
1066 self.share.expose(|share| share.write(buf));
1067 }
1068}
1069
1070impl Read for DealerPrivMsg {
1071 type Cfg = ();
1072
1073 fn read_cfg(
1074 buf: &mut impl bytes::Buf,
1075 _cfg: &Self::Cfg,
1076 ) -> Result<Self, commonware_codec::Error> {
1077 Ok(Self::new(Scalar::read_cfg(
1078 buf,
1079 &ScalarReadCfg::RejectZero,
1080 )?))
1081 }
1082}
1083
1084#[cfg(feature = "arbitrary")]
1085impl arbitrary::Arbitrary<'_> for DealerPrivMsg {
1086 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1087 Ok(Self::new(u.arbitrary()?))
1088 }
1089}
1090
1091#[derive(Clone, Debug)]
1092pub struct PlayerAck<P: PublicKey> {
1093 sig: P::Signature,
1094}
1095
1096impl<P: PublicKey> PartialEq for PlayerAck<P> {
1097 fn eq(&self, other: &Self) -> bool {
1098 self.sig == other.sig
1099 }
1100}
1101
1102impl<P: PublicKey> EncodeSize for PlayerAck<P> {
1103 fn encode_size(&self) -> usize {
1104 self.sig.encode_size()
1105 }
1106}
1107
1108impl<P: PublicKey> Write for PlayerAck<P> {
1109 fn write(&self, buf: &mut impl bytes::BufMut) {
1110 self.sig.write(buf);
1111 }
1112}
1113
1114impl<P: PublicKey> Read for PlayerAck<P> {
1115 type Cfg = ();
1116
1117 fn read_cfg(
1118 buf: &mut impl bytes::Buf,
1119 _cfg: &Self::Cfg,
1120 ) -> Result<Self, commonware_codec::Error> {
1121 Ok(Self {
1122 sig: ReadExt::read(buf)?,
1123 })
1124 }
1125}
1126
1127#[cfg(feature = "arbitrary")]
1128impl<P: PublicKey> arbitrary::Arbitrary<'_> for PlayerAck<P>
1129where
1130 P::Signature: for<'a> arbitrary::Arbitrary<'a>,
1131{
1132 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1133 let sig = u.arbitrary()?;
1134 Ok(Self { sig })
1135 }
1136}
1137
1138#[derive(Clone, PartialEq)]
1139enum AckOrReveal<P: PublicKey> {
1140 Ack(PlayerAck<P>),
1141 Reveal(DealerPrivMsg),
1142}
1143
1144impl<P: PublicKey> AckOrReveal<P> {
1145 const fn is_reveal(&self) -> bool {
1146 matches!(*self, Self::Reveal(_))
1147 }
1148}
1149
1150impl<P: PublicKey> std::fmt::Debug for AckOrReveal<P> {
1151 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1152 match self {
1153 Self::Ack(x) => write!(f, "Ack({x:?})"),
1154 Self::Reveal(_) => write!(f, "Reveal(REDACTED)"),
1155 }
1156 }
1157}
1158
1159impl<P: PublicKey> EncodeSize for AckOrReveal<P> {
1160 fn encode_size(&self) -> usize {
1161 1 + match self {
1162 Self::Ack(x) => x.encode_size(),
1163 Self::Reveal(x) => x.encode_size(),
1164 }
1165 }
1166}
1167
1168impl<P: PublicKey> Write for AckOrReveal<P> {
1169 fn write(&self, buf: &mut impl bytes::BufMut) {
1170 match self {
1171 Self::Ack(x) => {
1172 0u8.write(buf);
1173 x.write(buf);
1174 }
1175 Self::Reveal(x) => {
1176 1u8.write(buf);
1177 x.write(buf);
1178 }
1179 }
1180 }
1181}
1182
1183impl<P: PublicKey> Read for AckOrReveal<P> {
1184 type Cfg = ();
1185
1186 fn read_cfg(
1187 buf: &mut impl bytes::Buf,
1188 _cfg: &Self::Cfg,
1189 ) -> Result<Self, commonware_codec::Error> {
1190 let tag = u8::read(buf)?;
1191 match tag {
1192 0 => Ok(Self::Ack(ReadExt::read(buf)?)),
1193 1 => Ok(Self::Reveal(ReadExt::read(buf)?)),
1194 x => Err(commonware_codec::Error::InvalidEnum(x)),
1195 }
1196 }
1197}
1198
1199#[cfg(feature = "arbitrary")]
1200impl<P: PublicKey> arbitrary::Arbitrary<'_> for AckOrReveal<P>
1201where
1202 P: for<'a> arbitrary::Arbitrary<'a>,
1203 P::Signature: for<'a> arbitrary::Arbitrary<'a>,
1204{
1205 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1206 let choice = u.int_in_range(0..=1)?;
1207 match choice {
1208 0 => {
1209 let ack = u.arbitrary()?;
1210 Ok(Self::Ack(ack))
1211 }
1212 1 => {
1213 let reveal = u.arbitrary()?;
1214 Ok(Self::Reveal(reveal))
1215 }
1216 _ => unreachable!(),
1217 }
1218 }
1219}
1220
1221#[derive(Clone, Debug)]
1222enum DealerResult<P: PublicKey> {
1223 Ok(Map<P, AckOrReveal<P>>),
1224 TooManyReveals,
1225}
1226
1227impl<P: PublicKey> PartialEq for DealerResult<P> {
1228 fn eq(&self, other: &Self) -> bool {
1229 match (self, other) {
1230 (Self::Ok(x), Self::Ok(y)) => x == y,
1231 (Self::TooManyReveals, Self::TooManyReveals) => true,
1232 _ => false,
1233 }
1234 }
1235}
1236
1237impl<P: PublicKey> EncodeSize for DealerResult<P> {
1238 fn encode_size(&self) -> usize {
1239 1 + match self {
1240 Self::Ok(r) => r.encode_size(),
1241 Self::TooManyReveals => 0,
1242 }
1243 }
1244}
1245
1246impl<P: PublicKey> Write for DealerResult<P> {
1247 fn write(&self, buf: &mut impl bytes::BufMut) {
1248 match self {
1249 Self::Ok(r) => {
1250 0u8.write(buf);
1251 r.write(buf);
1252 }
1253 Self::TooManyReveals => {
1254 1u8.write(buf);
1255 }
1256 }
1257 }
1258}
1259
1260impl<P: PublicKey> Read for DealerResult<P> {
1261 type Cfg = NonZeroU32;
1262
1263 fn read_cfg(
1264 buf: &mut impl bytes::Buf,
1265 &max_players: &Self::Cfg,
1266 ) -> Result<Self, commonware_codec::Error> {
1267 let tag = u8::read(buf)?;
1268 match tag {
1269 0 => Ok(Self::Ok(Read::read_cfg(
1270 buf,
1271 &(RangeCfg::from(0..=max_players.get() as usize), (), ()),
1272 )?)),
1273 1 => Ok(Self::TooManyReveals),
1274 x => Err(commonware_codec::Error::InvalidEnum(x)),
1275 }
1276 }
1277}
1278
1279#[cfg(feature = "arbitrary")]
1280impl<P: PublicKey> arbitrary::Arbitrary<'_> for DealerResult<P>
1281where
1282 P: for<'a> arbitrary::Arbitrary<'a>,
1283 P::Signature: for<'a> arbitrary::Arbitrary<'a>,
1284{
1285 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1286 let choice = u.int_in_range(0..=1)?;
1287 match choice {
1288 0 => {
1289 use commonware_utils::TryFromIterator;
1290 use std::collections::HashMap;
1291
1292 let base: HashMap<P, AckOrReveal<P>> = u.arbitrary()?;
1293 let map =
1294 Map::try_from_iter(base).map_err(|_| arbitrary::Error::IncorrectFormat)?;
1295
1296 Ok(Self::Ok(map))
1297 }
1298 1 => Ok(Self::TooManyReveals),
1299 _ => unreachable!(),
1300 }
1301 }
1302}
1303
1304#[derive(Clone, Debug)]
1305pub struct DealerLog<V: Variant, P: PublicKey> {
1306 pub_msg: DealerPubMsg<V>,
1307 results: DealerResult<P>,
1308}
1309
1310impl<V: Variant, P: PublicKey> PartialEq for DealerLog<V, P> {
1311 fn eq(&self, other: &Self) -> bool {
1312 self.pub_msg == other.pub_msg && self.results == other.results
1313 }
1314}
1315
1316impl<V: Variant, P: PublicKey> EncodeSize for DealerLog<V, P> {
1317 fn encode_size(&self) -> usize {
1318 self.pub_msg.encode_size() + self.results.encode_size()
1319 }
1320}
1321
1322impl<V: Variant, P: PublicKey> Write for DealerLog<V, P> {
1323 fn write(&self, buf: &mut impl bytes::BufMut) {
1324 self.pub_msg.write(buf);
1325 self.results.write(buf);
1326 }
1327}
1328
1329impl<V: Variant, P: PublicKey> Read for DealerLog<V, P> {
1330 type Cfg = NonZeroU32;
1331
1332 fn read_cfg(
1333 buf: &mut impl bytes::Buf,
1334 cfg: &Self::Cfg,
1335 ) -> Result<Self, commonware_codec::Error> {
1336 Ok(Self {
1337 pub_msg: Read::read_cfg(buf, cfg)?,
1338 results: Read::read_cfg(buf, cfg)?,
1339 })
1340 }
1341}
1342
1343impl<V: Variant, P: PublicKey> DealerLog<V, P> {
1344 fn get_ack(&self, player: &P) -> Option<&PlayerAck<P>> {
1345 let DealerResult::Ok(results) = &self.results else {
1346 return None;
1347 };
1348 match results.get_value(player) {
1349 Some(AckOrReveal::Ack(ack)) => Some(ack),
1350 _ => None,
1351 }
1352 }
1353
1354 fn get_reveal(&self, player: &P) -> Option<&DealerPrivMsg> {
1355 let DealerResult::Ok(results) = &self.results else {
1356 return None;
1357 };
1358 match results.get_value(player) {
1359 Some(AckOrReveal::Reveal(priv_msg)) => Some(priv_msg),
1360 _ => None,
1361 }
1362 }
1363
1364 fn zip_players<'a, 'b>(
1365 &'a self,
1366 players: &'b Set<P>,
1367 ) -> Result<Option<impl Iterator<Item = (&'b P, &'a AckOrReveal<P>)>>, FaultReason> {
1368 match &self.results {
1369 DealerResult::TooManyReveals => Ok(None),
1370 DealerResult::Ok(results) => {
1371 if results.keys() != players {
1373 return Err(FaultReason::MismatchedLogPlayers);
1374 }
1375 Ok(Some(players.iter().zip(results.values().iter())))
1376 }
1377 }
1378 }
1379
1380 pub fn summary(&self) -> DealerLogSummary<P> {
1384 match &self.results {
1385 DealerResult::TooManyReveals => DealerLogSummary::TooManyReveals,
1386 DealerResult::Ok(map) => {
1387 let (reveals, acks): (Vec<_>, Vec<_>) =
1388 map.iter_pairs().partition(|(_, a_r)| a_r.is_reveal());
1389 DealerLogSummary::Ok {
1390 acks: acks
1391 .into_iter()
1392 .map(|(p, _)| p.clone())
1393 .try_collect()
1394 .expect("map keys are deduped"),
1395 reveals: reveals
1396 .into_iter()
1397 .map(|(p, _)| p.clone())
1398 .try_collect()
1399 .expect("map keys are deduped"),
1400 }
1401 }
1402 }
1403 }
1404}
1405
1406#[derive(Clone, Debug)]
1410pub enum DealerLogSummary<P> {
1411 TooManyReveals,
1414 Ok { acks: Set<P>, reveals: Set<P> },
1416}
1417
1418#[cfg(feature = "arbitrary")]
1419impl<V: Variant, P: PublicKey> arbitrary::Arbitrary<'_> for DealerLog<V, P>
1420where
1421 P: for<'a> arbitrary::Arbitrary<'a>,
1422 V::Public: for<'a> arbitrary::Arbitrary<'a>,
1423 P::Signature: for<'a> arbitrary::Arbitrary<'a>,
1424{
1425 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1426 let pub_msg = u.arbitrary()?;
1427 let results = u.arbitrary()?;
1428 Ok(Self { pub_msg, results })
1429 }
1430}
1431
1432#[derive(Clone, Debug)]
1440pub struct SignedDealerLog<V: Variant, S: Signer> {
1441 dealer: S::PublicKey,
1442 log: DealerLog<V, S::PublicKey>,
1443 sig: S::Signature,
1444}
1445
1446impl<V: Variant, S: Signer> PartialEq for SignedDealerLog<V, S> {
1447 fn eq(&self, other: &Self) -> bool {
1448 self.dealer == other.dealer && self.log == other.log && self.sig == other.sig
1449 }
1450}
1451
1452impl<V: Variant, S: Signer> SignedDealerLog<V, S> {
1453 fn sign(sk: &S, info: &Info<V, S::PublicKey>, log: DealerLog<V, S::PublicKey>) -> Self {
1454 let sig = transcript_for_log(info, &log).sign(sk);
1455 Self {
1456 dealer: sk.public_key(),
1457 log,
1458 sig,
1459 }
1460 }
1461
1462 #[allow(clippy::type_complexity)]
1469 pub fn check(
1470 self,
1471 info: &Info<V, S::PublicKey>,
1472 ) -> Option<(S::PublicKey, DealerLog<V, S::PublicKey>)> {
1473 if !transcript_for_log(info, &self.log).verify(&self.dealer, &self.sig) {
1474 return None;
1475 }
1476 Some((self.dealer, self.log))
1477 }
1478}
1479
1480impl<V: Variant, S: Signer> EncodeSize for SignedDealerLog<V, S> {
1481 fn encode_size(&self) -> usize {
1482 self.dealer.encode_size() + self.log.encode_size() + self.sig.encode_size()
1483 }
1484}
1485
1486impl<V: Variant, S: Signer> Write for SignedDealerLog<V, S> {
1487 fn write(&self, buf: &mut impl bytes::BufMut) {
1488 self.dealer.write(buf);
1489 self.log.write(buf);
1490 self.sig.write(buf);
1491 }
1492}
1493
1494impl<V: Variant, S: Signer> Read for SignedDealerLog<V, S> {
1495 type Cfg = NonZeroU32;
1496
1497 fn read_cfg(
1498 buf: &mut impl bytes::Buf,
1499 cfg: &Self::Cfg,
1500 ) -> Result<Self, commonware_codec::Error> {
1501 Ok(Self {
1502 dealer: ReadExt::read(buf)?,
1503 log: Read::read_cfg(buf, cfg)?,
1504 sig: ReadExt::read(buf)?,
1505 })
1506 }
1507}
1508
1509#[cfg(feature = "arbitrary")]
1510impl<V: Variant, S: Signer> arbitrary::Arbitrary<'_> for SignedDealerLog<V, S>
1511where
1512 S::PublicKey: for<'a> arbitrary::Arbitrary<'a>,
1513 V::Public: for<'a> arbitrary::Arbitrary<'a>,
1514 S::Signature: for<'a> arbitrary::Arbitrary<'a>,
1515{
1516 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1517 let dealer = u.arbitrary()?;
1518 let log = u.arbitrary()?;
1519 let sig = u.arbitrary()?;
1520 Ok(Self { dealer, log, sig })
1521 }
1522}
1523
1524fn transcript_for_round<V: Variant, P: PublicKey>(info: &Info<V, P>) -> Transcript {
1525 Transcript::resume(info.summary, TRANSCRIPT_VERSION)
1526}
1527
1528fn transcript_for_ack<V: Variant, P: PublicKey>(
1529 transcript: &Transcript,
1530 dealer: &P,
1531 pub_msg: &DealerPubMsg<V>,
1532) -> Transcript {
1533 let mut out = transcript.fork(SIG_ACK);
1534 out.commit(dealer.encode());
1535 out.commit(pub_msg.encode());
1536 out
1537}
1538
1539fn transcript_for_log<V: Variant, P: PublicKey>(
1540 info: &Info<V, P>,
1541 log: &DealerLog<V, P>,
1542) -> Transcript {
1543 let mut out = transcript_for_round(info).fork(SIG_LOG);
1544 out.commit(log.encode());
1545 out
1546}
1547
1548#[derive(Clone)]
1551pub struct Logs<V: Variant, P: PublicKey, M: Faults> {
1552 info: Info<V, P>,
1553 logs: BTreeMap<P, DealerLog<V, P>>,
1554 known: BTreeMap<P, Result<DealerLogOutcome, DealerLogError>>,
1555 phantom_m: PhantomData<M>,
1556}
1557
1558type SelectedLogs<V, P> = (Info<V, P>, Map<P, DealerLog<V, P>>);
1560
1561impl<V: Variant, P: PublicKey, M: Faults> Logs<V, P, M> {
1562 pub fn new(info: Info<V, P>) -> Self {
1564 Self {
1565 info,
1566 logs: Default::default(),
1567 known: Default::default(),
1568 phantom_m: Default::default(),
1569 }
1570 }
1571
1572 fn check_dealers<B: BatchVerifier<PublicKey = P>>(
1573 rng: &mut impl CryptoRng,
1574 info: &Info<V, P>,
1575 strategy: &impl Strategy,
1576 transcript: &Transcript,
1577 dealers: &[(&P, &DealerLog<V, P>)],
1578 ) -> Vec<(P, Result<DealerLogOutcome, DealerLogError>)> {
1579 let checks: Vec<_> = dealers
1580 .iter()
1581 .map(|&(dealer, log)| {
1582 let seed = Summary::random(&mut *rng);
1583 ((*dealer).clone(), log, seed)
1584 })
1585 .collect();
1586
1587 strategy.map_collect_vec(checks, |(dealer, log, seed)| {
1593 let mut local_rng =
1594 Transcript::resume(seed, TRANSCRIPT_VERSION).noise(NOISE_PRE_VERIFY);
1595 let result =
1596 info.check_dealer_log::<M, B>(&mut local_rng, strategy, transcript, &dealer, log);
1597 (dealer, result)
1598 })
1599 }
1600
1601 pub fn record(&mut self, dealer: P, log: DealerLog<V, P>) -> bool {
1610 self.known.remove(&dealer);
1611 self.logs.insert(dealer, log).is_some()
1612 }
1613
1614 pub fn pre_verify<B: BatchVerifier<PublicKey = P>>(
1623 &mut self,
1624 rng: &mut impl CryptoRng,
1625 strategy: &impl Strategy,
1626 ) {
1627 let required_commitments = self.info.required_commitments::<M>() as usize;
1628 let transcript = transcript_for_round(&self.info);
1629
1630 let mut need = required_commitments;
1634 let mut pending = Vec::new();
1635 let mut iter = self.logs.iter();
1636 while need > 0 {
1637 let Some((dealer, log)) = iter.next() else {
1638 break;
1639 };
1640 match self.known.get(dealer) {
1641 Some(Ok(DealerLogOutcome::Available)) => need -= 1,
1642 Some(_) => {}
1643 None => {
1644 need -= 1;
1645 pending.push((dealer, log));
1646 }
1647 }
1648 }
1649
1650 let pending_results =
1652 Self::check_dealers::<B>(rng, &self.info, strategy, &transcript, &pending);
1653 let mut all_pending_usable = true;
1654 for (dealer, result) in pending_results {
1655 let is_usable = matches!(result, Ok(DealerLogOutcome::Available));
1656 self.known.insert(dealer, result);
1657 all_pending_usable &= is_usable;
1658 }
1659 if all_pending_usable {
1660 return;
1661 }
1662
1663 let remaining: Vec<_> = iter
1671 .filter(|(dealer, _)| !self.known.contains_key(*dealer))
1672 .collect();
1673 let results = Self::check_dealers::<B>(rng, &self.info, strategy, &transcript, &remaining);
1674 for (dealer, result) in results {
1675 self.known.insert(dealer, result);
1676 }
1677 }
1678
1679 fn select<B: BatchVerifier<PublicKey = P>>(
1683 mut self,
1684 rng: &mut impl CryptoRng,
1685 strategy: &impl Strategy,
1686 ) -> Result<SelectedLogs<V, P>, Failure<P>> {
1687 self.pre_verify::<B>(rng, strategy);
1688 let required = self.info.required_commitments::<M>();
1689 let required_count =
1690 usize::try_from(required).expect("required commitments exceed usize::MAX");
1691 let out: Map<_, _> = self
1692 .logs
1693 .into_iter()
1694 .filter(|(dealer, _)| {
1695 matches!(
1696 self.known.get(dealer),
1697 Some(Ok(DealerLogOutcome::Available))
1698 )
1699 })
1700 .take(required_count)
1701 .try_collect()
1702 .expect("dealers should be unique");
1703 let found = u32::try_from(out.len()).expect("valid dealer count exceeds u32::MAX");
1704 if found < required {
1705 let unavailable =
1706 self.info
1707 .dealers
1708 .iter()
1709 .filter(|dealer| match self.known.get(*dealer) {
1710 None
1711 | Some(Ok(DealerLogOutcome::Unavailable))
1712 | Some(Err(DealerLogError::UnexpectedDealer)) => true,
1713 Some(Ok(DealerLogOutcome::Available))
1714 | Some(Err(DealerLogError::Fault(_))) => false,
1715 })
1716 .cloned()
1717 .try_collect::<Set<_>>()
1718 .expect("configured dealers are unique");
1719 let faults = self
1720 .known
1721 .into_iter()
1722 .filter_map(|(dealer, result)| match result {
1723 Err(DealerLogError::Fault(fault)) => Some((dealer, fault)),
1724 Ok(DealerLogOutcome::Available | DealerLogOutcome::Unavailable)
1725 | Err(DealerLogError::UnexpectedDealer) => None,
1726 })
1727 .try_collect::<Map<_, _>>()
1728 .expect("checked dealers are unique");
1729 return Err(Failure::InsufficientLogs {
1730 required,
1731 found,
1732 faults,
1733 unavailable,
1734 });
1735 }
1736 Ok((self.info, out))
1737 }
1738}
1739
1740pub struct Dealer<V: Variant, S: Signer> {
1741 me: S,
1742 info: Info<V, S::PublicKey>,
1743 pub_msg: DealerPubMsg<V>,
1744 results: Map<S::PublicKey, AckOrReveal<S::PublicKey>>,
1745 transcript: Transcript,
1746}
1747
1748impl<V: Variant, S: Signer> Dealer<V, S> {
1749 #[allow(clippy::type_complexity)]
1771 pub fn start<M: Faults>(
1772 mut rng: impl CryptoRng,
1773 info: Info<V, S::PublicKey>,
1774 me: S,
1775 share: Option<Share>,
1776 ) -> Result<(Self, DealerPubMsg<V>, Vec<(S::PublicKey, DealerPrivMsg)>), Error> {
1777 info.dealer_index(&me.public_key())
1779 .ok_or_else(|| Error::DealerNotInRound(format!("{:?}", me.public_key())))?;
1780 let share = info.unwrap_or_random_share(
1781 &mut rng,
1782 share.map(|x| x.private.expose_unwrap()),
1787 )?;
1788 let my_poly = Poly::new_with_constant(&mut rng, info.degree::<M>(), share);
1789 let priv_msgs = info
1790 .players
1791 .iter()
1792 .map(|pk| {
1793 (
1794 pk.clone(),
1795 DealerPrivMsg::new(my_poly.eval_msm(
1796 &info.player_scalar(pk).expect("player should exist"),
1797 &Sequential,
1798 )),
1799 )
1800 })
1801 .collect::<Vec<_>>();
1802 let results: Map<_, _> = priv_msgs
1803 .clone()
1804 .into_iter()
1805 .map(|(pk, priv_msg)| (pk, AckOrReveal::Reveal(priv_msg)))
1806 .try_collect()
1807 .expect("players are unique");
1808 let commitment = Poly::commit(my_poly);
1809 let pub_msg = DealerPubMsg { commitment };
1810 let transcript = {
1811 let t = transcript_for_round(&info);
1812 transcript_for_ack(&t, &me.public_key(), &pub_msg)
1813 };
1814 let this = Self {
1815 me,
1816 info,
1817 pub_msg: pub_msg.clone(),
1818 results,
1819 transcript,
1820 };
1821 Ok((this, pub_msg, priv_msgs))
1822 }
1823
1824 pub fn receive_player_ack(
1833 &mut self,
1834 player: S::PublicKey,
1835 ack: PlayerAck<S::PublicKey>,
1836 ) -> Result<(), PlayerAckError> {
1837 let Some(res_mut) = self.results.get_value_mut(&player) else {
1838 return Err(PlayerAckError::UnexpectedPlayer);
1839 };
1840 if !self.transcript.verify(&player, &ack.sig) {
1841 return Err(PlayerAckError::InvalidAck);
1842 }
1843 *res_mut = AckOrReveal::Ack(ack);
1844 Ok(())
1845 }
1846
1847 pub fn finalize<M: Faults>(self) -> SignedDealerLog<V, S> {
1851 let reveals = self
1852 .results
1853 .values()
1854 .iter()
1855 .filter(|x| x.is_reveal())
1856 .count() as u32;
1857 let results = if reveals > self.info.max_reveals::<M>() {
1859 DealerResult::TooManyReveals
1860 } else {
1861 DealerResult::Ok(self.results)
1862 };
1863 let log = DealerLog {
1864 pub_msg: self.pub_msg,
1865 results,
1866 };
1867 SignedDealerLog::sign(&self.me, &self.info, log)
1868 }
1869}
1870
1871struct Observe<V: Variant, P: PublicKey> {
1872 output: Output<V, P>,
1873 weights: Option<Interpolator<P, Scalar>>,
1874}
1875
1876impl<V: Variant, P: PublicKey> Observe<V, P> {
1877 fn reckon<M: Faults>(
1878 info: Info<V, P>,
1879 selected: Map<P, DealerLog<V, P>>,
1880 strategy: &impl Strategy,
1881 ) -> Self {
1882 let reveal_threshold = info.reveal_threshold::<M>();
1885 let mut reveal_counts: BTreeMap<P, u32> = BTreeMap::new();
1886 let mut revealed = Vec::new();
1887 for log in selected.values() {
1888 let iter = log
1889 .zip_players(&info.players)
1890 .expect("selected dealer log should match the round's players")
1891 .expect("selected dealer log should contain usable results");
1892 for (player, result) in iter {
1893 if !result.is_reveal() {
1894 continue;
1895 }
1896 let count = reveal_counts.entry(player.clone()).or_insert(0);
1897 *count += 1;
1898 if *count == reveal_threshold {
1899 revealed.push(player.clone());
1900 }
1901 }
1902 }
1903 let revealed: Set<P> = revealed
1904 .into_iter()
1905 .try_collect()
1906 .expect("players are unique");
1907
1908 let dealers: Set<P> = selected
1910 .keys()
1911 .iter()
1912 .cloned()
1913 .try_collect()
1914 .expect("selected dealers are unique");
1915
1916 let (public, weights) = if let Some(previous) = info.previous.as_ref() {
1918 let weights = previous
1919 .public()
1920 .mode()
1921 .subset_interpolator(previous.players(), selected.keys())
1922 .expect("the result of select should produce a valid subset");
1923 let commitments = selected
1924 .into_iter()
1925 .map(|(dealer, log)| (dealer, log.pub_msg.commitment))
1926 .try_collect::<Map<_, _>>()
1927 .expect("Map should have unique keys");
1928 let public = weights
1929 .interpolate(&commitments, strategy)
1930 .expect("select checks that enough points have been provided");
1931
1932 assert_eq!(
1935 previous.public().public(),
1936 public.constant(),
1937 "selected reshare commitments must preserve the previous public key",
1938 );
1939 (public, Some(weights))
1940 } else {
1941 let mut public = Poly::zero();
1942 for log in selected.values() {
1943 public += &log.pub_msg.commitment;
1944 }
1945 (public, None)
1946 };
1947 let n = info.players.len() as u32;
1948 let output = Output {
1949 summary: info.summary,
1950 public: Sharing::new(info.mode, NZU32!(n), public),
1951 dealers,
1952 players: info.players,
1953 revealed,
1954 };
1955 Self { output, weights }
1956 }
1957}
1958
1959pub fn observe<V: Variant, P: PublicKey, M: Faults, B: BatchVerifier<PublicKey = P>>(
1968 rng: &mut impl CryptoRng,
1969 logs: Logs<V, P, M>,
1970 strategy: &impl Strategy,
1971) -> Result<Output<V, P>, Failure<P>> {
1972 let (info, selected) = logs.select::<B>(rng, strategy)?;
1973 Ok(Observe::<V, P>::reckon::<M>(info, selected, strategy).output)
1974}
1975
1976pub struct Player<V: Variant, S: Signer> {
1982 me: S,
1983 me_pub: S::PublicKey,
1984 info: Info<V, S::PublicKey>,
1985 index: Participant,
1986 transcript: Transcript,
1987 view: BTreeMap<S::PublicKey, (DealerPubMsg<V>, DealerPrivMsg)>,
1988}
1989
1990impl<V: Variant, S: Signer> Player<V, S> {
1991 pub fn new(info: Info<V, S::PublicKey>, me: S) -> Result<Self, Error> {
1995 let me_pub = me.public_key();
1996 Ok(Self {
1997 index: info.player_index(&me_pub).ok_or(Error::PlayerNotInRound)?,
1998 me,
1999 me_pub,
2000 transcript: transcript_for_round(&info),
2001 info,
2002 view: BTreeMap::new(),
2003 })
2004 }
2005
2006 #[allow(clippy::type_complexity)]
2035 pub fn resume<M: Faults>(
2036 info: Info<V, S::PublicKey>,
2037 me: S,
2038 logs: &BTreeMap<S::PublicKey, DealerLog<V, S::PublicKey>>,
2039 msgs: impl IntoIterator<Item = (S::PublicKey, DealerPubMsg<V>, DealerPrivMsg)>,
2040 ) -> Result<(Self, BTreeMap<S::PublicKey, PlayerAck<S::PublicKey>>), Error> {
2041 let mut this = Self::new(info, me)?;
2043 let mut acks = BTreeMap::new();
2044 for (dealer, pub_msg, priv_msg) in msgs {
2045 match this.dealer_message::<M>(dealer.clone(), pub_msg, priv_msg) {
2046 Ok(Some(ack)) => {
2047 acks.insert(dealer, ack);
2048 }
2049 Ok(None) => {}
2050 Err(_) => {
2051 return Err(Error::InvalidPersistedDealing {
2052 dealer: format!("{dealer:?}"),
2053 });
2054 }
2055 }
2056 }
2057
2058 if logs.iter().any(|(dealer, log)| {
2061 let Some(ack) = log.get_ack(&this.me_pub) else {
2062 return false;
2063 };
2064 transcript_for_ack(&this.transcript, dealer, &log.pub_msg)
2066 .verify(&this.me_pub, &ack.sig)
2067 && !this.view.contains_key(dealer)
2068 }) {
2069 return Err(Error::MissingPlayerDealing);
2072 }
2073
2074 Ok((this, acks))
2075 }
2076
2077 pub fn dealer_message<M: Faults>(
2089 &mut self,
2090 dealer: S::PublicKey,
2091 pub_msg: DealerPubMsg<V>,
2092 priv_msg: DealerPrivMsg,
2093 ) -> Result<Option<PlayerAck<S::PublicKey>>, DealerMessageError> {
2094 if self.view.contains_key(&dealer) {
2095 return Ok(None);
2096 }
2097 if self.info.dealer_index(&dealer).is_none() {
2098 return Err(DealerMessageError::UnexpectedDealer);
2099 }
2100 self.info
2101 .check_dealer_pub_msg::<M>(&dealer, &pub_msg)
2102 .map_err(DealerMessageError::from)?;
2103 if !self
2104 .info
2105 .check_dealer_priv_msg(self.index, &pub_msg, &priv_msg)
2106 {
2107 return Err(DealerMessageError::InvalidDealerShare);
2108 }
2109 let sig = transcript_for_ack(&self.transcript, &dealer, &pub_msg).sign(&self.me);
2110 self.view.insert(dealer, (pub_msg, priv_msg));
2111 Ok(Some(PlayerAck { sig }))
2112 }
2113
2114 #[allow(clippy::type_complexity)]
2133 pub fn finalize<M: Faults, B: BatchVerifier<PublicKey = S::PublicKey>>(
2134 self,
2135 rng: &mut impl CryptoRng,
2136 logs: Logs<V, S::PublicKey, M>,
2137 strategy: &impl Strategy,
2138 ) -> Result<(Output<V, S::PublicKey>, Share), FinalizeError<S::PublicKey>> {
2139 if logs.info != self.info {
2140 return Err(Error::MismatchedLogs.into());
2141 }
2142 let (_, selected) = logs.select::<B>(rng, strategy)?;
2143
2144 let dealings = selected
2150 .iter_pairs()
2151 .map(|(dealer, log)| {
2152 let persisted = self.view.get(dealer);
2155 let share = match persisted {
2156 Some((pub_msg, priv_msg)) if pub_msg == &log.pub_msg => {
2157 priv_msg.share.clone().expose_unwrap()
2158 }
2159 _ => match log.get_reveal(&self.me_pub) {
2160 Some(priv_msg) => priv_msg.share.clone().expose_unwrap(),
2161 None if persisted.is_some() => {
2162 return Err(Error::InvalidPersistedDealing {
2163 dealer: format!("{dealer:?}"),
2164 });
2165 }
2166 None => return Err(Error::MissingPlayerDealing),
2167 },
2168 };
2169 Ok((dealer.clone(), share))
2170 })
2171 .collect::<Result<Vec<_>, Error>>()?
2172 .into_iter()
2173 .try_collect::<Map<_, _>>()
2174 .expect("Logs::select produces at most one entry per dealer");
2175 let Observe { output, weights } =
2176 Observe::<V, S::PublicKey>::reckon::<M>(self.info, selected, strategy);
2177 let private = weights.map_or_else(
2178 || {
2179 let mut out = <Scalar as Additive>::zero();
2180 for s in dealings.values() {
2181 out += s;
2182 }
2183 out
2184 },
2185 |weights| {
2186 weights
2187 .interpolate(&dealings, strategy)
2188 .expect("Logs::select ensures that we can recover")
2189 },
2190 );
2191 let share = Share::new(self.index, Private::new(private));
2192 Ok((output, share))
2193 }
2194}
2195
2196pub type DealResult<V, P> = Result<(Output<V, P>, Map<P, Share>), Error>;
2198
2199pub fn deal<V: Variant, P: Clone + Ord, M: Faults>(
2201 mut rng: impl CryptoRng,
2202 mode: Mode,
2203 players: Set<P>,
2204) -> DealResult<V, P> {
2205 if players.is_empty() {
2206 return Err(Error::NumPlayers(0));
2207 }
2208 let n = NZU32!(players.len() as u32);
2209 let t = players.quorum::<M>();
2210 let private = Poly::new(&mut rng, t - 1);
2211 let shares: Map<_, _> = players
2212 .iter()
2213 .enumerate()
2214 .map(|(i, p)| {
2215 let participant = Participant::from_usize(i);
2216 let eval = private.eval_msm(
2217 &mode
2218 .scalar(n, participant)
2219 .expect("player index should be valid"),
2220 &Sequential,
2221 );
2222 let share = Share::new(participant, Private::new(eval));
2223 (p.clone(), share)
2224 })
2225 .try_collect()
2226 .expect("players are unique");
2227 let output = Output {
2228 summary: Summary::random(&mut rng),
2229 public: Sharing::new(mode, n, Poly::commit(private)),
2230 dealers: players.clone(),
2231 players,
2232 revealed: Set::default(),
2233 };
2234 Ok((output, shares))
2235}
2236
2237pub fn deal_anonymous<V: Variant, M: Faults>(
2243 rng: impl CryptoRng,
2244 mode: Mode,
2245 n: NonZeroU32,
2246) -> (Sharing<V>, Vec<Share>) {
2247 let players = (0..n.get()).try_collect().unwrap();
2248 let (output, shares) = deal::<V, _, M>(rng, mode, players).unwrap();
2249 (output.public().clone(), shares.values().to_vec())
2250}
2251
2252#[cfg(any(feature = "arbitrary", test))]
2253mod test_plan {
2254 use super::*;
2255 use crate::{
2256 PublicKey,
2257 bls12381::primitives::{
2258 ops::{self, threshold},
2259 variant::Variant,
2260 },
2261 ed25519,
2262 };
2263 use anyhow::anyhow;
2264 use bytes::BytesMut;
2265 use commonware_utils::{Faults, N3f1, TestRng, TryCollect};
2266 use core::num::NonZeroI32;
2267 use std::collections::BTreeSet;
2268
2269 fn apply_mask(bytes: &mut BytesMut, mask: &[u8]) -> bool {
2271 let mut modified = false;
2272 for (l, &r) in bytes.iter_mut().zip(mask.iter()) {
2273 modified |= r != 0;
2274 *l ^= r;
2275 }
2276 modified
2277 }
2278
2279 #[derive(Clone, Default, Debug)]
2280 pub struct Masks {
2281 pub info_summary: Vec<u8>,
2282 pub dealer: Vec<u8>,
2283 pub pub_msg: Vec<u8>,
2284 pub log: Vec<u8>,
2285 }
2286
2287 impl Masks {
2288 fn modifies_player_ack(&self) -> bool {
2289 self.info_summary.iter().any(|&b| b != 0)
2290 || self.dealer.iter().any(|&b| b != 0)
2291 || self.pub_msg.iter().any(|&b| b != 0)
2292 }
2293
2294 fn transcript_for_round<V: Variant, P: PublicKey>(
2295 &self,
2296 info: &Info<V, P>,
2297 ) -> anyhow::Result<(bool, Transcript)> {
2298 let mut summary_bs = info.summary.encode_mut();
2299 let modified = apply_mask(&mut summary_bs, &self.info_summary);
2300 let summary = Summary::read(&mut summary_bs)?;
2301 Ok((modified, Transcript::resume(summary, TRANSCRIPT_VERSION)))
2302 }
2303
2304 fn transcript_for_player_ack<V: Variant, P: PublicKey>(
2305 &self,
2306 info: &Info<V, P>,
2307 dealer: &P,
2308 pub_msg: &DealerPubMsg<V>,
2309 ) -> anyhow::Result<(bool, Transcript)> {
2310 let (mut modified, transcript) = self.transcript_for_round(info)?;
2311 let mut transcript = transcript.fork(SIG_ACK);
2312
2313 let mut dealer_bs = dealer.encode_mut();
2314 modified |= apply_mask(&mut dealer_bs, &self.dealer);
2315 transcript.commit(&mut dealer_bs);
2316
2317 let mut pub_msg_bs = pub_msg.encode_mut();
2318 modified |= apply_mask(&mut pub_msg_bs, &self.pub_msg);
2319 transcript.commit(&mut pub_msg_bs);
2320
2321 Ok((modified, transcript))
2322 }
2323
2324 fn transcript_for_signed_dealer_log<V: Variant, P: PublicKey>(
2325 &self,
2326 info: &Info<V, P>,
2327 log: &DealerLog<V, P>,
2328 ) -> anyhow::Result<(bool, Transcript)> {
2329 let (mut modified, transcript) = self.transcript_for_round(info)?;
2330 let mut transcript = transcript.fork(SIG_LOG);
2331
2332 let mut log_bs = log.encode_mut();
2333 modified |= apply_mask(&mut log_bs, &self.log);
2334 transcript.commit(&mut log_bs);
2335
2336 Ok((modified, transcript))
2337 }
2338 }
2339
2340 #[derive(Debug, Default)]
2342 pub struct Round {
2343 dealers: Vec<u32>,
2344 players: Vec<u32>,
2345 crash_resume_players: BTreeSet<(u32, u32)>,
2346 resume_missing_dealer_msg_fails: BTreeSet<(u32, u32)>,
2347 finalize_missing_dealer_msg_fails: BTreeSet<u32>,
2348 no_acks: BTreeSet<(u32, u32)>,
2349 bad_shares: BTreeSet<(u32, u32)>,
2350 bad_player_sigs: BTreeMap<(u32, u32), Masks>,
2351 bad_reveals: BTreeSet<(u32, u32)>,
2352 bad_dealer_sigs: BTreeMap<u32, Masks>,
2353 replace_shares: BTreeSet<u32>,
2354 shift_degrees: BTreeMap<u32, NonZeroI32>,
2355 }
2356
2357 impl Round {
2358 pub fn new(dealers: Vec<u32>, players: Vec<u32>) -> Self {
2359 Self {
2360 dealers,
2361 players,
2362 ..Default::default()
2363 }
2364 }
2365
2366 pub fn no_ack(mut self, dealer: u32, player: u32) -> Self {
2367 self.no_acks.insert((dealer, player));
2368 self
2369 }
2370
2371 pub fn crash_resume_player(mut self, after_dealer: u32, player: u32) -> Self {
2372 self.crash_resume_players.insert((after_dealer, player));
2373 self
2374 }
2375
2376 pub fn resume_missing_dealer_msg_fails(
2377 mut self,
2378 after_dealer: u32,
2379 missing_dealer: u32,
2380 ) -> Self {
2381 self.resume_missing_dealer_msg_fails
2382 .insert((after_dealer, missing_dealer));
2383 self
2384 }
2385
2386 pub fn finalize_missing_dealer_msg_fails(mut self, player: u32) -> Self {
2387 self.finalize_missing_dealer_msg_fails.insert(player);
2388 self
2389 }
2390
2391 pub fn bad_share(mut self, dealer: u32, player: u32) -> Self {
2392 self.bad_shares.insert((dealer, player));
2393 self
2394 }
2395
2396 pub fn bad_player_sig(mut self, dealer: u32, player: u32, masks: Masks) -> Self {
2397 self.bad_player_sigs.insert((dealer, player), masks);
2398 self
2399 }
2400
2401 pub fn bad_reveal(mut self, dealer: u32, player: u32) -> Self {
2402 self.bad_reveals.insert((dealer, player));
2403 self
2404 }
2405
2406 pub fn bad_dealer_sig(mut self, dealer: u32, masks: Masks) -> Self {
2407 self.bad_dealer_sigs.insert(dealer, masks);
2408 self
2409 }
2410
2411 pub fn replace_share(mut self, dealer: u32) -> Self {
2412 self.replace_shares.insert(dealer);
2413 self
2414 }
2415
2416 pub fn shift_degree(mut self, dealer: u32, shift: NonZeroI32) -> Self {
2417 self.shift_degrees.insert(dealer, shift);
2418 self
2419 }
2420
2421 pub fn validate(
2424 &self,
2425 num_participants: u32,
2426 previous_players: Option<&[u32]>,
2427 ) -> anyhow::Result<()> {
2428 if self.dealers.is_empty() {
2429 return Err(anyhow!("dealers is empty"));
2430 }
2431 if self.players.is_empty() {
2432 return Err(anyhow!("players is empty"));
2433 }
2434 for &d in &self.dealers {
2436 if d >= num_participants {
2437 return Err(anyhow!("dealer {d} out of range [1, {num_participants}]"));
2438 }
2439 }
2440 for &p in &self.players {
2441 if p >= num_participants {
2442 return Err(anyhow!("player {p} out of range [1, {num_participants}]"));
2443 }
2444 }
2445 for &(after_dealer, player) in &self.crash_resume_players {
2447 if !self.dealers.contains(&after_dealer) {
2448 return Err(anyhow!("crash_resume dealer {after_dealer} not in round"));
2449 }
2450 if !self.players.contains(&player) {
2451 return Err(anyhow!("crash_resume player {player} not in round"));
2452 }
2453 }
2454 let dealer_positions: BTreeMap<u32, usize> = self
2455 .dealers
2456 .iter()
2457 .enumerate()
2458 .map(|(idx, &dealer)| (dealer, idx))
2459 .collect();
2460 let previous_successful_round = previous_players.is_some();
2461 for &(after_dealer, missing_dealer) in &self.resume_missing_dealer_msg_fails {
2462 if !self.dealers.contains(&after_dealer) {
2463 return Err(anyhow!("resume_missing dealer {after_dealer} not in round"));
2464 }
2465 if !self.dealers.contains(&missing_dealer) {
2466 return Err(anyhow!(
2467 "resume_missing missing_dealer {missing_dealer} not in round"
2468 ));
2469 }
2470 let after_pos = dealer_positions[&after_dealer];
2471 let missing_pos = dealer_positions[&missing_dealer];
2472 if missing_pos > after_pos {
2473 return Err(anyhow!(
2474 "resume_missing missing_dealer {missing_dealer} appears after {after_dealer}"
2475 ));
2476 }
2477 if self.bad(previous_successful_round, missing_dealer) {
2478 return Err(anyhow!(
2479 "resume_missing_dealer_msg_fails requires dealer {missing_dealer} to be good"
2480 ));
2481 }
2482 let any_valid_ack = self.players.iter().any(|&player| {
2483 let ack_corrupted = self.no_acks.contains(&(missing_dealer, player))
2484 || self.bad_shares.contains(&(missing_dealer, player))
2485 || self
2486 .bad_player_sigs
2487 .get(&(missing_dealer, player))
2488 .is_some_and(Masks::modifies_player_ack);
2489 !ack_corrupted
2490 });
2491 if !any_valid_ack {
2492 return Err(anyhow!(
2493 "resume_missing_dealer_msg_fails requires dealer {missing_dealer} to ack at least one player"
2494 ));
2495 }
2496 }
2497 for &player in &self.finalize_missing_dealer_msg_fails {
2498 if !self.players.contains(&player) {
2499 return Err(anyhow!("finalize_missing player {player} not in round"));
2500 }
2501 }
2502
2503 if let Some(prev_players) = previous_players {
2505 for &d in &self.dealers {
2507 if !prev_players.contains(&d) {
2508 return Err(anyhow!("dealer {d} was not a player in previous round"));
2509 }
2510 }
2511 let required = N3f1::quorum(prev_players.len());
2513 if (self.dealers.len() as u32) < required {
2514 return Err(anyhow!(
2515 "not enough dealers: have {}, need {} (quorum of {} previous players)",
2516 self.dealers.len(),
2517 required,
2518 prev_players.len()
2519 ));
2520 }
2521 }
2522
2523 Ok(())
2524 }
2525
2526 fn bad(&self, previous_successful_round: bool, dealer: u32) -> bool {
2527 if self.replace_shares.contains(&dealer) && previous_successful_round {
2528 return true;
2529 }
2530 if let Some(shift) = self.shift_degrees.get(&dealer) {
2531 let degree = N3f1::quorum(self.players.len()) as i32 - 1;
2532 if (degree + shift.get()).max(0) != degree {
2538 return true;
2539 }
2540 }
2541 if self.bad_reveals.iter().any(|&(d, _)| d == dealer) {
2542 return true;
2543 }
2544 let revealed_players = self
2545 .bad_shares
2546 .iter()
2547 .copied()
2548 .chain(self.no_acks.iter().copied())
2549 .filter_map(|(d, p)| if d == dealer { Some(p) } else { None })
2550 .collect::<BTreeSet<_>>();
2551 revealed_players.len() as u32 > N3f1::max_faults(self.players.len())
2552 }
2553
2554 fn expect_failure(&self, previous_successful_round: Option<u32>) -> bool {
2556 let good_dealer_count = self
2557 .dealers
2558 .iter()
2559 .filter(|&&d| !self.bad(previous_successful_round.is_some(), d))
2560 .count();
2561 let required = previous_successful_round
2562 .map(N3f1::quorum)
2563 .unwrap_or_default()
2564 .max(N3f1::quorum(self.dealers.len())) as usize;
2565 good_dealer_count < required
2566 }
2567 }
2568
2569 #[derive(Debug)]
2571 pub struct Plan {
2572 num_participants: NonZeroU32,
2573 rounds: Vec<Round>,
2574 }
2575
2576 impl Plan {
2577 pub const fn new(num_participants: NonZeroU32) -> Self {
2578 Self {
2579 num_participants,
2580 rounds: Vec::new(),
2581 }
2582 }
2583
2584 pub fn with(mut self, round: Round) -> Self {
2585 self.rounds.push(round);
2586 self
2587 }
2588
2589 pub(crate) fn validate(&self) -> anyhow::Result<()> {
2591 let mut last_successful_players: Option<Vec<u32>> = None;
2592
2593 for round in &self.rounds {
2594 round.validate(
2595 self.num_participants.get(),
2596 last_successful_players.as_deref(),
2597 )?;
2598
2599 if !round.expect_failure(last_successful_players.as_ref().map(|x| x.len() as u32)) {
2601 last_successful_players = Some(round.players.clone());
2602 }
2603 }
2604 Ok(())
2605 }
2606
2607 pub fn run<V: Variant>(self, seed: u64) -> anyhow::Result<()> {
2609 self.validate()?;
2610
2611 let mut rng = TestRng::new(seed);
2612
2613 let keys = (0..self.num_participants.get())
2615 .map(|_| ed25519::PrivateKey::random(&mut rng))
2616 .collect::<Vec<_>>();
2617
2618 let pk_to_key_idx: BTreeMap<ed25519::PublicKey, u32> = keys
2621 .iter()
2622 .enumerate()
2623 .map(|(i, k)| (k.public_key(), i as u32))
2624 .collect();
2625
2626 let max_shift = self
2629 .rounds
2630 .iter()
2631 .flat_map(|r| r.shift_degrees.values())
2632 .map(|s| s.get())
2633 .max()
2634 .unwrap_or(0)
2635 .max(0) as u32;
2636 let max_read_size =
2637 NonZeroU32::new(self.num_participants.get() + max_shift).expect("non-zero");
2638
2639 let mut previous_output: Option<Output<V, ed25519::PublicKey>> = None;
2640 let mut shares: BTreeMap<ed25519::PublicKey, Share> = BTreeMap::new();
2641 let mut threshold_public_key: Option<V::Public> = None;
2642
2643 for (i_round, round) in self.rounds.into_iter().enumerate() {
2644 let previous_successful_round =
2645 previous_output.as_ref().map(|o| o.players.len() as u32);
2646
2647 let dealer_set = round
2648 .dealers
2649 .iter()
2650 .map(|&i| keys[i as usize].public_key())
2651 .try_collect::<Set<_>>()
2652 .unwrap();
2653 let player_set: Set<ed25519::PublicKey> = round
2654 .players
2655 .iter()
2656 .map(|&i| keys[i as usize].public_key())
2657 .try_collect()
2658 .unwrap();
2659
2660 let info = Info::new::<N3f1>(
2662 b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
2663 i_round as u64,
2664 previous_output.clone(),
2665 Mode::NonZeroCounter,
2666 Reveal::V1,
2667 dealer_set.clone(),
2668 player_set.clone(),
2669 )?;
2670
2671 let mut players: Map<_, _> = round
2672 .players
2673 .iter()
2674 .map(|&i| {
2675 let sk = keys[i as usize].clone();
2676 let pk = sk.public_key();
2677 let player = Player::new(info.clone(), sk)?;
2678 Ok((pk, player))
2679 })
2680 .collect::<anyhow::Result<Vec<_>>>()?
2681 .try_into()
2682 .unwrap();
2683 let mut acked_dealings: BTreeMap<
2684 ed25519::PublicKey,
2685 Vec<(ed25519::PublicKey, DealerPubMsg<V>, DealerPrivMsg)>,
2686 > = player_set
2687 .iter()
2688 .cloned()
2689 .map(|pk| (pk, Vec::new()))
2690 .collect();
2691 let mut crash_resume_by_dealer: BTreeMap<u32, Vec<u32>> = BTreeMap::new();
2692 for &(after_dealer, player) in &round.crash_resume_players {
2693 crash_resume_by_dealer
2694 .entry(after_dealer)
2695 .or_default()
2696 .push(player);
2697 }
2698 let mut resume_missing_msg_by_dealer: BTreeMap<u32, Vec<u32>> = BTreeMap::new();
2699 for &(after_dealer, missing_dealer) in &round.resume_missing_dealer_msg_fails {
2700 resume_missing_msg_by_dealer
2701 .entry(after_dealer)
2702 .or_default()
2703 .push(missing_dealer);
2704 }
2705
2706 let mut dealer_logs = BTreeMap::new();
2708 for &i_dealer in &round.dealers {
2709 let sk = keys[i_dealer as usize].clone();
2710 let pk = sk.public_key();
2711 let share = match (shares.get(&pk), round.replace_shares.contains(&i_dealer)) {
2712 (None, _) => None,
2713 (Some(s), false) => Some(s.clone()),
2714 (Some(_), true) => Some(Share::new(
2715 Participant::new(i_dealer),
2716 Private::random(&mut rng),
2717 )),
2718 };
2719
2720 let (mut dealer, pub_msg, mut priv_msgs) =
2722 if let Some(shift) = round.shift_degrees.get(&i_dealer) {
2723 let degree = u32::try_from(info.degree::<N3f1>() as i32 + shift.get())
2725 .unwrap_or_default();
2726
2727 let share = info
2729 .unwrap_or_random_share(
2730 &mut rng,
2731 share.map(|s| s.private.expose_unwrap()),
2732 )
2733 .expect("Failed to generate dealer share");
2734
2735 let my_poly = Poly::new_with_constant(&mut rng, degree, share);
2736 let priv_msgs = info
2737 .players
2738 .iter()
2739 .map(|pk| {
2740 (
2741 pk.clone(),
2742 DealerPrivMsg::new(my_poly.eval_msm(
2743 &info.player_scalar(pk).expect("player should exist"),
2744 &Sequential,
2745 )),
2746 )
2747 })
2748 .collect::<Vec<_>>();
2749 let results: Map<_, _> = priv_msgs
2750 .iter()
2751 .map(|(pk, pm)| (pk.clone(), AckOrReveal::Reveal(pm.clone())))
2752 .try_collect()
2753 .unwrap();
2754 let commitment = Poly::commit(my_poly);
2755 let pub_msg = DealerPubMsg { commitment };
2756 let transcript = {
2757 let t = transcript_for_round(&info);
2758 transcript_for_ack(&t, &pk, &pub_msg)
2759 };
2760 let dealer = Dealer {
2761 me: sk.clone(),
2762 info: info.clone(),
2763 pub_msg: pub_msg.clone(),
2764 results,
2765 transcript,
2766 };
2767 (dealer, pub_msg, priv_msgs)
2768 } else {
2769 Dealer::start::<N3f1>(&mut rng, info.clone(), sk.clone(), share)?
2770 };
2771
2772 for (player, priv_msg) in &mut priv_msgs {
2774 let player_key_idx = pk_to_key_idx[player];
2775 if round.bad_shares.contains(&(i_dealer, player_key_idx)) {
2776 *priv_msg = DealerPrivMsg::new(Scalar::random(&mut rng));
2777 }
2778 }
2779 assert_eq!(priv_msgs.len(), players.len());
2780
2781 let mut num_reveals = players.len() as u32;
2783 for (player_pk, priv_msg) in priv_msgs {
2784 assert_eq!(priv_msg, ReadExt::read(&mut priv_msg.encode())?);
2786
2787 let i_player = players
2788 .index(&player_pk)
2789 .ok_or_else(|| anyhow!("unknown player: {:?}", player_pk))?;
2790 let player_key_idx = pk_to_key_idx[&player_pk];
2791 let player = &mut players.values_mut()[usize::from(i_player)];
2792 let persisted = priv_msg.clone();
2793
2794 let ack = player
2795 .dealer_message::<N3f1>(pk.clone(), pub_msg.clone(), priv_msg)
2796 .ok()
2797 .flatten();
2798 assert_eq!(ack, ReadExt::read(&mut ack.encode())?);
2799 if let Some(ack) = ack {
2800 acked_dealings
2801 .get_mut(&player_pk)
2802 .expect("player should be present")
2803 .push((pk.clone(), pub_msg.clone(), persisted));
2804 let masks = round
2805 .bad_player_sigs
2806 .get(&(i_dealer, player_key_idx))
2807 .cloned()
2808 .unwrap_or_default();
2809 let (modified, transcript) =
2810 masks.transcript_for_player_ack(&info, &pk, &pub_msg)?;
2811 assert_eq!(transcript.verify(&player_pk, &ack.sig), !modified);
2812
2813 if !round.no_acks.contains(&(i_dealer, player_key_idx)) {
2815 dealer.receive_player_ack(player_pk, ack)?;
2816 num_reveals -= 1;
2817 }
2818 } else {
2819 assert!(
2820 round.bad_shares.contains(&(i_dealer, player_key_idx))
2821 || round.bad(previous_successful_round.is_some(), i_dealer)
2822 );
2823 }
2824 }
2825
2826 let signed_log = dealer.finalize::<N3f1>();
2828 assert_eq!(
2829 signed_log,
2830 Read::read_cfg(&mut signed_log.encode(), &max_read_size)?
2831 );
2832
2833 let masks = round
2835 .bad_dealer_sigs
2836 .get(&i_dealer)
2837 .cloned()
2838 .unwrap_or_default();
2839 let (modified, transcript) =
2840 masks.transcript_for_signed_dealer_log(&info, &signed_log.log)?;
2841 assert_eq!(transcript.verify(&pk, &signed_log.sig), !modified);
2842 let (found_pk, mut log) = signed_log
2843 .check(&info)
2844 .ok_or_else(|| anyhow!("signed log should verify"))?;
2845 assert_eq!(pk, found_pk);
2846 match &mut log.results {
2848 DealerResult::TooManyReveals => {
2849 assert!(num_reveals > info.max_reveals::<N3f1>());
2850 }
2851 DealerResult::Ok(results) => {
2852 assert_eq!(results.len(), players.len());
2853 for &i_player in &round.players {
2854 if !round.bad_reveals.contains(&(i_dealer, i_player)) {
2855 continue;
2856 }
2857 let player_pk = keys[i_player as usize].public_key();
2858 *results
2859 .get_value_mut(&player_pk)
2860 .ok_or_else(|| anyhow!("unknown player: {:?}", player_pk))? =
2861 AckOrReveal::Reveal(DealerPrivMsg::new(Scalar::random(
2862 &mut rng,
2863 )));
2864 }
2865 }
2866 }
2867 dealer_logs.insert(pk, log);
2868
2869 for &missing_dealer in resume_missing_msg_by_dealer
2872 .get(&i_dealer)
2873 .into_iter()
2874 .flatten()
2875 {
2876 assert!(
2877 !round.bad(previous_successful_round.is_some(), missing_dealer),
2878 "resume_missing_dealer_msg_fails requires dealer {missing_dealer} to be good"
2879 );
2880 let missing_pk = keys[missing_dealer as usize].public_key();
2881 let missing_log = dealer_logs
2882 .get(&missing_pk)
2883 .unwrap_or_else(|| panic!("missing dealer log for {:?}", missing_pk));
2884 for &i_player in &round.players {
2885 let player_pk = keys[i_player as usize].public_key();
2886 let was_acked = missing_log.get_ack(&player_pk).is_some();
2887
2888 let replay = acked_dealings
2889 .get(&player_pk)
2890 .cloned()
2891 .expect("player should be present");
2892 let replay_without = replay
2893 .into_iter()
2894 .filter(|(dealer, _, _)| dealer != &missing_pk);
2895 let player_sk = keys[i_player as usize].clone();
2896 let resumed = Player::resume::<N3f1>(
2897 info.clone(),
2898 player_sk,
2899 &dealer_logs,
2900 replay_without,
2901 );
2902 if was_acked {
2903 assert!(
2904 matches!(resumed, Err(Error::MissingPlayerDealing)),
2905 "resume without dealer {missing_dealer} message should report MissingPlayerDealing for player {i_player}"
2906 );
2907 } else {
2908 assert!(
2909 resumed.is_ok(),
2910 "resume without dealer {missing_dealer} message should succeed for unacked player {i_player}"
2911 );
2912 }
2913 }
2914 }
2915
2916 for &i_player in crash_resume_by_dealer.get(&i_dealer).into_iter().flatten() {
2918 let player_pk = keys[i_player as usize].public_key();
2919 let player_sk = keys[i_player as usize].clone();
2920 let replay = acked_dealings
2921 .get(&player_pk)
2922 .cloned()
2923 .expect("player should be present");
2924 let (resumed, _) =
2925 Player::resume::<N3f1>(info.clone(), player_sk, &dealer_logs, replay)
2926 .expect("player resume perturbation should succeed");
2927 *players
2928 .get_value_mut(&player_pk)
2929 .expect("player should be present") = resumed;
2930 }
2931 }
2932
2933 let mut logs = Logs::<_, _, N3f1>::new(info.clone());
2935 for (dealer, log) in &dealer_logs {
2936 logs.record(dealer.clone(), log.clone());
2937 }
2938 let selection = logs.clone().select::<ed25519::Batch>(&mut rng, &Sequential);
2939 if let Ok(ref selection) = selection {
2940 let good_pks = selection
2941 .1
2942 .iter_pairs()
2943 .map(|(pk, _)| pk.clone())
2944 .collect::<BTreeSet<_>>();
2945 for &i_dealer in &round.dealers {
2946 if round.bad(previous_successful_round.is_some(), i_dealer) {
2947 assert!(!good_pks.contains(&keys[i_dealer as usize].public_key()));
2948 }
2949 }
2950 }
2951 let observe_result =
2953 observe::<_, _, N3f1, ed25519::Batch>(&mut rng, logs.clone(), &Sequential);
2954 if round.expect_failure(previous_successful_round) {
2955 assert!(
2956 observe_result.is_err(),
2957 "Round {i_round} should have failed but succeeded",
2958 );
2959 continue;
2960 }
2961 let observer_output = observe_result?;
2962 let selection = selection.expect("select should succeed if observe succeeded");
2963
2964 let required_commitments = info.required_commitments::<N3f1>() as usize;
2967 let expected_dealers: Set<ed25519::PublicKey> = dealer_set
2968 .iter()
2969 .filter(|pk| {
2970 let i = keys.iter().position(|k| &k.public_key() == *pk).unwrap() as u32;
2971 !round.bad(previous_successful_round.is_some(), i)
2972 })
2973 .take(required_commitments)
2974 .cloned()
2975 .try_collect()
2976 .expect("dealers are unique");
2977 let expected_dealer_indices: BTreeSet<u32> = expected_dealers
2978 .iter()
2979 .filter_map(|pk| {
2980 keys.iter()
2981 .position(|k| &k.public_key() == pk)
2982 .map(|i| i as u32)
2983 })
2984 .collect();
2985 assert_eq!(
2986 observer_output.dealers(),
2987 &expected_dealers,
2988 "Output dealers should match expected good dealers"
2989 );
2990
2991 let selected_dealers: BTreeSet<u32> = selection
2993 .1
2994 .keys()
2995 .iter()
2996 .filter_map(|pk| {
2997 keys.iter()
2998 .position(|k| &k.public_key() == pk)
2999 .map(|i| i as u32)
3000 })
3001 .collect();
3002 assert_eq!(
3003 selected_dealers, expected_dealer_indices,
3004 "Selection should match expected dealers"
3005 );
3006 let selected_players: Set<ed25519::PublicKey> = round
3007 .players
3008 .iter()
3009 .map(|&i| keys[i as usize].public_key())
3010 .try_collect()
3011 .expect("players are unique");
3012 for &i_player in &round.finalize_missing_dealer_msg_fails {
3013 let player_pk = keys[i_player as usize].public_key();
3014 let player_sk = keys[i_player as usize].clone();
3015 let mut tested = 0u32;
3016 for &dealer_idx in &selected_dealers {
3017 if round.bad(previous_successful_round.is_some(), dealer_idx) {
3018 continue;
3019 }
3020 let dealer_pk = keys[dealer_idx as usize].public_key();
3021 let dealer_log = dealer_logs
3022 .get(&dealer_pk)
3023 .unwrap_or_else(|| panic!("missing dealer log for {:?}", dealer_pk));
3024 if dealer_log.get_ack(&player_pk).is_none() {
3025 continue;
3026 }
3027 let replay = acked_dealings
3028 .get(&player_pk)
3029 .cloned()
3030 .expect("player should be present");
3031 let replay_without = replay
3032 .into_iter()
3033 .filter(|(dealer, _, _)| dealer != &dealer_pk);
3034 let resume_logs: BTreeMap<_, _> = dealer_logs
3035 .iter()
3036 .filter(|(dealer, _)| *dealer != &dealer_pk)
3037 .map(|(dealer, log)| (dealer.clone(), log.clone()))
3038 .collect();
3039 let (resumed, _) = Player::resume::<N3f1>(
3040 info.clone(),
3041 player_sk.clone(),
3042 &resume_logs,
3043 replay_without,
3044 )
3045 .expect("resume should succeed with stale logs");
3046 let finalize_res = resumed.finalize::<N3f1, ed25519::Batch>(
3047 &mut rng,
3048 logs.clone(),
3049 &Sequential,
3050 );
3051 assert!(
3052 matches!(
3053 finalize_res,
3054 Err(FinalizeError::Error(Error::MissingPlayerDealing))
3055 ),
3056 "finalize without dealer {dealer_idx} message should return MissingPlayerDealing for player {i_player}"
3057 );
3058 tested += 1;
3059 }
3060 assert!(
3061 tested > 0,
3062 "finalize_missing_dealer_msg_fails for player {i_player} tested no dealers"
3063 );
3064 }
3065
3066 let mut expected_reveals: BTreeMap<ed25519::PublicKey, u32> = BTreeMap::new();
3072 for &(dealer_idx, player_key_idx) in round.no_acks.union(&round.bad_shares) {
3073 if !selected_dealers.contains(&dealer_idx) {
3074 continue;
3075 }
3076 let pk = keys[player_key_idx as usize].public_key();
3077 if selected_players.position(&pk).is_none() {
3078 continue;
3079 }
3080 *expected_reveals.entry(pk).or_insert(0) += 1;
3081 }
3082
3083 let reveal_threshold = info.reveal_threshold::<N3f1>();
3085 for player in player_set.iter() {
3086 let expected =
3087 expected_reveals.get(player).copied().unwrap_or(0) >= reveal_threshold;
3088 let actual = observer_output.revealed().position(player).is_some();
3089 assert_eq!(
3090 expected, actual,
3091 "Unexpected outcome for player {player:?} (expected={expected}, actual={actual})"
3092 );
3093 }
3094
3095 for (player_pk, player) in players.into_iter() {
3097 let (player_output, share) = player
3098 .finalize::<N3f1, ed25519::Batch>(&mut rng, logs.clone(), &Sequential)
3099 .expect("Player finalize should succeed");
3100
3101 assert_eq!(
3102 player_output, observer_output,
3103 "Player output should match observer output"
3104 );
3105
3106 let expected_public = observer_output
3108 .public
3109 .partial_public(share.index)
3110 .expect("share index should be valid");
3111 let actual_public = share.public::<V>();
3112 assert_eq!(
3113 expected_public, actual_public,
3114 "Share should match public polynomial"
3115 );
3116
3117 shares.insert(player_pk.clone(), share);
3118 }
3119
3120 let current_public = *observer_output.public().public();
3122 match threshold_public_key {
3123 None => threshold_public_key = Some(current_public),
3124 Some(tpk) => {
3125 assert_eq!(
3126 tpk, current_public,
3127 "Public key should remain constant across reshares"
3128 );
3129 }
3130 }
3131
3132 let test_message = format!("test message round {i_round}").into_bytes();
3134 let namespace = b"test";
3135
3136 let mut partial_sigs = Vec::new();
3137 for &i_player in &round.players {
3138 let share = &shares[&keys[i_player as usize].public_key()];
3139 let partial_sig = threshold::sign_message::<V>(share, namespace, &test_message);
3140
3141 threshold::verify_message::<V>(
3142 &observer_output.public,
3143 namespace,
3144 &test_message,
3145 &partial_sig,
3146 )
3147 .expect("Partial signature verification should succeed");
3148
3149 partial_sigs.push(partial_sig);
3150 }
3151
3152 let threshold = observer_output.quorum::<N3f1>();
3153 let threshold_sig = threshold::recover(
3154 &observer_output.public,
3155 &partial_sigs[0..threshold as usize],
3156 &Sequential,
3157 )
3158 .expect("Should recover threshold signature");
3159
3160 ops::verify_message::<V>(
3162 threshold_public_key.as_ref().unwrap(),
3163 namespace,
3164 &test_message,
3165 &threshold_sig,
3166 )
3167 .expect("Threshold signature verification should succeed");
3168
3169 previous_output = Some(observer_output);
3171 }
3172 Ok(())
3173 }
3174 }
3175
3176 #[cfg(feature = "arbitrary")]
3177 mod impl_arbitrary {
3178 use super::*;
3179 use arbitrary::{Arbitrary, Unstructured};
3180 use core::ops::ControlFlow;
3181
3182 const MAX_NUM_PARTICIPANTS: u32 = 20;
3183 const MAX_ROUNDS: u32 = 10;
3184
3185 fn arbitrary_masks<'a>(u: &mut Unstructured<'a>) -> arbitrary::Result<Masks> {
3186 Ok(Masks {
3187 info_summary: Arbitrary::arbitrary(u)?,
3188 dealer: Arbitrary::arbitrary(u)?,
3189 pub_msg: Arbitrary::arbitrary(u)?,
3190 log: Arbitrary::arbitrary(u)?,
3191 })
3192 }
3193
3194 fn pick<'a, T>(
3199 u: &mut Unstructured<'a>,
3200 num: usize,
3201 mut data: Vec<T>,
3202 ) -> arbitrary::Result<Vec<T>> {
3203 let len = data.len();
3204 let num = num.min(len);
3205 for start in 0..num {
3207 data.swap(start, u.int_in_range(start..=len - 1)?);
3208 }
3209 data.truncate(num);
3210 Ok(data)
3211 }
3212
3213 fn arbitrary_round<'a>(
3214 u: &mut Unstructured<'a>,
3215 num_participants: u32,
3216 last_successful_players: Option<&Set<u32>>,
3217 ) -> arbitrary::Result<Round> {
3218 let dealers = if let Some(players) = last_successful_players {
3219 let to_pick = u.int_in_range(players.quorum::<N3f1>() as usize..=players.len())?;
3220 pick(u, to_pick, players.into_iter().copied().collect())?
3221 } else {
3222 let to_pick = u.int_in_range(1..=num_participants as usize)?;
3223 pick(u, to_pick, (0..num_participants).collect())?
3224 };
3225 let players = {
3226 let to_pick = u.int_in_range(1..=num_participants as usize)?;
3227 pick(u, to_pick, (0..num_participants).collect())?
3228 };
3229 let pairs = dealers
3230 .iter()
3231 .flat_map(|d| players.iter().map(|p| (*d, *p)))
3232 .collect::<Vec<_>>();
3233 let pick_pair_set = |u: &mut Unstructured<'a>| {
3234 let num = u.int_in_range(0..=pairs.len())?;
3235 if num == 0 {
3236 return Ok(BTreeSet::new());
3237 }
3238 Ok(pick(u, num, pairs.clone())?.into_iter().collect())
3239 };
3240 let pick_dealer_set = |u: &mut Unstructured<'a>| {
3241 let num = u.int_in_range(0..=dealers.len())?;
3242 if num == 0 {
3243 return Ok(BTreeSet::new());
3244 }
3245 Ok(pick(u, num, dealers.clone())?.into_iter().collect())
3246 };
3247 let round = Round {
3248 crash_resume_players: BTreeSet::new(),
3249 resume_missing_dealer_msg_fails: BTreeSet::new(),
3250 finalize_missing_dealer_msg_fails: BTreeSet::new(),
3251 no_acks: pick_pair_set(u)?,
3252 bad_shares: pick_pair_set(u)?,
3253 bad_player_sigs: {
3254 let indices = pick_pair_set(u)?;
3255 indices
3256 .into_iter()
3257 .map(|k| Ok((k, arbitrary_masks(u)?)))
3258 .collect::<arbitrary::Result<_>>()?
3259 },
3260 bad_reveals: pick_pair_set(u)?,
3261 bad_dealer_sigs: {
3262 let indices = pick_dealer_set(u)?;
3263 indices
3264 .into_iter()
3265 .map(|k| Ok((k, arbitrary_masks(u)?)))
3266 .collect::<arbitrary::Result<_>>()?
3267 },
3268 replace_shares: pick_dealer_set(u)?,
3269 shift_degrees: {
3270 let indices = pick_dealer_set(u)?;
3271 indices
3272 .into_iter()
3273 .map(|k| {
3274 let expected = N3f1::quorum(players.len()) as i32 - 1;
3275 let shift = u.int_in_range(1..=expected.max(1))?;
3276 let shift = if bool::arbitrary(u)? { -shift } else { shift };
3277 Ok((k, NonZeroI32::new(shift).expect("checked to not be zero")))
3278 })
3279 .collect::<arbitrary::Result<_>>()?
3280 },
3281 dealers,
3282 players,
3283 };
3284 Ok(round)
3285 }
3286
3287 impl<'a> Arbitrary<'a> for Plan {
3288 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
3289 let num_participants = u.int_in_range(1..=MAX_NUM_PARTICIPANTS)?;
3290 let mut rounds = Vec::new();
3291 let mut last_successful_players: Option<Set<u32>> = None;
3292 u.arbitrary_loop(None, Some(MAX_ROUNDS), |u| {
3293 let round =
3294 arbitrary_round(u, num_participants, last_successful_players.as_ref())?;
3295 if !round
3296 .expect_failure(last_successful_players.as_ref().map(|x| x.len() as u32))
3297 {
3298 last_successful_players =
3299 Some(round.players.iter().copied().try_collect().unwrap());
3300 }
3301 rounds.push(round);
3302 Ok(ControlFlow::Continue(()))
3303 })?;
3304 let plan = Self {
3305 num_participants: NZU32!(num_participants),
3306 rounds,
3307 };
3308 plan.validate()
3309 .map_err(|_| arbitrary::Error::IncorrectFormat)?;
3310 Ok(plan)
3311 }
3312 }
3313 }
3314}
3315
3316#[cfg(feature = "arbitrary")]
3317pub use test_plan::Plan as FuzzPlan;
3318
3319#[cfg(test)]
3320mod test {
3321 use super::{test_plan::*, *};
3322 use crate::{bls12381::primitives::variant::MinPk, ed25519};
3323 use anyhow::anyhow;
3324 use arbitrary::{Arbitrary, Unstructured};
3325 use commonware_invariants::minifuzz;
3326 use commonware_utils::{N3f1, TestRng, test_rng};
3327 use core::num::NonZeroI32;
3328
3329 const PRE_VERIFY_DEALERS: usize = 8;
3330 type PreVerifyLog = DealerLog<MinPk, ed25519::PublicKey>;
3331 type PreVerifyLogs = Logs<MinPk, ed25519::PublicKey, N3f1>;
3332
3333 fn check_pre_verify_log(
3334 info: &Info<MinPk, ed25519::PublicKey>,
3335 dealer: &ed25519::PublicKey,
3336 log: &PreVerifyLog,
3337 ) -> Result<DealerLogOutcome, DealerLogError> {
3338 let transcript = transcript_for_round(info);
3339 PreVerifyLogs::check_dealers::<ed25519::Batch>(
3340 &mut test_rng(),
3341 info,
3342 &Sequential,
3343 &transcript,
3344 &[(dealer, log)],
3345 )
3346 .pop()
3347 .expect("one dealer should produce one check")
3348 .1
3349 }
3350
3351 fn reshare_info(seed: u64) -> Info<MinPk, ed25519::PublicKey> {
3352 let participants = (seed..seed + 4)
3353 .map(ed25519::PrivateKey::from_seed)
3354 .map(|key| key.public_key())
3355 .try_collect::<Set<_>>()
3356 .expect("participants must be unique");
3357 let (previous, _) = deal::<MinPk, _, N3f1>(
3358 TestRng::new(seed + 4),
3359 Mode::NonZeroCounter,
3360 participants.clone(),
3361 )
3362 .expect("previous dealing must succeed");
3363 Info::new::<N3f1>(
3364 b"specific-reshare-error-test",
3365 1,
3366 Some(previous),
3367 Mode::NonZeroCounter,
3368 Reveal::V1,
3369 participants.clone(),
3370 participants,
3371 )
3372 .expect("reshare info must be valid")
3373 }
3374
3375 fn pub_msg_with_different_constant(
3376 info: &Info<MinPk, ed25519::PublicKey>,
3377 expected: &<MinPk as Variant>::Public,
3378 seed: u64,
3379 ) -> DealerPubMsg<MinPk> {
3380 let degree = info.degree::<N3f1>();
3381 let mut pub_msg = DealerPubMsg::<MinPk> {
3382 commitment: Poly::commit(Poly::new_with_constant(
3383 TestRng::new(seed),
3384 degree,
3385 Scalar::zero(),
3386 )),
3387 };
3388 if pub_msg.commitment.constant() == expected {
3389 pub_msg.commitment = Poly::commit(Poly::new_with_constant(
3390 TestRng::new(seed),
3391 degree,
3392 Scalar::one(),
3393 ));
3394 }
3395 assert_eq!(pub_msg.commitment.degree_exact(), degree);
3396 assert_ne!(pub_msg.commitment.constant(), expected);
3397 pub_msg
3398 }
3399
3400 #[test]
3401 fn info_rejects_inconsistent_previous_output() {
3402 let participants = (0..4)
3403 .map(ed25519::PrivateKey::from_seed)
3404 .map(|key| key.public_key())
3405 .try_collect::<Set<_>>()
3406 .expect("participants must be unique");
3407 let (previous, _) =
3408 deal::<MinPk, _, N3f1>(TestRng::new(4), Mode::NonZeroCounter, participants.clone())
3409 .expect("previous dealing must succeed");
3410
3411 let mut wrong_total = previous.clone();
3412 wrong_total.public = Sharing::<MinPk>::new(
3413 Mode::NonZeroCounter,
3414 NZU32!(1),
3415 Poly::commit(Poly::<Scalar>::new(TestRng::new(5), 2)),
3416 );
3417
3418 let mut excessive_degree = previous;
3419 excessive_degree.public = Sharing::<MinPk>::new(
3420 Mode::NonZeroCounter,
3421 NZU32!(4),
3422 Poly::commit(Poly::<Scalar>::new(TestRng::new(6), 3)),
3423 );
3424
3425 let mut lower_threshold = excessive_degree.clone();
3428 lower_threshold.public = Sharing::<MinPk>::new(
3429 Mode::NonZeroCounter,
3430 NZU32!(4),
3431 Poly::commit(Poly::<Scalar>::new(TestRng::new(7), 1)),
3432 );
3433 assert_eq!(lower_threshold.public.required(), 2);
3434 assert_eq!(lower_threshold.quorum::<N3f1>(), 3);
3435
3436 for previous in [wrong_total, excessive_degree, lower_threshold] {
3437 assert!(matches!(
3438 Info::<MinPk, _>::new::<N3f1>(
3439 b"invalid-previous-output-test",
3440 1,
3441 Some(previous),
3442 Mode::NonZeroCounter,
3443 Reveal::V1,
3444 participants.clone(),
3445 participants.clone(),
3446 ),
3447 Err(Error::InvalidPreviousOutput)
3448 ));
3449 }
3450 }
3451
3452 fn is_revealed_after(
3453 reveal: Reveal,
3454 dealer_count: usize,
3455 player_count: usize,
3456 reveal_count: usize,
3457 ) -> bool {
3458 let keys: Vec<_> = (0..dealer_count + player_count)
3459 .map(|seed| ed25519::PrivateKey::from_seed(seed as u64 + 1_000))
3460 .collect();
3461 let dealer_keys = &keys[..dealer_count];
3462 let player_keys = &keys[dealer_count..];
3463 let dealers: Set<_> = dealer_keys
3464 .iter()
3465 .map(|key| key.public_key())
3466 .try_collect()
3467 .expect("dealers must be unique");
3468 let players: Set<_> = player_keys
3469 .iter()
3470 .map(|key| key.public_key())
3471 .try_collect()
3472 .expect("players must be unique");
3473 let info = Info::<MinPk, _>::new::<N3f1>(
3474 b"reveal-threshold-test",
3475 0,
3476 None,
3477 Mode::NonZeroCounter,
3478 reveal,
3479 dealers,
3480 players,
3481 )
3482 .expect("info must be valid");
3483 let target = player_keys[0].public_key();
3484 let player_keys: BTreeMap<_, _> = player_keys
3485 .iter()
3486 .cloned()
3487 .map(|key| (key.public_key(), key))
3488 .collect();
3489
3490 let required = usize::try_from(info.required_commitments::<N3f1>())
3491 .expect("required commitments exceed usize::MAX");
3492 let mut selected = Vec::new();
3493 for (dealer_number, dealer_key) in dealer_keys.iter().take(required).enumerate() {
3494 let (mut dealer, pub_msg, priv_msgs) = Dealer::start::<N3f1>(
3495 TestRng::new(dealer_number as u64),
3496 info.clone(),
3497 dealer_key.clone(),
3498 None,
3499 )
3500 .expect("dealer must start");
3501 for (player, priv_msg) in priv_msgs {
3502 if dealer_number < reveal_count && player == target {
3503 continue;
3504 }
3505 let mut receiver = Player::<MinPk, _>::new(
3506 info.clone(),
3507 player_keys.get(&player).expect("player must exist").clone(),
3508 )
3509 .expect("player must initialize");
3510 let ack = receiver
3511 .dealer_message::<N3f1>(dealer_key.public_key(), pub_msg.clone(), priv_msg)
3512 .expect("dealing must be valid")
3513 .expect("dealing must be new");
3514 dealer
3515 .receive_player_ack(player, ack)
3516 .expect("ack must be valid");
3517 }
3518 selected.push(
3519 dealer
3520 .finalize::<N3f1>()
3521 .check(&info)
3522 .expect("dealer log must verify"),
3523 );
3524 }
3525 let selected = selected
3526 .into_iter()
3527 .try_collect::<Map<_, _>>()
3528 .expect("dealers must be unique");
3529 let output = Observe::reckon::<N3f1>(info, selected, &Sequential).output;
3530
3531 output.revealed().position(&target).is_some()
3532 }
3533
3534 #[test]
3535 fn reveal_threshold_uses_dealer_set_when_dealers_are_fewer() {
3536 assert!(is_revealed_after(Reveal::V1, 4, 10, 2));
3537 }
3538
3539 #[test]
3540 fn reveal_threshold_uses_dealer_set_when_dealers_are_more() {
3541 assert!(!is_revealed_after(Reveal::V1, 13, 4, 4));
3542 }
3543
3544 #[test]
3545 #[allow(deprecated)]
3546 fn legacy_reveal_threshold_uses_player_set() {
3547 assert!(!is_revealed_after(Reveal::V0, 4, 10, 2));
3548 }
3549
3550 #[test]
3551 #[allow(deprecated)]
3552 fn reveal_v1_is_committed_to_summary() {
3553 let participants = (0..4)
3556 .map(ed25519::PrivateKey::from_seed)
3557 .map(|key| key.public_key())
3558 .try_collect::<Set<_>>()
3559 .expect("participants must be unique");
3560 let info = |mode, reveal| {
3561 Info::<MinPk, _>::new::<N3f1>(
3562 b"reveal-version-transcript-test",
3563 0,
3564 None,
3565 mode,
3566 reveal,
3567 participants.clone(),
3568 participants.clone(),
3569 )
3570 .expect("info must be valid")
3571 };
3572 let v0_non_zero = info(Mode::NonZeroCounter, Reveal::V0);
3573 let v0_roots = info(Mode::RootsOfUnity, Reveal::V0);
3574 let v1_non_zero = info(Mode::NonZeroCounter, Reveal::V1);
3575 let v1_roots = info(Mode::RootsOfUnity, Reveal::V1);
3576
3577 assert_ne!(v0_non_zero.summary, v0_roots.summary);
3580 assert_ne!(v0_non_zero.summary, v1_non_zero.summary);
3581 assert_ne!(v0_non_zero.summary, v1_roots.summary);
3582 assert_ne!(v0_roots.summary, v1_non_zero.summary);
3583 assert_ne!(v0_roots.summary, v1_roots.summary);
3584 assert_ne!(v1_non_zero.summary, v1_roots.summary);
3585
3586 assert_ne!(v0_non_zero, v1_non_zero);
3588 assert_ne!(v0_roots, v1_roots);
3589 }
3590
3591 #[test]
3592 #[allow(deprecated)]
3593 fn reveals_reject_cross_configured_logs() {
3594 let dealer = ed25519::PrivateKey::from_seed(0);
3597 let participants: Set<ed25519::PublicKey> = vec![dealer.public_key()]
3598 .try_into()
3599 .expect("participant must be unique");
3600 let info = |reveal| {
3601 Info::<MinPk, _>::new::<N3f1>(
3602 b"reveal-version-signature-test",
3603 0,
3604 None,
3605 Mode::NonZeroCounter,
3606 reveal,
3607 participants.clone(),
3608 participants.clone(),
3609 )
3610 .expect("info must be valid")
3611 };
3612 let v0 = info(Reveal::V0);
3613 let v1 = info(Reveal::V1);
3614
3615 let (dealer, _, _) = Dealer::start::<N3f1>(TestRng::new(0), v0.clone(), dealer, None)
3617 .expect("dealer must start");
3618 let log = dealer.finalize::<N3f1>();
3619
3620 assert!(log.clone().check(&v0).is_some());
3621 assert!(log.check(&v1).is_none());
3622 }
3623
3624 #[test]
3625 #[allow(deprecated)]
3626 fn observers_match_players_for_each_reveal_calculation() {
3627 fn outputs(
3628 reveal: Reveal,
3629 ) -> (
3630 Output<MinPk, ed25519::PublicKey>,
3631 Output<MinPk, ed25519::PublicKey>,
3632 ed25519::PublicKey,
3633 ) {
3634 const DEALER_COUNT: usize = 4;
3635 const PLAYER_COUNT: usize = 10;
3636 const SELECTED_DEALERS: usize = 3;
3637 const TARGET_REVEALS: usize = 2;
3638
3639 let keys: Vec<_> = (0..DEALER_COUNT + PLAYER_COUNT)
3642 .map(|seed| ed25519::PrivateKey::from_seed(seed as u64 + 3_000))
3643 .collect();
3644 let dealer_keys = &keys[..DEALER_COUNT];
3645 let player_keys = &keys[DEALER_COUNT..];
3646 let dealers = dealer_keys
3647 .iter()
3648 .map(|key| key.public_key())
3649 .try_collect()
3650 .expect("dealers must be unique");
3651 let players = player_keys
3652 .iter()
3653 .map(|key| key.public_key())
3654 .try_collect()
3655 .expect("players must be unique");
3656 let info = Info::<MinPk, _>::new::<N3f1>(
3657 b"public-reveal-calculation-test",
3658 0,
3659 None,
3660 Mode::NonZeroCounter,
3661 reveal,
3662 dealers,
3663 players,
3664 )
3665 .expect("info must be valid");
3666 let target = player_keys[0].public_key();
3667 let finalizer_key = player_keys[1].clone();
3668 let finalizer = finalizer_key.public_key();
3669 let player_keys: BTreeMap<_, _> = player_keys
3670 .iter()
3671 .cloned()
3672 .map(|key| (key.public_key(), key))
3673 .collect();
3674 let mut verified_logs = BTreeMap::new();
3675 let mut logs = Logs::<MinPk, _, N3f1>::new(info.clone());
3676 let mut persisted = Vec::new();
3677
3678 for (dealer_number, dealer_key) in dealer_keys.iter().take(SELECTED_DEALERS).enumerate()
3681 {
3682 let dealer_pk = dealer_key.public_key();
3683 let (mut dealer, pub_msg, priv_msgs) = Dealer::start::<N3f1>(
3684 TestRng::new(dealer_number as u64 + 4_000),
3685 info.clone(),
3686 dealer_key.clone(),
3687 None,
3688 )
3689 .expect("dealer must start");
3690 for (player, priv_msg) in priv_msgs {
3691 if dealer_number < TARGET_REVEALS && player == target {
3692 continue;
3693 }
3694 if player == finalizer {
3695 persisted.push((dealer_pk.clone(), pub_msg.clone(), priv_msg.clone()));
3696 }
3697 let mut receiver = Player::<MinPk, _>::new(
3698 info.clone(),
3699 player_keys.get(&player).expect("player must exist").clone(),
3700 )
3701 .expect("player must initialize");
3702 let ack = receiver
3703 .dealer_message::<N3f1>(dealer_pk.clone(), pub_msg.clone(), priv_msg)
3704 .expect("dealing must be valid")
3705 .expect("dealing must be new");
3706 dealer
3707 .receive_player_ack(player, ack)
3708 .expect("ack must be valid");
3709 }
3710 let (dealer, log) = dealer
3711 .finalize::<N3f1>()
3712 .check(&info)
3713 .expect("dealer log must verify");
3714 verified_logs.insert(dealer.clone(), log.clone());
3715 logs.record(dealer, log);
3716 }
3717
3718 let (player, _) =
3720 Player::resume::<N3f1>(info, finalizer_key, &verified_logs, persisted)
3721 .expect("player must resume");
3722 let observed = observe::<MinPk, _, N3f1, ed25519::Batch>(
3723 &mut test_rng(),
3724 logs.clone(),
3725 &Sequential,
3726 )
3727 .expect("observation must succeed");
3728 let (finalized, _) = player
3729 .finalize::<N3f1, ed25519::Batch>(&mut test_rng(), logs, &Sequential)
3730 .expect("finalization must succeed");
3731 (finalized, observed, target)
3732 }
3733
3734 let (v1_finalized, v1_observed, v1_target) = outputs(Reveal::V1);
3737 let (v0_finalized, v0_observed, v0_target) = outputs(Reveal::V0);
3738
3739 assert_eq!(v1_finalized, v1_observed);
3740 assert_eq!(v0_finalized, v0_observed);
3741 assert!(v1_finalized.revealed().position(&v1_target).is_some());
3742 assert!(v0_finalized.revealed().position(&v0_target).is_none());
3743 }
3744
3745 #[test]
3746 fn reveal_threshold_uses_previous_players_during_reshare() {
3747 let keys: Vec<_> = (0..14)
3748 .map(|seed| ed25519::PrivateKey::from_seed(seed + 2_000))
3749 .collect();
3750 let previous_players = keys[..10]
3751 .iter()
3752 .map(|key| key.public_key())
3753 .try_collect::<Set<_>>()
3754 .expect("previous players must be unique");
3755 let (previous, _) = deal::<MinPk, _, N3f1>(
3756 TestRng::new(0),
3757 Mode::NonZeroCounter,
3758 previous_players.clone(),
3759 )
3760 .expect("previous dealing must succeed");
3761 let dealers = previous_players
3762 .iter()
3763 .take(7)
3764 .cloned()
3765 .try_collect::<Set<_>>()
3766 .expect("dealers must be unique");
3767 let players = keys[10..]
3768 .iter()
3769 .map(|key| key.public_key())
3770 .try_collect::<Set<_>>()
3771 .expect("players must be unique");
3772 let info = Info::new::<N3f1>(
3773 b"reshare-reveal-threshold-test",
3774 1,
3775 Some(previous),
3776 Mode::NonZeroCounter,
3777 Reveal::V1,
3778 dealers,
3779 players,
3780 )
3781 .expect("reshare info must be valid");
3782
3783 assert_eq!(info.required_commitments::<N3f1>(), 7);
3784 assert_eq!(info.reveal_threshold::<N3f1>(), 4);
3785 }
3786
3787 struct PreVerifyDealer {
3788 key: ed25519::PublicKey,
3789 valid: PreVerifyLog,
3790 invalid: PreVerifyLog,
3791 }
3792
3793 struct PreVerifyFixture {
3794 info: Info<MinPk, ed25519::PublicKey>,
3795 wrong_info: Info<MinPk, ed25519::PublicKey>,
3796 dealers: Vec<PreVerifyDealer>,
3797 }
3798
3799 impl PreVerifyFixture {
3800 fn new() -> Self {
3801 fn pre_verify_test_keys() -> Vec<ed25519::PrivateKey> {
3802 (0..PRE_VERIFY_DEALERS as u64)
3803 .map(ed25519::PrivateKey::from_seed)
3804 .collect()
3805 }
3806
3807 fn pre_verify_test_info(
3808 keys: &[ed25519::PrivateKey],
3809 round: u64,
3810 ) -> Info<MinPk, ed25519::PublicKey> {
3811 let dealers: Set<_> = keys
3812 .iter()
3813 .map(|sk| sk.public_key())
3814 .try_collect()
3815 .expect("dealers must be unique");
3816 Info::<MinPk, _>::new::<N3f1>(
3817 b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
3818 round,
3819 None,
3820 Mode::NonZeroCounter,
3821 Reveal::V1,
3822 dealers.clone(),
3823 dealers,
3824 )
3825 .expect("info must be valid")
3826 }
3827
3828 fn generate_dealer_log(
3829 info: &Info<MinPk, ed25519::PublicKey>,
3830 keys: &[ed25519::PrivateKey],
3831 dealer_index: usize,
3832 seed: u64,
3833 ) -> DealerLog<MinPk, ed25519::PublicKey> {
3834 let mut players: BTreeMap<_, _> = keys
3835 .iter()
3836 .cloned()
3837 .map(|sk| {
3838 let pk = sk.public_key();
3839 (
3840 pk,
3841 Player::<MinPk, _>::new(info.clone(), sk)
3842 .expect("player initialization must succeed"),
3843 )
3844 })
3845 .collect();
3846
3847 let dealer_sk = keys[dealer_index].clone();
3848 let dealer_pk = dealer_sk.public_key();
3849 let mut rng = TestRng::new(seed);
3850 let (mut dealer, pub_msg, priv_msgs) =
3851 Dealer::start::<N3f1>(&mut rng, info.clone(), dealer_sk, None)
3852 .expect("dealer initialization must succeed");
3853 for (player_pk, priv_msg) in priv_msgs {
3854 let ack = players
3855 .get_mut(&player_pk)
3856 .expect("player should exist")
3857 .dealer_message::<N3f1>(dealer_pk.clone(), pub_msg.clone(), priv_msg)
3858 .expect("dealer message must succeed")
3859 .expect("dealer message must be new");
3860 dealer
3861 .receive_player_ack(player_pk, ack)
3862 .expect("ack handling must succeed");
3863 }
3864 dealer
3865 .finalize::<N3f1>()
3866 .check(info)
3867 .expect("signed dealer log must verify against its own info")
3868 .1
3869 }
3870
3871 let keys = pre_verify_test_keys();
3872 let info = pre_verify_test_info(&keys, 0);
3873 let wrong_info = pre_verify_test_info(&keys, 1);
3874 let mut logs_by_key: BTreeMap<_, _> = keys
3875 .iter()
3876 .enumerate()
3877 .map(|(dealer_index, sk)| {
3878 let key = sk.public_key();
3879 let seed = dealer_index as u64;
3880 let valid = generate_dealer_log(&info, &keys, dealer_index, seed);
3881 let invalid = generate_dealer_log(&wrong_info, &keys, dealer_index, seed);
3882 assert_eq!(
3883 valid.pub_msg, invalid.pub_msg,
3884 "wrong-info log generation should only change transcript-bound signatures"
3885 );
3886 (key, (valid, invalid))
3887 })
3888 .collect();
3889 let dealers = info
3890 .dealers
3891 .iter()
3892 .cloned()
3893 .map(|key| {
3894 let (valid, invalid) = logs_by_key
3895 .remove(&key)
3896 .expect("fixture should include every dealer");
3897 PreVerifyDealer {
3898 key,
3899 valid,
3900 invalid,
3901 }
3902 })
3903 .collect();
3904 Self {
3905 info,
3906 wrong_info,
3907 dealers,
3908 }
3909 }
3910
3911 fn required_commitments(&self) -> usize {
3912 usize::try_from(self.info.required_commitments::<N3f1>())
3913 .expect("required commitments exceed usize::MAX")
3914 }
3915
3916 fn expected(&self, valid: &[bool]) -> Set<ed25519::PublicKey> {
3917 assert_eq!(
3918 valid.len(),
3919 self.dealers.len(),
3920 "fixture size should match case"
3921 );
3922 self.dealers
3923 .iter()
3924 .zip(valid.iter().copied())
3925 .filter(|(_, is_valid)| *is_valid)
3926 .take(self.required_commitments())
3927 .map(|(dealer, _)| dealer.key.clone())
3928 .try_collect()
3929 .expect("dealers must be unique")
3930 }
3931
3932 fn record(&self, logs: &mut PreVerifyLogs, dealer_index: usize, is_valid: bool) {
3933 let dealer = &self.dealers[dealer_index];
3934 let log = if is_valid {
3935 dealer.valid.clone()
3936 } else {
3937 dealer.invalid.clone()
3938 };
3939 logs.record(dealer.key.clone(), log);
3940 }
3941
3942 fn logs_for(
3943 &self,
3944 info: &Info<MinPk, ed25519::PublicKey>,
3945 valid: &[bool],
3946 ) -> PreVerifyLogs {
3947 assert_eq!(
3948 valid.len(),
3949 self.dealers.len(),
3950 "fixture size should match case"
3951 );
3952 let mut logs = PreVerifyLogs::new(info.clone());
3953 for (dealer_index, &is_valid) in valid.iter().enumerate() {
3954 self.record(&mut logs, dealer_index, is_valid);
3955 }
3956 logs
3957 }
3958 }
3959
3960 #[derive(Debug)]
3961 struct IncrementalPreVerifyCase {
3962 valid: [bool; PRE_VERIFY_DEALERS],
3963 batches: Vec<Vec<usize>>,
3964 }
3965
3966 impl<'a> Arbitrary<'a> for IncrementalPreVerifyCase {
3967 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
3968 let mut valid = [false; PRE_VERIFY_DEALERS];
3969 for is_valid in &mut valid {
3970 *is_valid = u.arbitrary()?;
3971 }
3972
3973 let mut order: Vec<_> = (0..valid.len()).collect();
3974 for index in 0..valid.len() {
3975 let last = order.len() - 1;
3976 order.swap(index, u.int_in_range(index..=last)?);
3977 }
3978
3979 let mut batches = Vec::new();
3980 let mut start = 0;
3981 while start < order.len() {
3982 let batch_size = u.int_in_range(1..=order.len() - start)?;
3983 batches.push(order[start..start + batch_size].to_vec());
3984 start += batch_size;
3985 }
3986
3987 Ok(Self { valid, batches })
3988 }
3989 }
3990
3991 impl IncrementalPreVerifyCase {
3992 fn run(self, fixture: &PreVerifyFixture) -> arbitrary::Result<()> {
3993 let required_commitments = fixture.required_commitments();
3994 let expected = fixture.expected(&self.valid);
3995 let fresh = fixture.logs_for(&fixture.info, &self.valid);
3996
3997 let mut incremental = PreVerifyLogs::new(fixture.info.clone());
3998 let mut incremental_rng = test_rng();
3999 for batch in &self.batches {
4000 for &dealer_index in batch {
4001 fixture.record(&mut incremental, dealer_index, self.valid[dealer_index]);
4002 }
4003 incremental.pre_verify::<ed25519::Batch>(&mut incremental_rng, &Sequential);
4004 }
4005
4006 let mut fresh_rng = test_rng();
4007 let fresh_selected = fresh
4008 .select::<ed25519::Batch>(&mut fresh_rng, &Sequential)
4009 .map(|(_, selection)| selection.keys().clone());
4010 let incremental_selected = incremental
4011 .select::<ed25519::Batch>(&mut incremental_rng, &Sequential)
4012 .map(|(_, selection)| selection.keys().clone());
4013
4014 match &fresh_selected {
4015 Ok(selected) => assert_eq!(
4016 selected, &expected,
4017 "all-at-once selection disagreed with validity mask: {:?}",
4018 self
4019 ),
4020 Err(_) => assert!(
4021 expected.len() < required_commitments,
4022 "all-at-once selection failed despite quorum-sized expected set: {:?}",
4023 self
4024 ),
4025 }
4026
4027 match (fresh_selected, incremental_selected) {
4028 (Err(_), Err(_)) => {}
4029 (Ok(fresh_selected), Ok(incremental_selected)) => assert_eq!(
4030 incremental_selected, fresh_selected,
4031 "incremental selection disagreed with all-at-once selection: {:?}",
4032 self
4033 ),
4034 (Ok(fresh_selected), Err(err)) => panic!(
4035 "incremental selection failed with {err:?} but all-at-once selected {fresh_selected:?}: {self:?}"
4036 ),
4037 (Err(err), Ok(incremental_selected)) => panic!(
4038 "incremental selection returned {incremental_selected:?} but all-at-once failed with {err:?}: {self:?}"
4039 ),
4040 }
4041
4042 Ok(())
4043 }
4044 }
4045
4046 #[test]
4047 fn incremental_pre_verify_preserves_dealer_order() {
4048 let fixture = PreVerifyFixture::new();
4049 minifuzz::test(move |u| u.arbitrary::<IncrementalPreVerifyCase>()?.run(&fixture));
4050 }
4051
4052 #[test]
4053 fn check_dealers_reports_invalid_reveal() {
4054 let fixture = PreVerifyFixture::new();
4055 let dealer = &fixture.dealers[0];
4056 let mut log = dealer.valid.clone();
4057 let DealerResult::Ok(results) = &mut log.results else {
4058 panic!("valid fixture should contain player results");
4059 };
4060 let player = fixture.info.players.iter().next().unwrap().clone();
4061 *results.get_value_mut(&player).unwrap() =
4062 AckOrReveal::Reveal(DealerPrivMsg::new(Scalar::zero()));
4063
4064 let result = check_pre_verify_log(&fixture.info, &dealer.key, &log);
4065 assert!(matches!(
4066 result,
4067 Err(DealerLogError::Fault(FaultReason::InvalidReveal))
4068 ));
4069 }
4070
4071 #[test]
4072 fn dealer_log_identifies_unexpected_dealer() {
4073 let fixture = PreVerifyFixture::new();
4074 let dealer = &fixture.dealers[0];
4075 let stranger = ed25519::PrivateKey::from_seed(u64::MAX).public_key();
4076 assert!(fixture.info.dealer_index(&stranger).is_none());
4077 assert!(matches!(
4078 check_pre_verify_log(&fixture.info, &stranger, &dealer.valid),
4079 Err(DealerLogError::UnexpectedDealer)
4080 ));
4081 }
4082
4083 #[test]
4084 fn dealer_public_message_distinguishes_fault_reasons() {
4085 let fixture = PreVerifyFixture::new();
4086 let dealer = &fixture.dealers[0];
4087 let expected = fixture.info.degree::<N3f1>();
4088 let actual = 0;
4089 assert_ne!(expected, actual);
4090 let log = DealerLog {
4091 pub_msg: DealerPubMsg::<MinPk> {
4092 commitment: Poly::commit(Poly::new_with_constant(
4093 TestRng::new(10_000),
4094 actual,
4095 Scalar::one(),
4096 )),
4097 },
4098 results: dealer.valid.results.clone(),
4099 };
4100 assert!(matches!(
4101 check_pre_verify_log(&fixture.info, &dealer.key, &log),
4102 Err(DealerLogError::Fault(
4103 FaultReason::InvalidCommitmentDegree {
4104 expected: error_expected,
4105 actual: error_actual,
4106 }
4107 )) if error_expected == expected && error_actual == actual
4108 ));
4109
4110 let info = reshare_info(20_000);
4111 let dealer = info.dealers.iter().next().unwrap().clone();
4112 let expected = info
4113 .previous
4114 .as_ref()
4115 .unwrap()
4116 .share_commitment(&dealer)
4117 .unwrap();
4118 let pub_msg = pub_msg_with_different_constant(&info, &expected, 20_005);
4119 let log = DealerLog {
4120 pub_msg,
4121 results: DealerResult::TooManyReveals,
4122 };
4123 assert!(matches!(
4124 check_pre_verify_log(&info, &dealer, &log),
4125 Err(DealerLogError::Fault(
4126 FaultReason::MismatchedReshareCommitment
4127 ))
4128 ));
4129 }
4130
4131 #[test]
4132 fn dealer_public_message_distinguishes_live_errors() -> anyhow::Result<()> {
4133 let fixture = PreVerifyFixture::new();
4135 let dealer = fixture.dealers[0].key.clone();
4136 let expected = fixture.info.degree::<N3f1>();
4137 let actual = 0;
4138 assert_ne!(expected, actual);
4139 let pub_msg = DealerPubMsg::<MinPk> {
4140 commitment: Poly::commit(Poly::new_with_constant(
4141 TestRng::new(10_000),
4142 actual,
4143 Scalar::one(),
4144 )),
4145 };
4146 let mut player = Player::new(fixture.info, ed25519::PrivateKey::from_seed(0))?;
4147 assert!(matches!(
4148 player.dealer_message::<N3f1>(
4149 dealer,
4150 pub_msg,
4151 DealerPrivMsg::new(Scalar::zero()),
4152 ),
4153 Err(DealerMessageError::InvalidCommitmentDegree {
4154 expected: error_expected,
4155 actual: error_actual,
4156 }) if error_expected == expected && error_actual == actual
4157 ));
4158
4159 let info = reshare_info(20_000);
4161 let dealer = info.dealers.iter().next().unwrap().clone();
4162 let expected = info
4163 .previous
4164 .as_ref()
4165 .unwrap()
4166 .share_commitment(&dealer)
4167 .unwrap();
4168 let pub_msg = pub_msg_with_different_constant(&info, &expected, 20_005);
4169 let mut player = Player::new(info, ed25519::PrivateKey::from_seed(20_000))?;
4170 assert!(matches!(
4171 player.dealer_message::<N3f1>(dealer, pub_msg, DealerPrivMsg::new(Scalar::zero()),),
4172 Err(DealerMessageError::MismatchedReshareCommitment)
4173 ));
4174
4175 Ok(())
4176 }
4177
4178 #[test]
4179 fn dealer_log_distinguishes_unavailable_and_faulty_outcomes() {
4180 let fixture = PreVerifyFixture::new();
4181 let dealer = &fixture.dealers[0];
4182
4183 assert!(matches!(
4184 check_pre_verify_log(&fixture.info, &dealer.key, &dealer.valid),
4185 Ok(DealerLogOutcome::Available)
4186 ));
4187
4188 let mut log = dealer.valid.clone();
4189 let DealerResult::Ok(results) = &mut log.results else {
4190 panic!("valid fixture should contain player results");
4191 };
4192 results.truncate(results.len() - 1);
4193 assert!(matches!(
4194 check_pre_verify_log(&fixture.info, &dealer.key, &log),
4195 Err(DealerLogError::Fault(FaultReason::MismatchedLogPlayers))
4196 ));
4197
4198 let mut log = dealer.valid.clone();
4199 log.results = DealerResult::TooManyReveals;
4200 assert!(matches!(
4201 check_pre_verify_log(&fixture.info, &dealer.key, &log),
4202 Ok(DealerLogOutcome::Unavailable)
4203 ));
4204
4205 let mut log = dealer.valid.clone();
4206 let DealerResult::Ok(results) = &mut log.results else {
4207 panic!("valid fixture should contain player results");
4208 };
4209 let excessive_reveals = usize::try_from(fixture.info.max_reveals::<N3f1>())
4210 .expect("maximum reveals exceed usize::MAX")
4211 + 1;
4212 for result in results.values_mut().iter_mut().take(excessive_reveals) {
4213 *result = AckOrReveal::Reveal(DealerPrivMsg::new(Scalar::one()));
4214 }
4215 assert!(matches!(
4216 check_pre_verify_log(&fixture.info, &dealer.key, &log),
4217 Err(DealerLogError::Fault(FaultReason::ExcessiveReveals))
4218 ));
4219 }
4220
4221 #[test]
4222 fn logs_are_bound_to_constructor_info() {
4223 let fixture = PreVerifyFixture::new();
4224 let mut logs = fixture.logs_for(&fixture.info, &[false; PRE_VERIFY_DEALERS]);
4225 let mut wrong_logs = fixture.logs_for(&fixture.wrong_info, &[false; PRE_VERIFY_DEALERS]);
4226 let mut rng = test_rng();
4227
4228 logs.pre_verify::<ed25519::Batch>(&mut rng, &Sequential);
4229 wrong_logs.pre_verify::<ed25519::Batch>(&mut rng, &Sequential);
4230 assert!(
4231 wrong_logs
4232 .select::<ed25519::Batch>(&mut rng, &Sequential)
4233 .is_ok(),
4234 "control check: logs should verify when bound to the round they were created for"
4235 );
4236 let Err(Failure::InsufficientLogs {
4237 required,
4238 found,
4239 faults,
4240 unavailable,
4241 }) = observe::<MinPk, _, N3f1, ed25519::Batch>(&mut rng, logs, &Sequential)
4242 else {
4243 panic!("logs bound to a different round must fail reconciliation");
4244 };
4245 assert_eq!(
4246 usize::try_from(required).expect("required commitments exceed usize::MAX"),
4247 fixture.required_commitments()
4248 );
4249 assert_eq!(found, 0);
4250 assert_eq!(faults.len(), PRE_VERIFY_DEALERS);
4251 assert!(unavailable.is_empty());
4252 for dealer in &fixture.dealers {
4253 assert!(matches!(
4254 faults.get_value(&dealer.key),
4255 Some(FaultReason::InvalidAck)
4256 ));
4257 }
4258 }
4259
4260 #[test]
4261 fn pre_verify_preserves_classifications_and_invalidates_replacements() {
4262 let fixture = PreVerifyFixture::new();
4263 let mut logs = fixture.logs_for(&fixture.info, &[true; PRE_VERIFY_DEALERS]);
4264 fixture.record(&mut logs, 0, false);
4265
4266 let mut too_many_reveals = fixture.dealers[1].valid.clone();
4267 too_many_reveals.results = DealerResult::TooManyReveals;
4268 logs.record(fixture.dealers[1].key.clone(), too_many_reveals);
4269
4270 let mut wrong_players = fixture.dealers[2].valid.clone();
4271 let DealerResult::Ok(results) = &mut wrong_players.results else {
4272 panic!("valid fixture should contain player results");
4273 };
4274 results.truncate(results.len() - 1);
4275 logs.record(fixture.dealers[2].key.clone(), wrong_players);
4276 logs.pre_verify::<ed25519::Batch>(&mut test_rng(), &Sequential);
4277
4278 let Err(Failure::InsufficientLogs {
4279 required,
4280 found,
4281 faults,
4282 unavailable,
4283 }) = observe::<MinPk, _, N3f1, ed25519::Batch>(&mut test_rng(), logs.clone(), &Sequential)
4284 else {
4285 panic!("three rejected logs must prevent reconciliation");
4286 };
4287 assert_eq!(
4288 usize::try_from(required).expect("required commitments exceed usize::MAX"),
4289 fixture.required_commitments()
4290 );
4291 assert_eq!(
4292 usize::try_from(found).expect("valid dealer count exceeds usize::MAX"),
4293 PRE_VERIFY_DEALERS - 3
4294 );
4295 assert_eq!(faults.len(), 2);
4296 assert!(matches!(
4297 faults.get_value(&fixture.dealers[0].key),
4298 Some(FaultReason::InvalidAck)
4299 ));
4300 assert_eq!(unavailable.len(), 1);
4301 assert!(unavailable.position(&fixture.dealers[1].key).is_some());
4302 assert!(faults.get_value(&fixture.dealers[1].key).is_none());
4303 assert!(matches!(
4304 faults.get_value(&fixture.dealers[2].key),
4305 Some(FaultReason::MismatchedLogPlayers)
4306 ));
4307
4308 fixture.record(&mut logs, 0, true);
4309 assert!(
4310 observe::<MinPk, _, N3f1, ed25519::Batch>(&mut test_rng(), logs, &Sequential).is_ok(),
4311 "replacing one rejected log must restore the exact quorum"
4312 );
4313 }
4314
4315 #[test]
4316 fn missing_logs_report_dealers_as_unavailable() {
4317 let fixture = PreVerifyFixture::new();
4318 let logs = PreVerifyLogs::new(fixture.info.clone());
4319 let Err(Failure::InsufficientLogs {
4320 required,
4321 found,
4322 faults,
4323 unavailable,
4324 }) = observe::<MinPk, _, N3f1, ed25519::Batch>(&mut test_rng(), logs, &Sequential)
4325 else {
4326 panic!("missing logs must fail reconciliation");
4327 };
4328 assert_eq!(
4329 usize::try_from(required).expect("required commitments exceed usize::MAX"),
4330 fixture.required_commitments()
4331 );
4332 assert_eq!(found, 0);
4333 assert!(faults.is_empty());
4334 assert_eq!(unavailable, fixture.info.dealers);
4335 }
4336
4337 #[test]
4338 fn finalize_wraps_reconciliation_failure() {
4339 let fixture = PreVerifyFixture::new();
4340 let player =
4341 Player::<MinPk, _>::new(fixture.info.clone(), ed25519::PrivateKey::from_seed(0))
4342 .expect("player initialization must succeed");
4343 let logs = PreVerifyLogs::new(fixture.info);
4344
4345 assert!(matches!(
4346 player.finalize::<N3f1, ed25519::Batch>(&mut test_rng(), logs, &Sequential),
4347 Err(FinalizeError::Failure(Failure::InsufficientLogs { .. }))
4348 ));
4349 }
4350
4351 #[test]
4352 fn finalize_rejects_logs_bound_to_different_round() {
4353 let fixture = PreVerifyFixture::new();
4354 let player =
4355 Player::<MinPk, _>::new(fixture.info.clone(), ed25519::PrivateKey::from_seed(0))
4356 .expect("player initialization must succeed");
4357 let wrong_logs = fixture.logs_for(&fixture.wrong_info, &[false; PRE_VERIFY_DEALERS]);
4358
4359 let result =
4360 player.finalize::<N3f1, ed25519::Batch>(&mut test_rng(), wrong_logs, &Sequential);
4361
4362 assert!(
4363 matches!(result, Err(FinalizeError::Error(Error::MismatchedLogs))),
4364 "finalize should reject logs bound to a different round"
4365 );
4366 }
4367
4368 fn replaced_dealing_fixture(
4369 replacement_ack: bool,
4370 ) -> (
4371 Player<MinPk, ed25519::PrivateKey>,
4372 Logs<MinPk, ed25519::PublicKey, N3f1>,
4373 ) {
4374 let mut keys: Vec<_> = (0..4).map(ed25519::PrivateKey::from_seed).collect();
4375 keys.sort_by_key(|key| key.public_key());
4376 let participants: Set<_> = keys
4377 .iter()
4378 .map(|key| key.public_key())
4379 .try_collect()
4380 .expect("participants must be unique");
4381 let info = Info::<MinPk, _>::new::<N3f1>(
4382 b"stale-dealing-test",
4383 0,
4384 None,
4385 Mode::NonZeroCounter,
4386 Reveal::V1,
4387 participants.clone(),
4388 participants,
4389 )
4390 .expect("info must be valid");
4391 let malicious = keys[0].clone();
4392 let malicious_pk = malicious.public_key();
4393 let target_key = keys[3].clone();
4394 let target = target_key.public_key();
4395
4396 let (_, stale_pub_msg, stale_priv_msgs) =
4397 Dealer::start::<N3f1>(TestRng::new(100), info.clone(), malicious, None)
4398 .expect("dealer must start");
4399 let stale_priv_msg = stale_priv_msgs
4400 .into_iter()
4401 .find_map(|(player, priv_msg)| (player == target).then_some(priv_msg))
4402 .expect("target dealing must exist");
4403
4404 let mut verified_logs = BTreeMap::new();
4405 let mut logs = Logs::<MinPk, _, N3f1>::new(info.clone());
4406 let mut persisted = vec![(malicious_pk, stale_pub_msg.clone(), stale_priv_msg)];
4407 for (dealer_number, dealer_key) in keys.iter().take(3).enumerate() {
4408 let seed = 200 + dealer_number as u64;
4409 let dealer_pk = dealer_key.public_key();
4410 let (mut dealer, pub_msg, priv_msgs) =
4411 Dealer::start::<N3f1>(TestRng::new(seed), info.clone(), dealer_key.clone(), None)
4412 .expect("dealer must start");
4413 if dealer_number == 0 {
4414 assert_ne!(pub_msg, stale_pub_msg);
4415 }
4416
4417 for (player, priv_msg) in priv_msgs {
4418 if dealer_number == 0 && player == target && !replacement_ack {
4419 continue;
4420 }
4421 if dealer_number != 0 && player == target {
4422 persisted.push((dealer_pk.clone(), pub_msg.clone(), priv_msg.clone()));
4423 }
4424 let receiver_key = keys
4425 .iter()
4426 .find(|key| key.public_key() == player)
4427 .expect("receiver must exist")
4428 .clone();
4429 let mut receiver = Player::<MinPk, _>::new(info.clone(), receiver_key)
4430 .expect("player must initialize");
4431 let ack = receiver
4432 .dealer_message::<N3f1>(dealer_pk.clone(), pub_msg.clone(), priv_msg)
4433 .expect("dealing must be valid")
4434 .expect("dealing must be new");
4435 dealer
4436 .receive_player_ack(player, ack)
4437 .expect("ack must be valid");
4438 }
4439
4440 let (dealer, log) = dealer
4441 .finalize::<N3f1>()
4442 .check(&info)
4443 .expect("dealer log must verify");
4444 if dealer_number == 0 {
4445 assert_eq!(log.get_reveal(&target).is_some(), !replacement_ack);
4446 }
4447 verified_logs.insert(dealer.clone(), log.clone());
4448 logs.record(dealer, log);
4449 }
4450
4451 let (target_player, _) =
4452 Player::resume::<N3f1>(info, target_key, &verified_logs, persisted)
4453 .expect("target must resume");
4454 (target_player, logs)
4455 }
4456
4457 #[test]
4458 fn stale_dealing_is_not_reused_for_replaced_log() {
4459 let (target_player, logs) = replaced_dealing_fixture(false);
4460 let (output, share) = target_player
4461 .finalize::<N3f1, ed25519::Batch>(&mut test_rng(), logs, &Sequential)
4462 .expect("target must finalize");
4463 assert_eq!(
4464 share.public::<MinPk>(),
4465 output
4466 .public()
4467 .partial_public(share.index)
4468 .expect("share index must be valid")
4469 );
4470 }
4471
4472 #[test]
4473 fn replacement_ack_rejects_stale_persisted_dealing() {
4474 let (target_player, logs) = replaced_dealing_fixture(true);
4475 assert!(matches!(
4476 target_player.finalize::<N3f1, ed25519::Batch>(&mut test_rng(), logs, &Sequential),
4477 Err(FinalizeError::Error(Error::InvalidPersistedDealing { .. }))
4478 ));
4479 }
4480
4481 #[test]
4482 fn single_round() -> anyhow::Result<()> {
4483 Plan::new(NZU32!(4))
4484 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
4485 .run::<MinPk>(0)
4486 }
4487
4488 #[test]
4489 fn multiple_rounds() -> anyhow::Result<()> {
4490 Plan::new(NZU32!(4))
4491 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
4492 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
4493 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
4494 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
4495 .run::<MinPk>(0)
4496 }
4497
4498 #[test]
4499 fn player_crash_resume_after_dealer() -> anyhow::Result<()> {
4500 Plan::new(NZU32!(4))
4501 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).crash_resume_player(1, 2))
4502 .run::<MinPk>(0)
4503 }
4504
4505 #[test]
4506 fn resume_missing_good_dealer_message_fails_after_checkpoint() -> anyhow::Result<()> {
4507 Plan::new(NZU32!(4))
4508 .with(
4509 Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
4510 .resume_missing_dealer_msg_fails(2, 1),
4511 )
4512 .run::<MinPk>(0)
4513 }
4514
4515 #[test]
4516 fn resume_missing_good_dealer_message_skips_unacked_players() -> anyhow::Result<()> {
4517 Plan::new(NZU32!(4))
4518 .with(
4519 Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
4520 .no_ack(1, 0)
4521 .resume_missing_dealer_msg_fails(2, 1),
4522 )
4523 .run::<MinPk>(0)
4524 }
4525
4526 #[test]
4527 fn finalize_fails_after_resume_without_good_dealer_message() -> anyhow::Result<()> {
4528 Plan::new(NZU32!(4))
4529 .with(
4530 Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
4531 .no_ack(0, 1)
4532 .finalize_missing_dealer_msg_fails(0),
4533 )
4534 .run::<MinPk>(0)
4535 }
4536
4537 #[test]
4538 fn invalid_checkpoint_configs_fail_validation() {
4539 assert!(
4540 Plan::new(NZU32!(4))
4541 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).crash_resume_player(4, 2))
4542 .validate()
4543 .is_err()
4544 );
4545 assert!(
4546 Plan::new(NZU32!(4))
4547 .with(
4548 Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
4549 .resume_missing_dealer_msg_fails(1, 2),
4550 )
4551 .validate()
4552 .is_err()
4553 );
4554 assert!(
4555 Plan::new(NZU32!(4))
4556 .with(
4557 Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
4558 .bad_reveal(1, 0)
4559 .resume_missing_dealer_msg_fails(2, 1),
4560 )
4561 .validate()
4562 .is_err()
4563 );
4564 }
4565
4566 #[test]
4567 fn changing_committee() -> anyhow::Result<()> {
4568 Plan::new(NonZeroU32::new(5).unwrap())
4569 .with(Round::new(vec![0, 1, 2], vec![1, 2, 3]))
4570 .with(Round::new(vec![1, 2, 3], vec![2, 3, 4]))
4571 .with(Round::new(vec![2, 3, 4], vec![3, 4, 0]))
4572 .with(Round::new(vec![3, 4, 0], vec![4, 0, 1]))
4573 .run::<MinPk>(0)
4574 }
4575
4576 #[test]
4577 fn missing_ack() -> anyhow::Result<()> {
4578 Plan::new(NonZeroU32::new(4).unwrap())
4580 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 0))
4581 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 1))
4582 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 2))
4583 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).no_ack(0, 3))
4584 .run::<MinPk>(0)
4585 }
4586
4587 #[test]
4588 fn increasing_decreasing_committee() -> anyhow::Result<()> {
4589 Plan::new(NonZeroU32::new(5).unwrap())
4590 .with(Round::new(vec![0, 1], vec![0, 1, 2]))
4591 .with(Round::new(vec![0, 1, 2], vec![0, 1, 2, 3]))
4592 .with(Round::new(vec![0, 1, 2], vec![0, 1]))
4593 .with(Round::new(vec![0, 1], vec![0, 1, 2, 3, 4]))
4594 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1]))
4595 .run::<MinPk>(0)
4596 }
4597
4598 #[test]
4599 fn bad_reveal_fails() -> anyhow::Result<()> {
4600 Plan::new(NonZeroU32::new(4).unwrap())
4601 .with(Round::new(vec![0], vec![0, 1, 2, 3]).bad_reveal(0, 1))
4602 .run::<MinPk>(0)
4603 }
4604
4605 #[test]
4606 fn bad_share() -> anyhow::Result<()> {
4607 Plan::new(NonZeroU32::new(4).unwrap())
4608 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).bad_share(0, 1))
4609 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).bad_share(0, 2))
4610 .run::<MinPk>(0)
4611 }
4612
4613 #[test]
4614 fn shift_degree_fails() -> anyhow::Result<()> {
4615 Plan::new(NonZeroU32::new(4).unwrap())
4616 .with(Round::new(vec![0], vec![0, 1, 2, 3]).shift_degree(
4617 0,
4618 NonZeroI32::new(1).ok_or_else(|| anyhow!("invalid NZI32"))?,
4619 ))
4620 .run::<MinPk>(0)
4621 }
4622
4623 #[test]
4624 fn replace_share_fails() -> anyhow::Result<()> {
4625 Plan::new(NonZeroU32::new(4).unwrap())
4626 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]))
4627 .with(Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]).replace_share(0))
4628 .run::<MinPk>(0)
4629 }
4630
4631 #[test]
4632 fn too_many_reveals_dealer() -> anyhow::Result<()> {
4633 Plan::new(NonZeroU32::new(4).unwrap())
4634 .with(
4635 Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
4636 .no_ack(0, 0)
4637 .no_ack(0, 1),
4638 )
4639 .run::<MinPk>(0)
4640 }
4641
4642 #[test]
4643 fn too_many_reveals_player() -> anyhow::Result<()> {
4644 Plan::new(NonZeroU32::new(4).unwrap())
4645 .with(
4646 Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
4647 .no_ack(0, 0)
4648 .no_ack(1, 0)
4649 .no_ack(3, 0),
4650 )
4651 .run::<MinPk>(0)
4652 }
4653
4654 #[test]
4655 fn bad_sigs() -> anyhow::Result<()> {
4656 Plan::new(NonZeroU32::new(4).unwrap())
4657 .with(
4658 Round::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3])
4659 .bad_dealer_sig(
4660 0,
4661 Masks {
4662 log: vec![0xFF; 8],
4663 ..Default::default()
4664 },
4665 )
4666 .bad_player_sig(
4667 0,
4668 1,
4669 Masks {
4670 pub_msg: vec![0xFF; 8],
4671 ..Default::default()
4672 },
4673 ),
4674 )
4675 .run::<MinPk>(0)
4676 }
4677
4678 #[test]
4679 fn issue_2745_regression() -> anyhow::Result<()> {
4680 Plan::new(NonZeroU32::new(6).unwrap())
4681 .with(
4682 Round::new(vec![0], vec![5, 1, 3, 0, 4])
4683 .no_ack(0, 5)
4684 .bad_share(0, 5),
4685 )
4686 .with(Round::new(vec![0, 1, 3, 4], vec![0]))
4687 .with(Round::new(vec![0], vec![0]))
4688 .run::<MinPk>(0)
4689 }
4690
4691 #[test]
4692 fn signed_dealer_log_commitment() -> anyhow::Result<()> {
4693 let sk = ed25519::PrivateKey::from_seed(0);
4694 let pk = sk.public_key();
4695 let info = Info::<MinPk, _>::new::<N3f1>(
4696 b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
4697 0,
4698 None,
4699 Mode::NonZeroCounter,
4700 Reveal::V1,
4701 vec![sk.public_key()].try_into().unwrap(),
4702 vec![sk.public_key()].try_into().unwrap(),
4703 )?;
4704 let mut log0 = {
4705 let (dealer, _, _) =
4706 Dealer::start::<N3f1>(&mut test_rng(), info.clone(), sk.clone(), None)?;
4707 dealer.finalize::<N3f1>()
4708 };
4709 let mut log1 = {
4710 let (mut dealer, pub_msg, priv_msgs) =
4711 Dealer::start::<N3f1>(&mut test_rng(), info.clone(), sk.clone(), None)?;
4712 let mut player = Player::new(info.clone(), sk)?;
4713 let ack = player
4714 .dealer_message::<N3f1>(pk.clone(), pub_msg, priv_msgs[0].1.clone())?
4715 .expect("dealer message must be new");
4716 dealer.receive_player_ack(pk, ack)?;
4717 dealer.finalize::<N3f1>()
4718 };
4719 std::mem::swap(&mut log0.log, &mut log1.log);
4720 assert!(log0.check(&info).is_none());
4721 assert!(log1.check(&info).is_none());
4722
4723 Ok(())
4724 }
4725
4726 #[test]
4727 fn dealer_message_reports_errors_and_skips_duplicates() -> anyhow::Result<()> {
4728 let a = ed25519::PrivateKey::from_seed(0);
4729 let b = ed25519::PrivateKey::from_seed(1);
4730 let stranger = ed25519::PrivateKey::from_seed(2);
4731 let (a_pk, b_pk, stranger_pk) = (a.public_key(), b.public_key(), stranger.public_key());
4732 let members: Set<ed25519::PublicKey> = vec![a_pk.clone(), b_pk.clone()].try_into().unwrap();
4733 let info = Info::<MinPk, _>::new::<N3f1>(
4734 b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
4735 0,
4736 None,
4737 Mode::NonZeroCounter,
4738 Reveal::V1,
4739 members.clone(),
4740 members,
4741 )?;
4742
4743 let (_, pub_msg, priv_msgs) =
4745 Dealer::start::<N3f1>(&mut test_rng(), info.clone(), a, None)?;
4746 let priv_for_a = priv_msgs
4747 .iter()
4748 .find(|(player, _)| *player == a_pk)
4749 .map(|(_, msg)| msg.clone())
4750 .expect("share for a");
4751 let priv_for_b = priv_msgs
4752 .iter()
4753 .find(|(player, _)| *player == b_pk)
4754 .map(|(_, msg)| msg.clone())
4755 .expect("share for b");
4756
4757 assert!(matches!(
4759 Player::resume::<N3f1>(
4760 info.clone(),
4761 b.clone(),
4762 &BTreeMap::new(),
4763 [(a_pk.clone(), pub_msg.clone(), priv_for_a.clone())],
4764 ),
4765 Err(Error::InvalidPersistedDealing { .. })
4766 ));
4767 let mut player = Player::new(info, b)?;
4768 assert!(matches!(
4769 player.dealer_message::<N3f1>(a_pk.clone(), pub_msg.clone(), priv_for_a),
4770 Err(DealerMessageError::InvalidDealerShare)
4771 ));
4772
4773 assert!(matches!(
4775 player.dealer_message::<N3f1>(stranger_pk, pub_msg.clone(), priv_for_b.clone(),),
4776 Err(DealerMessageError::UnexpectedDealer)
4777 ));
4778
4779 assert!(
4781 player
4782 .dealer_message::<N3f1>(a_pk.clone(), pub_msg.clone(), priv_for_b.clone())?
4783 .is_some()
4784 );
4785
4786 assert!(matches!(
4788 player.dealer_message::<N3f1>(a_pk, pub_msg, priv_for_b),
4789 Ok(None)
4790 ));
4791
4792 Ok(())
4793 }
4794
4795 #[test]
4796 fn receive_player_ack_reports_non_attributable_errors() -> anyhow::Result<()> {
4797 let a = ed25519::PrivateKey::from_seed(0);
4798 let stranger = ed25519::PrivateKey::from_seed(1);
4799 let (a_pk, stranger_pk) = (a.public_key(), stranger.public_key());
4800 let members: Set<ed25519::PublicKey> = vec![a_pk.clone()].try_into().unwrap();
4801 let info = Info::<MinPk, _>::new::<N3f1>(
4802 b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
4803 0,
4804 None,
4805 Mode::NonZeroCounter,
4806 Reveal::V1,
4807 members.clone(),
4808 members,
4809 )?;
4810
4811 let (mut dealer, pub_msg, priv_msgs) =
4812 Dealer::start::<N3f1>(TestRng::new(0), info.clone(), a.clone(), None)?;
4813 let (_, alternate_pub_msg, alternate_priv_msgs) =
4814 Dealer::start::<N3f1>(TestRng::new(1), info.clone(), a.clone(), None)?;
4815 assert_ne!(pub_msg, alternate_pub_msg);
4816
4817 let mut player = Player::new(info.clone(), a.clone())?;
4818 let ack = player
4819 .dealer_message::<N3f1>(a_pk.clone(), pub_msg, priv_msgs[0].1.clone())?
4820 .expect("valid ack");
4821
4822 let mut alternate_player = Player::new(info, a)?;
4826 let alternate_ack = alternate_player
4827 .dealer_message::<N3f1>(
4828 a_pk.clone(),
4829 alternate_pub_msg,
4830 alternate_priv_msgs[0].1.clone(),
4831 )?
4832 .expect("valid ack for alternate message");
4833 assert!(matches!(
4834 dealer.receive_player_ack(a_pk.clone(), alternate_ack),
4835 Err(PlayerAckError::InvalidAck)
4836 ));
4837
4838 assert!(matches!(
4840 dealer.receive_player_ack(stranger_pk, ack.clone()),
4841 Err(PlayerAckError::UnexpectedPlayer)
4842 ));
4843
4844 assert!(dealer.receive_player_ack(a_pk, ack).is_ok());
4846
4847 Ok(())
4848 }
4849
4850 #[test]
4851 fn info_with_different_mode_is_not_equal() -> Result<(), Error> {
4852 let sk = ed25519::PrivateKey::from_seed(0);
4853 let pk = sk.public_key();
4854 let dealers: Set<ed25519::PublicKey> = vec![pk.clone()].try_into().unwrap();
4855 let players: Set<ed25519::PublicKey> = vec![pk].try_into().unwrap();
4856
4857 let non_zero_counter_info = Info::<MinPk, _>::new::<N3f1>(
4858 b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
4859 0,
4860 None,
4861 Mode::NonZeroCounter,
4862 Reveal::V1,
4863 dealers.clone(),
4864 players.clone(),
4865 )?;
4866 let roots_of_unity_mode_info = Info::<MinPk, _>::new::<N3f1>(
4867 b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
4868 0,
4869 None,
4870 Mode::RootsOfUnity,
4871 Reveal::V1,
4872 dealers,
4873 players,
4874 )?;
4875
4876 assert_ne!(non_zero_counter_info, roots_of_unity_mode_info);
4877 Ok(())
4878 }
4879
4880 #[test]
4881 fn resume_ignores_invalid_logged_ack_signature() -> Result<(), Error> {
4882 let dealer_sk = ed25519::PrivateKey::from_seed(11);
4883 let dealer_pk = dealer_sk.public_key();
4884 let player_sk = ed25519::PrivateKey::from_seed(22);
4885 let player_pk = player_sk.public_key();
4886 let dealers: Set<ed25519::PublicKey> = vec![dealer_pk.clone()].try_into().unwrap();
4887 let players: Set<ed25519::PublicKey> = vec![player_pk.clone()].try_into().unwrap();
4888 let info = Info::<MinPk, _>::new::<N3f1>(
4889 b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
4890 0,
4891 None,
4892 Mode::NonZeroCounter,
4893 Reveal::V1,
4894 dealers.clone(),
4895 players.clone(),
4896 )?;
4897 let wrong_round_info = Info::<MinPk, _>::new::<N3f1>(
4898 b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_TEST",
4899 1,
4900 None,
4901 Mode::NonZeroCounter,
4902 Reveal::V1,
4903 dealers,
4904 players,
4905 )?;
4906 let (_, pub_msg, _) =
4907 Dealer::start::<N3f1>(&mut test_rng(), info.clone(), dealer_sk, None)?;
4908 let bad_ack = PlayerAck {
4909 sig: transcript_for_ack(
4910 &transcript_for_round(&wrong_round_info),
4911 &dealer_pk,
4912 &pub_msg,
4913 )
4914 .sign(&player_sk),
4915 };
4916 let results: Map<_, _> = vec![(player_pk, AckOrReveal::Ack(bad_ack))]
4917 .into_iter()
4918 .try_collect()
4919 .unwrap();
4920 let mut logs = BTreeMap::new();
4921 logs.insert(
4922 dealer_pk,
4923 DealerLog {
4924 pub_msg,
4925 results: DealerResult::Ok(results),
4926 },
4927 );
4928
4929 let resumed = Player::resume::<N3f1>(info, player_sk, &logs, []);
4930 assert!(resumed.is_ok());
4931 let (_, acks) = resumed.unwrap();
4932 assert!(acks.is_empty());
4933
4934 Ok(())
4935 }
4936
4937 #[test]
4938 fn test_dealer_priv_msg_redacted() {
4939 let mut rng = test_rng();
4940 let msg = DealerPrivMsg::new(Scalar::random(&mut rng));
4941 let debug = format!("{:?}", msg);
4942 assert!(debug.contains("REDACTED"));
4943 }
4944
4945 #[test]
4946 fn test_dealer_priv_msg_decode_rejects_zero_scalar() {
4947 let mut encoded = Scalar::zero().encode();
4948 let decoded = DealerPrivMsg::read_cfg(&mut encoded, &());
4949 assert!(decoded.is_err());
4950 }
4951
4952 #[test]
4953 fn test_dealer_pub_msg_decode_rejects_zero_commitment() {
4954 let mut rng = test_rng();
4955 let commitment = Poly::commit(Poly::new_with_constant(&mut rng, 0, Scalar::zero()));
4956 let mut encoded = DealerPubMsg::<MinPk> { commitment }.encode();
4957 let decoded = DealerPubMsg::<MinPk>::read_cfg(&mut encoded, &NZU32!(1));
4958 assert!(decoded.is_err());
4959 }
4960
4961 #[cfg(feature = "arbitrary")]
4962 mod conformance {
4963 use super::*;
4964 use commonware_codec::conformance::CodecConformance;
4965 use commonware_conformance::Conformance;
4966
4967 fn feldman_transcript(seed: u64, mode: Mode, reveal: Reveal) -> Vec<u8> {
4968 const APPLICATION_NAMESPACE: &[u8] =
4969 b"_COMMONWARE_CRYPTOGRAPHY_BLS12381_DKG_CONFORMANCE";
4970 let dealer_sk = ed25519::PrivateKey::from_seed(11);
4971 let dealer_pk = dealer_sk.public_key();
4972 let player_sk = ed25519::PrivateKey::from_seed(22);
4973 let player_pk = player_sk.public_key();
4974 let dealers: Set<ed25519::PublicKey> = vec![dealer_pk.clone()].try_into().unwrap();
4975 let players: Set<ed25519::PublicKey> = vec![player_pk].try_into().unwrap();
4976 let info = Info::<MinPk, _>::new::<N3f1>(
4977 APPLICATION_NAMESPACE,
4978 seed,
4979 None,
4980 mode,
4981 reveal,
4982 dealers,
4983 players,
4984 )
4985 .unwrap();
4986
4987 let (_, pub_msg, _) = Dealer::start::<N3f1>(
4988 &mut TestRng::new(seed),
4989 info.clone(),
4990 dealer_sk.clone(),
4991 None,
4992 )
4993 .unwrap();
4994
4995 let round_transcript = transcript_for_round(&info);
4996 let ack = transcript_for_ack(&round_transcript, &dealer_pk, &pub_msg);
4997 let ack_summary = ack.summarize();
4998 let ack_signature = ack.sign(&player_sk);
4999
5000 let log = DealerLog {
5001 pub_msg,
5002 results: DealerResult::TooManyReveals,
5003 };
5004 let log_summary = transcript_for_log(&info, &log).summarize();
5005 let signed_log = SignedDealerLog::sign(&dealer_sk, &info, log);
5006
5007 let mut output = info.summary.encode().to_vec();
5008 output.extend(ack_summary.encode());
5009 output.extend(ack_signature.encode());
5010 output.extend(log_summary.encode());
5011 output.extend(signed_log.encode());
5012 output
5013 }
5014
5015 struct FeldmanTranscript<const POLYNOMIAL_MODE: u8, const REVEAL_MODE: u8>;
5017
5018 impl<const POLYNOMIAL_MODE: u8, const REVEAL_MODE: u8> Conformance
5019 for FeldmanTranscript<POLYNOMIAL_MODE, REVEAL_MODE>
5020 {
5021 #[allow(deprecated)]
5022 async fn commit(seed: u64) -> Vec<u8> {
5023 let (mode, reveal) = match (POLYNOMIAL_MODE, REVEAL_MODE) {
5024 (0, 0) => (Mode::NonZeroCounter, Reveal::V0),
5025 (0, 1) => (Mode::NonZeroCounter, Reveal::V1),
5026 (1, 0) => (Mode::RootsOfUnity, Reveal::V0),
5027 (1, 1) => (Mode::RootsOfUnity, Reveal::V1),
5028 _ => panic!("unsupported Feldman transcript mode"),
5029 };
5030 feldman_transcript(seed, mode, reveal)
5031 }
5032 }
5033
5034 commonware_conformance::conformance_tests! {
5035 FeldmanTranscript<0, 0> => 16,
5036 FeldmanTranscript<0, 1> => 16,
5037 FeldmanTranscript<1, 0> => 16,
5038 FeldmanTranscript<1, 1> => 16,
5039 CodecConformance<Output<MinPk, ed25519::PublicKey>>,
5040 CodecConformance<DealerPubMsg<MinPk>>,
5041 CodecConformance<DealerPrivMsg>,
5042 CodecConformance<PlayerAck<ed25519::PublicKey>>,
5043 CodecConformance<AckOrReveal<ed25519::PublicKey>>,
5044 CodecConformance<DealerResult<ed25519::PublicKey>>,
5045 CodecConformance<DealerLog<MinPk, ed25519::PublicKey>>,
5046 CodecConformance<SignedDealerLog<MinPk, ed25519::PrivateKey>>,
5047 }
5048 }
5049}