Skip to main content

frost_dkg/
data.rs

1use super::*;
2use elliptic_curve::group::GroupEncoding;
3use elliptic_curve::subtle::ConditionallySelectable;
4use elliptic_curve::{Group, PrimeField};
5use elliptic_curve_tools::{SumOfProducts, group, prime_field};
6use serde::{Deserialize, Serialize};
7use std::fmt::{self, Display, Formatter};
8use std::sync::Arc;
9use vsss_rs::{IdentifierPrimeField, ShareVerifierGroup};
10
11/// Valid protocol rounds.
12#[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Ord, PartialOrd, Hash)]
13pub enum Round {
14    /// First round.
15    One,
16    /// Second round.
17    Two,
18    /// Third round.
19    Three,
20    /// Fourth round.
21    Four,
22}
23
24impl Display for Round {
25    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
26        match self {
27            Self::One => write!(f, "1"),
28            Self::Two => write!(f, "2"),
29            Self::Three => write!(f, "3"),
30            Self::Four => write!(f, "4"),
31        }
32    }
33}
34
35macro_rules! impl_round_to_int {
36    ($($ident:ident),+$(,)*) => {
37        $(
38            impl From<Round> for $ident {
39                fn from(value: Round) -> Self {
40                    match value {
41                        Round::One => 1,
42                        Round::Two => 2,
43                        Round::Three => 3,
44                        Round::Four => 4,
45                    }
46                }
47            }
48
49            impl TryFrom<$ident> for Round {
50                type Error = String;
51
52                fn try_from(value: $ident) -> Result<Self, Self::Error> {
53                    match value {
54                        1 => Ok(Round::One),
55                        2 => Ok(Round::Two),
56                        3 => Ok(Round::Three),
57                        4 => Ok(Round::Four),
58                        _ => Err(format!("Invalid round: {}", value)),
59                    }
60                }
61            }
62        )+
63    };
64}
65
66impl_round_to_int!(u8, u16, u32, u128, usize);
67
68/// The participant type.
69#[derive(Debug, Copy, Clone, Default, Deserialize, Serialize)]
70pub enum ParticipantType {
71    /// A participant that contributes a secret.
72    #[default]
73    Secret,
74    /// A participant that refreshes an existing sharing.
75    Refresh,
76}
77
78macro_rules! impl_participant_to_int {
79    ($($ident:ident),+$(,)*) => {
80        $(
81            impl From<ParticipantType> for $ident {
82                fn from(value: ParticipantType) -> Self {
83                    match value {
84                        ParticipantType::Secret => 1,
85                        ParticipantType::Refresh => 2,
86                    }
87                }
88            }
89
90            impl TryFrom<$ident> for ParticipantType {
91                type Error = String;
92
93                fn try_from(value: $ident) -> Result<Self, Self::Error> {
94                    match value {
95                        1 => Ok(ParticipantType::Secret),
96                        2 => Ok(ParticipantType::Refresh),
97                        _ => Err(format!("Invalid participant type: {}", value)),
98                    }
99                }
100            }
101        )+
102    };
103}
104
105impl_participant_to_int!(u8, u16, u32, u128, usize);
106
107/// A Schnorr signature.
108#[derive(Debug, Default, Copy, Clone, Deserialize, Serialize)]
109pub struct Signature<G: Group<Scalar: ScalarHash> + GroupEncoding + Default> {
110    #[serde(with = "group")]
111    pub(crate) r: G,
112    #[serde(with = "prime_field")]
113    pub(crate) s: G::Scalar,
114}
115
116/// The output of a protocol round for one participant.
117#[derive(Debug, Clone, Deserialize, Serialize)]
118pub struct ParticipantRoundOutput<F: ScalarHash> {
119    /// The recipient's ordinal index.
120    pub dst_ordinal: usize,
121    /// The recipient's participant ID.
122    #[serde(bound(
123        serialize = "IdentifierPrimeField<F>: Serialize",
124        deserialize = "IdentifierPrimeField<F>: Deserialize<'de>"
125    ))]
126    pub dst_id: IdentifierPrimeField<F>,
127    /// The data to send.
128    pub data: WireMessage,
129}
130
131impl<F> ParticipantRoundOutput<F>
132where
133    F: ScalarHash,
134{
135    /// Create a participant round output.
136    pub fn new(dst_ordinal: usize, dst_id: IdentifierPrimeField<F>, data: WireMessage) -> Self {
137        Self {
138            dst_ordinal,
139            dst_id,
140            data,
141        }
142    }
143}
144
145/// The completed output of a DKG participant.
146///
147/// This type owns the participant's final result and intentionally does not
148/// implement [`Clone`] to avoid accidentally duplicating secret share material.
149pub struct DkgOutput<G>
150where
151    G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
152    G::Scalar: ScalarHash,
153{
154    pub(crate) secret_share: SecretShare<G::Scalar>,
155    pub(crate) public_key: G,
156    pub(crate) feldman_verifiers: Vec<ShareVerifierGroup<G>>,
157    pub(crate) participant_ids: Vec<Option<IdentifierPrimeField<G::Scalar>>>,
158    pub(crate) transcript_hash: [u8; 32],
159}
160
161impl<G> fmt::Debug for DkgOutput<G>
162where
163    G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
164    G::Scalar: ScalarHash,
165{
166    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
167        f.debug_struct("DkgOutput")
168            .field("public_key", &self.public_key)
169            .field("feldman_verifiers", &self.feldman_verifiers)
170            .field("participant_ids", &self.participant_ids)
171            .field("transcript_hash", &self.transcript_hash)
172            .finish_non_exhaustive()
173    }
174}
175
176impl<G> DkgOutput<G>
177where
178    G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
179    G::Scalar: ScalarHash,
180{
181    /// The participant's final secret share.
182    pub fn secret_share(&self) -> SecretShare<G::Scalar> {
183        self.secret_share
184    }
185
186    /// The public key produced by the DKG.
187    pub fn public_key(&self) -> G {
188        self.public_key
189    }
190
191    /// The participant's Feldman verifiers.
192    pub fn feldman_verifiers(&self) -> &[ShareVerifierGroup<G>] {
193        &self.feldman_verifiers
194    }
195
196    /// The participants included in the completed DKG.
197    pub fn participant_ids(&self) -> &[Option<IdentifierPrimeField<G::Scalar>>] {
198        &self.participant_ids
199    }
200
201    /// The final protocol transcript hash.
202    pub fn transcript_hash(&self) -> [u8; 32] {
203        self.transcript_hash
204    }
205}
206
207/// An opaque, serialized protocol message.
208#[derive(Clone)]
209pub struct WireMessage(Arc<[u8]>);
210
211impl WireMessage {
212    /// Borrow the serialized message.
213    pub fn as_bytes(&self) -> &[u8] {
214        &self.0
215    }
216
217    /// Borrow the serialized message as a byte slice.
218    pub fn as_slice(&self) -> &[u8] {
219        self.as_bytes()
220    }
221}
222
223impl From<Vec<u8>> for WireMessage {
224    fn from(value: Vec<u8>) -> Self {
225        Self(Arc::from(value))
226    }
227}
228
229impl Serialize for WireMessage {
230    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
231    where
232        S: serde::Serializer,
233    {
234        serializer.serialize_bytes(self.as_bytes())
235    }
236}
237
238impl<'de> Deserialize<'de> for WireMessage {
239    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
240    where
241        D: serde::Deserializer<'de>,
242    {
243        Vec::<u8>::deserialize(deserializer).map(Self::from)
244    }
245}
246
247impl AsRef<[u8]> for WireMessage {
248    fn as_ref(&self) -> &[u8] {
249        self.as_bytes()
250    }
251}
252
253impl fmt::Debug for WireMessage {
254    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
255        f.debug_struct("WireMessage")
256            .field("len", &self.0.len())
257            .finish()
258    }
259}
260
261fn serialize_round_message<T>(round: Round, value: &T) -> DkgResult<WireMessage>
262where
263    T: Serialize + ?Sized,
264{
265    Ok(postcard::to_extend(value, vec![u8::from(round)])?.into())
266}
267
268/// The transport destination for an opaque protocol message.
269#[derive(Clone, Debug)]
270pub enum MessageDestination<F: ScalarHash> {
271    /// Send the message through the application's broadcast channel.
272    Broadcast,
273    /// Send the message only to the specified participant.
274    Direct {
275        /// The recipient's ordinal index.
276        ordinal: usize,
277        /// The recipient's identifier.
278        id: IdentifierPrimeField<F>,
279    },
280}
281
282/// An opaque outbound protocol message and its transport destination.
283#[derive(Clone, Debug)]
284pub struct OutboundMessage<F: ScalarHash> {
285    destination: MessageDestination<F>,
286    message: WireMessage,
287}
288
289impl<F: ScalarHash> OutboundMessage<F> {
290    /// The transport destination for this message.
291    pub fn destination(&self) -> &MessageDestination<F> {
292        &self.destination
293    }
294
295    /// The opaque bytes to send.
296    pub fn message(&self) -> &WireMessage {
297        &self.message
298    }
299}
300
301/// The outbound messages produced by one protocol round.
302#[derive(Clone, Debug)]
303pub struct OutboundMessages<F: ScalarHash> {
304    messages: Vec<OutboundMessage<F>>,
305    broadcast_recipients: Vec<Option<IdentifierPrimeField<F>>>,
306}
307
308impl<F: ScalarHash> OutboundMessages<F> {
309    /// The transport-aware messages produced by the round.
310    pub fn messages(&self) -> &[OutboundMessage<F>] {
311        &self.messages
312    }
313
314    /// The number of transport-aware messages in this batch.
315    pub fn len(&self) -> usize {
316        self.messages.len()
317    }
318
319    /// Whether this batch contains no messages.
320    pub fn is_empty(&self) -> bool {
321        self.messages.is_empty()
322    }
323
324    /// Iterate over the transport-aware messages in this batch.
325    pub fn iter(&self) -> std::slice::Iter<'_, OutboundMessage<F>> {
326        self.messages.iter()
327    }
328
329    /// Expand broadcasts into participant-targeted messages.
330    ///
331    /// This is useful for transports that do not provide native broadcast.
332    pub fn into_per_recipient(self) -> Vec<ParticipantRoundOutput<F>> {
333        let mut outputs = Vec::new();
334        for message in self.messages {
335            match message.destination {
336                MessageDestination::Broadcast => {
337                    outputs.extend(self.broadcast_recipients.iter().enumerate().filter_map(
338                        |(ordinal, id)| {
339                            id.map(|id| {
340                                ParticipantRoundOutput::new(ordinal, id, message.message.clone())
341                            })
342                        },
343                    ));
344                }
345                MessageDestination::Direct { ordinal, id } => {
346                    outputs.push(ParticipantRoundOutput::new(ordinal, id, message.message));
347                }
348            }
349        }
350        outputs
351    }
352}
353
354impl<F: ScalarHash> IntoIterator for OutboundMessages<F> {
355    type Item = OutboundMessage<F>;
356    type IntoIter = std::vec::IntoIter<Self::Item>;
357
358    fn into_iter(self) -> Self::IntoIter {
359        self.messages.into_iter()
360    }
361}
362
363impl<'a, F: ScalarHash> IntoIterator for &'a OutboundMessages<F> {
364    type Item = &'a OutboundMessage<F>;
365    type IntoIter = std::slice::Iter<'a, OutboundMessage<F>>;
366
367    fn into_iter(self) -> Self::IntoIter {
368        self.iter()
369    }
370}
371
372/// The result of advancing a participant by one protocol round.
373#[derive(Clone, Debug)]
374pub enum AdvanceResult<F: ScalarHash> {
375    /// Messages that must be delivered before advancing again.
376    Messages(OutboundMessages<F>),
377    /// The participant has completed the protocol and can be converted into
378    /// [`DkgOutput`] with `Participant::into_output`.
379    Complete,
380}
381
382/// A protocol round's output generator.
383#[derive(Debug, Clone)]
384pub enum RoundOutputGenerator<G>
385where
386    G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
387    G::Scalar: ScalarHash,
388{
389    /// The round 1 output generator.
390    Round1(Round1OutputGenerator<G>),
391    /// The round 2 output generator.
392    Round2(Round2OutputGenerator<G>),
393    /// The round 3 output generator.
394    Round3,
395}
396
397impl<G> RoundOutputGenerator<G>
398where
399    G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
400    G::Scalar: ScalarHash,
401{
402    /// Serialize the round output into opaque, transport-aware messages.
403    pub fn into_messages(self) -> DkgResult<OutboundMessages<G::Scalar>> {
404        match self {
405            Self::Round1(data) => {
406                let round1_output_data = Round1Data {
407                    sender_ordinal: data.sender_ordinal,
408                    sender_id: data.sender_id,
409                    sender_type: data.sender_type,
410                    feldman_commitments: data.feldman_commitments,
411                    verifying_share: data.verifying_share,
412                    signature: data.signature,
413                };
414                let mut broadcast_recipients = data
415                    .participant_ids
416                    .into_iter()
417                    .map(Some)
418                    .collect::<Vec<_>>();
419                broadcast_recipients[data.sender_ordinal] = None;
420                Ok(OutboundMessages {
421                    messages: vec![OutboundMessage {
422                        destination: MessageDestination::Broadcast,
423                        message: serialize_round_message(Round::One, &round1_output_data)?,
424                    }],
425                    broadcast_recipients,
426                })
427            }
428            Self::Round2(data) => {
429                let mut messages = Vec::with_capacity(data.participant_ids.len().saturating_sub(1));
430                for (ordinal, id) in data.participant_ids.into_iter().enumerate() {
431                    if ordinal == data.sender_ordinal {
432                        continue;
433                    }
434                    let Some(id) = id else {
435                        continue;
436                    };
437                    debug_assert_eq!(data.secret_shares[ordinal].identifier, id);
438                    let round2_output_data = Round2Data {
439                        sender_ordinal: data.sender_ordinal,
440                        sender_id: data.sender_id,
441                        sender_type: data.sender_type,
442                        secret_share: data.secret_shares[ordinal],
443                        transcript_hash: data.transcript_hash,
444                    };
445                    messages.push(OutboundMessage {
446                        destination: MessageDestination::Direct { ordinal, id },
447                        message: serialize_round_message(Round::Two, &round2_output_data)?,
448                    });
449                }
450                Ok(OutboundMessages {
451                    messages,
452                    broadcast_recipients: Vec::new(),
453                })
454            }
455            Self::Round3 => Ok(OutboundMessages {
456                messages: Vec::new(),
457                broadcast_recipients: Vec::new(),
458            }),
459        }
460    }
461
462    /// Iterate over the data to send to other participants.
463    ///
464    /// Each output identifies the recipient by ordinal index and participant ID.
465    pub fn iter(&self) -> DkgResult<std::vec::IntoIter<ParticipantRoundOutput<G::Scalar>>> {
466        let outputs = match self {
467            Self::Round1(data) => {
468                let round1_output_data = Round1Data {
469                    sender_ordinal: data.sender_ordinal,
470                    sender_id: data.sender_id,
471                    sender_type: data.sender_type,
472                    feldman_commitments: data.feldman_commitments.clone(),
473                    verifying_share: data.verifying_share,
474                    signature: data.signature,
475                };
476                let output = serialize_round_message(Round::One, &round1_output_data)?;
477                data.participant_ids
478                    .iter()
479                    .enumerate()
480                    .filter_map(|(index, id)| {
481                        if index == data.sender_ordinal {
482                            None
483                        } else {
484                            Some(ParticipantRoundOutput::new(index, *id, output.clone()))
485                        }
486                    })
487                    .collect()
488            }
489            Self::Round2(data) => {
490                let mut round2_output_data = Round2Data {
491                    sender_ordinal: data.sender_ordinal,
492                    sender_id: data.sender_id,
493                    sender_type: data.sender_type,
494                    secret_share: SecretShare::<G::Scalar>::default(),
495                    transcript_hash: data.transcript_hash,
496                };
497                let mut outputs = Vec::with_capacity(data.participant_ids.len().saturating_sub(1));
498                for (index, id) in data.participant_ids.iter().enumerate() {
499                    if index == data.sender_ordinal {
500                        continue;
501                    }
502                    let Some(id) = id else {
503                        continue;
504                    };
505                    debug_assert_eq!(data.secret_shares[index].identifier, *id);
506                    round2_output_data.secret_share = data.secret_shares[index];
507                    let output = serialize_round_message(Round::Two, &round2_output_data)?;
508                    outputs.push(ParticipantRoundOutput::new(index, *id, output));
509                }
510                outputs
511            }
512            Self::Round3 => Vec::new(),
513        };
514        Ok(outputs.into_iter())
515    }
516}
517
518/// The output generator for round 1.
519#[derive(Debug, Clone)]
520pub struct Round1OutputGenerator<G>
521where
522    G: GroupEncoding + Default + SumOfProducts + ConditionallySelectable,
523    G::Scalar: ScalarHash,
524{
525    /// The recipient participant IDs.
526    pub(crate) participant_ids: Vec<IdentifierPrimeField<G::Scalar>>,
527    /// The sender's participant type.
528    pub(crate) sender_type: ParticipantType,
529    /// The sender's ordinal index.
530    pub(crate) sender_ordinal: usize,
531    /// The sender's ID.
532    pub(crate) sender_id: IdentifierPrimeField<G::Scalar>,
533    /// The Feldman verifier set.
534    pub(crate) feldman_commitments: Vec<ShareVerifierGroup<G>>,
535    /// The verifying share.
536    pub(crate) verifying_share: G,
537    /// The Schnorr signature.
538    pub(crate) signature: Signature<G>,
539}
540
541/// The round 1 data.
542#[derive(Clone, Debug, Default, Deserialize, Serialize)]
543pub struct Round1Data<G>
544where
545    G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
546    G::Scalar: ScalarHash,
547{
548    /// The sender's ordinal index.
549    pub(crate) sender_ordinal: usize,
550    /// The sender's ID.
551    #[serde(bound(
552        serialize = "IdentifierPrimeField<G::Scalar>: Serialize",
553        deserialize = "IdentifierPrimeField<G::Scalar>: Deserialize<'de>"
554    ))]
555    pub(crate) sender_id: IdentifierPrimeField<G::Scalar>,
556    /// The sender's participant type.
557    pub(crate) sender_type: ParticipantType,
558    /// The Feldman commitments.
559    #[serde(bound(
560        serialize = "ShareVerifierGroup<G>: Serialize",
561        deserialize = "ShareVerifierGroup<G>: Deserialize<'de>"
562    ))]
563    pub(crate) feldman_commitments: Vec<ShareVerifierGroup<G>>,
564    /// The verifying share.
565    #[serde(with = "group")]
566    pub(crate) verifying_share: G,
567    /// The Schnorr signature.
568    #[serde(bound(
569        serialize = "Signature<G>: Serialize",
570        deserialize = "Signature<G>: Deserialize<'de>"
571    ))]
572    pub(crate) signature: Signature<G>,
573}
574
575impl<G> Round1Data<G>
576where
577    G: GroupEncoding + Default + SumOfProducts + ConditionallySelectable,
578    G::Scalar: ScalarHash,
579{
580    pub(crate) fn add_to_transcript(&self, transcript: &mut merlin::Transcript) {
581        transcript.append_message(
582            b"sender_ordinal",
583            &(self.sender_ordinal as u16).to_be_bytes(),
584        );
585        transcript.append_message(b"sender_id", self.sender_id.0.to_repr().as_ref());
586        transcript.append_message(b"sender_type", &u16::from(self.sender_type).to_be_bytes());
587        transcript.append_message(b"signature.r", self.signature.r.to_bytes().as_ref());
588        transcript.append_message(b"signature.s", self.signature.s.to_repr().as_ref());
589        transcript.append_message(
590            b"feldman_commitments.len()",
591            &(self.feldman_commitments.len() as u16).to_be_bytes(),
592        );
593        for (i, commitment) in self.feldman_commitments.iter().enumerate() {
594            transcript.append_u64(b"feldman_commitments_index", i as u64);
595            transcript.append_message(b"feldman_commitment", commitment.to_bytes().as_ref());
596        }
597    }
598
599    /// Get the sender's ordinal index in the DKG.
600    pub fn sender_ordinal(&self) -> usize {
601        self.sender_ordinal
602    }
603
604    /// Get the sender's ID in the DKG.
605    pub fn sender_id(&self) -> IdentifierPrimeField<G::Scalar> {
606        self.sender_id
607    }
608
609    /// Get the sender's participant type in the DKG.
610    pub fn sender_type(&self) -> ParticipantType {
611        self.sender_type
612    }
613
614    /// Get the Feldman commitments used by the DKG.
615    pub fn feldman_commitments(&self) -> &[ShareVerifierGroup<G>] {
616        &self.feldman_commitments
617    }
618
619    /// Get the verifying share used to verify the Schnorr signature.
620    pub fn verifying_share(&self) -> G {
621        self.verifying_share
622    }
623
624    /// Get the Schnorr signature used by the DKG.
625    pub fn signature(&self) -> Signature<G> {
626        self.signature
627    }
628}
629
630/// The output generator for round 2.
631#[derive(Clone)]
632pub struct Round2OutputGenerator<G>
633where
634    G: GroupEncoding + Default + SumOfProducts + ConditionallySelectable,
635    G::Scalar: ScalarHash,
636{
637    /// The recipient participant IDs.
638    pub(crate) participant_ids: Vec<Option<IdentifierPrimeField<G::Scalar>>>,
639    /// The sender's ordinal index.
640    pub(crate) sender_ordinal: usize,
641    /// The sender's ID.
642    pub(crate) sender_id: IdentifierPrimeField<G::Scalar>,
643    /// The sender's participant type.
644    pub(crate) sender_type: ParticipantType,
645    /// The peer-to-peer data, indexed by participant ordinal.
646    pub(crate) secret_shares: Vec<SecretShare<G::Scalar>>,
647    /// The transcript hash.
648    pub(crate) transcript_hash: [u8; 32],
649}
650
651impl<G> fmt::Debug for Round2OutputGenerator<G>
652where
653    G: GroupEncoding + Default + SumOfProducts + ConditionallySelectable,
654    G::Scalar: ScalarHash,
655{
656    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
657        f.debug_struct("Round2OutputGenerator")
658            .field("participant_ids", &self.participant_ids)
659            .field("sender_ordinal", &self.sender_ordinal)
660            .field("sender_id", &self.sender_id)
661            .field("sender_type", &self.sender_type)
662            .field("recipient_count", &self.secret_shares.len())
663            .field("transcript_hash", &self.transcript_hash)
664            .finish()
665    }
666}
667
668/// The round 2 data.
669#[derive(Clone, Default, Deserialize, Serialize)]
670pub struct Round2Data<F: ScalarHash> {
671    /// The sender's ordinal index.
672    pub(crate) sender_ordinal: usize,
673    /// The sender's ID.
674    #[serde(bound(
675        serialize = "IdentifierPrimeField<F>: Serialize",
676        deserialize = "IdentifierPrimeField<F>: Deserialize<'de>"
677    ))]
678    pub(crate) sender_id: IdentifierPrimeField<F>,
679    /// The sender's participant type.
680    pub(crate) sender_type: ParticipantType,
681    /// The peer-to-peer secret share.
682    #[serde(bound(
683        serialize = "SecretShare<F>: Serialize",
684        deserialize = "SecretShare<F>: Deserialize<'de>"
685    ))]
686    pub(crate) secret_share: SecretShare<F>,
687    /// The hash of the transcript containing all received messages.
688    pub(crate) transcript_hash: [u8; 32],
689}
690
691impl<F: ScalarHash> fmt::Debug for Round2Data<F> {
692    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
693        f.debug_struct("Round2Data")
694            .field("sender_ordinal", &self.sender_ordinal)
695            .field("sender_id", &self.sender_id)
696            .field("sender_type", &self.sender_type)
697            .field("transcript_hash", &self.transcript_hash)
698            .finish_non_exhaustive()
699    }
700}
701
702impl<F: ScalarHash> Round2Data<F> {
703    pub(crate) fn add_to_transcript(&self, transcript: &mut merlin::Transcript) {
704        transcript.append_message(
705            b"sender_ordinal",
706            &(self.sender_ordinal as u16).to_be_bytes(),
707        );
708        transcript.append_message(b"sender_id", self.sender_id.0.to_repr().as_ref());
709        transcript.append_message(b"sender_type", &u16::from(self.sender_type).to_be_bytes());
710        transcript.append_message(b"transcript_hash", &self.transcript_hash);
711    }
712
713    /// Get the sender's ordinal index in the DKG.
714    pub fn sender_ordinal(&self) -> usize {
715        self.sender_ordinal
716    }
717
718    /// Get the sender's ID in the DKG.
719    pub fn sender_id(&self) -> IdentifierPrimeField<F> {
720        self.sender_id
721    }
722
723    /// Get the sender's participant type in the DKG.
724    pub fn sender_type(&self) -> ParticipantType {
725        self.sender_type
726    }
727
728    /// Get the secret share used by the DKG.
729    pub fn secret_share(&self) -> SecretShare<F> {
730        self.secret_share
731    }
732
733    /// Get the transcript hash used by the DKG.
734    pub fn transcript_hash(&self) -> [u8; 32] {
735        self.transcript_hash
736    }
737}
738
739#[cfg(test)]
740mod tests {
741    use super::*;
742
743    #[test]
744    fn round_two_debug_omits_secret_shares() {
745        let data = Round2Data::<k256::Scalar> {
746            sender_ordinal: 0,
747            sender_id: IdentifierPrimeField::ONE,
748            sender_type: ParticipantType::Secret,
749            secret_share: SecretShare::default(),
750            transcript_hash: [0; 32],
751        };
752        let generator = Round2OutputGenerator::<k256::ProjectivePoint> {
753            participant_ids: vec![Some(IdentifierPrimeField::ONE)],
754            sender_ordinal: 0,
755            sender_id: IdentifierPrimeField::ONE,
756            sender_type: ParticipantType::Secret,
757            secret_shares: vec![SecretShare::default()],
758            transcript_hash: [0; 32],
759        };
760
761        assert!(!format!("{data:?}").contains("secret_share"));
762        assert!(!format!("{generator:?}").contains("secret_share"));
763    }
764
765    #[test]
766    fn serialized_round_message_prefixes_payload_without_changing_it() {
767        let message =
768            serialize_round_message(Round::Two, &42_u16).expect("serialize framed payload");
769
770        assert_eq!(message.as_bytes().first(), Some(&u8::from(Round::Two)));
771        assert_eq!(
772            postcard::from_bytes::<u16>(&message.as_bytes()[1..]).expect("deserialize payload"),
773            42
774        );
775    }
776
777    #[test]
778    fn participant_round_output_postcard_round_trip() {
779        let output = ParticipantRoundOutput::<k256::Scalar>::new(
780            1,
781            IdentifierPrimeField(k256::Scalar::ONE),
782            vec![1, 2, 3].into(),
783        );
784
785        let encoded = postcard::to_stdvec(&output).expect("serialize round output");
786        let decoded = postcard::from_bytes::<ParticipantRoundOutput<k256::Scalar>>(&encoded)
787            .expect("deserialize round output");
788
789        assert_eq!(decoded.dst_ordinal, output.dst_ordinal);
790        assert_eq!(decoded.dst_id, output.dst_id);
791        assert_eq!(decoded.data.as_bytes(), output.data.as_bytes());
792    }
793}