tempo_dkg_onchain_artifacts/
lib.rs

1//! Items that are written to chain.
2
3use bytes::{Buf, BufMut};
4use commonware_codec::{
5    EncodeSize, FixedSize as _, RangeCfg, Read, ReadExt as _, Write, varint::UInt,
6};
7use commonware_consensus::types::Epoch;
8use commonware_cryptography::{
9    Signer as _, Verifier as _,
10    bls12381::primitives::{group, poly::Public, variant::MinSig},
11    ed25519::{PrivateKey, PublicKey, Signature},
12};
13use commonware_utils::{quorum, set::Ordered};
14
15/// A message from a player to a dealer, confirming the receipt of share.
16///
17/// Contains the player's public key, as well as its signature over the
18/// ceremony's epoch, dealear public key, and commitment contained in the
19/// share message.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct Ack {
22    /// The public key identifier of the player sending the acknowledgment.
23    player: PublicKey,
24    /// A signature covering the DKG round, dealer ID, and the dealer's commitment.
25    /// This confirms the player received and validated the correct share.
26    signature: Signature,
27}
28
29impl Ack {
30    /// Create a new acknowledgment signed by `signer`.
31    pub fn new(
32        namespace: &[u8],
33        signer: PrivateKey,
34        player: PublicKey,
35        epoch: Epoch,
36        dealer: &PublicKey,
37        commitment: &Public<MinSig>,
38    ) -> Self {
39        let payload = Self::construct_signature_payload(epoch, dealer, commitment);
40        let signature = signer.sign(Some(namespace), &payload);
41        Self { player, signature }
42    }
43
44    fn construct_signature_payload(
45        epoch: Epoch,
46        dealer: &PublicKey,
47        commitment: &Public<MinSig>,
48    ) -> Vec<u8> {
49        let mut payload =
50            Vec::with_capacity(Epoch::SIZE + PublicKey::SIZE + commitment.encode_size());
51        epoch.write(&mut payload);
52        dealer.write(&mut payload);
53        commitment.write(&mut payload);
54        payload
55    }
56
57    pub fn verify(
58        &self,
59        namespace: &[u8],
60        public_key: &PublicKey,
61        epoch: Epoch,
62        dealer: &PublicKey,
63        commitment: &Public<MinSig>,
64    ) -> bool {
65        let payload = Self::construct_signature_payload(epoch, dealer, commitment);
66        public_key.verify(Some(namespace), &payload, &self.signature)
67    }
68
69    pub fn player(&self) -> &PublicKey {
70        &self.player
71    }
72}
73
74impl Write for Ack {
75    fn write(&self, buf: &mut impl BufMut) {
76        self.player.write(buf);
77        self.signature.write(buf);
78    }
79}
80
81impl EncodeSize for Ack {
82    fn encode_size(&self) -> usize {
83        self.player.encode_size() + self.signature.encode_size()
84    }
85}
86
87impl Read for Ack {
88    type Cfg = ();
89
90    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, commonware_codec::Error> {
91        Ok(Self {
92            player: PublicKey::read(buf)?,
93            signature: Signature::read(buf)?,
94        })
95    }
96}
97
98/// The outcome of a dkg ceremony round.
99///
100/// Called public because it only contains the public polynomial.
101#[derive(Clone, Debug, PartialEq, Eq)]
102pub struct PublicOutcome {
103    pub epoch: Epoch,
104    pub participants: Ordered<PublicKey>,
105    pub public: Public<MinSig>,
106}
107
108impl Write for PublicOutcome {
109    fn write(&self, buf: &mut impl BufMut) {
110        UInt(self.epoch).write(buf);
111        self.participants.write(buf);
112        self.public.write(buf);
113    }
114}
115
116impl Read for PublicOutcome {
117    type Cfg = ();
118
119    fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
120        let epoch = UInt::read(buf)?.into();
121        let max_participants: usize = u16::MAX.into();
122        let participants = Ordered::read_cfg(buf, &(RangeCfg::from(1..=max_participants), ()))?;
123        let public =
124            Public::<MinSig>::read_cfg(buf, &(quorum(participants.len() as u32) as usize))?;
125        Ok(Self {
126            epoch,
127            participants,
128            public,
129        })
130    }
131}
132
133impl EncodeSize for PublicOutcome {
134    fn encode_size(&self) -> usize {
135        UInt(self.epoch).encode_size() + self.participants.encode_size() + self.public.encode_size()
136    }
137}
138
139/// The local outcome of a dealer's dealings.
140///
141/// This is the intermediate outcome of a ceremony, which contains a dealer's
142/// generated commitment, all acks for the shares it sent to the ceremony's
143/// players, and finally the revealed shares, for which it did not receive acks.
144///
145/// This object is persisted on-chain. Every player collects the intermediate
146/// outcomes of the other dealers to create the overall outcome of the ceremony.
147#[derive(Clone, Debug, PartialEq, Eq)]
148pub struct IntermediateOutcome {
149    /// The number of players in this epoch.
150    n_players: u16,
151
152    /// The public key of the dealer.
153    dealer: PublicKey,
154
155    /// The dealer's signature over the resharing round, commitment, acks, and reveals.
156    dealer_signature: Signature,
157
158    /// The epoch of the resharing operation.
159    epoch: Epoch,
160
161    /// The new group public key polynomial.
162    commitment: Public<MinSig>,
163
164    /// All signed acknowledgements from participants.
165    acks: Vec<Ack>,
166
167    /// Any revealed secret shares.
168    reveals: Vec<group::Share>,
169}
170
171impl IntermediateOutcome {
172    /// Creates a new intermediate ceremony outcome.
173    ///
174    /// This object contains, the number of players, the epoch of the ceremony,
175    /// the dealer's commitment (public polynomial), the acks received by the
176    /// players and the revealed shares for which no acks are received.
177    ///
178    /// Finally, it also includes a signature over
179    /// `(namespace, epoch, commitment, acks, reveals)` signed by the dealer.
180    pub fn new(
181        n_players: u16,
182        dealer_signer: &PrivateKey,
183        namespace: &[u8],
184        epoch: Epoch,
185        commitment: Public<MinSig>,
186        acks: Vec<Ack>,
187        reveals: Vec<group::Share>,
188    ) -> Self {
189        // Sign the resharing outcome
190        let payload =
191            Self::signature_payload_from_parts(n_players, epoch, &commitment, &acks, &reveals);
192        let dealer_signature = dealer_signer.sign(Some(namespace), payload.as_ref());
193
194        Self {
195            n_players,
196            dealer: dealer_signer.public_key(),
197            dealer_signature,
198            epoch,
199            commitment,
200            acks,
201            reveals,
202        }
203    }
204
205    /// Creates a new intermediate ceremony outcome.
206    ///
207    /// This method constructs a signature without the number players. This is
208    /// incorrect and addressed by [`Self::new`]. [`Self::new_pre_allegretto`]
209    /// exists for compatibility reasons and should only be used for hardforks
210    /// pre allegretto.
211    ///
212    /// This object contains, the number of players, the epoch of the ceremony,
213    /// the dealer's commitment (public polynomial), the acks received by the
214    /// players and the revealed shares for which no acks are received.
215    ///
216    /// Finally, it also includes a signature over
217    /// `(namespace, epoch, commitment, acks, reveals)` signed by the dealer.
218    pub fn new_pre_allegretto(
219        n_players: u16,
220        dealer_signer: &PrivateKey,
221        namespace: &[u8],
222        epoch: Epoch,
223        commitment: Public<MinSig>,
224        acks: Vec<Ack>,
225        reveals: Vec<group::Share>,
226    ) -> Self {
227        // Sign the resharing outcome
228        let payload =
229            Self::signature_payload_from_parts_pre_allegretto(epoch, &commitment, &acks, &reveals);
230        let dealer_signature = dealer_signer.sign(Some(namespace), payload.as_ref());
231
232        Self {
233            n_players,
234            dealer: dealer_signer.public_key(),
235            dealer_signature,
236            epoch,
237            commitment,
238            acks,
239            reveals,
240        }
241    }
242
243    /// Verifies the intermediate outcome's signature.
244    pub fn verify(&self, namespace: &[u8]) -> bool {
245        let payload = Self::signature_payload_from_parts(
246            self.n_players,
247            self.epoch,
248            &self.commitment,
249            &self.acks,
250            &self.reveals,
251        );
252        self.dealer
253            .verify(Some(namespace), &payload, &self.dealer_signature)
254    }
255
256    /// Verifies the intermediate outcome's signature.
257    ///
258    /// This method constructs a signature without the number players. This is
259    /// incorrect and addressed by [`Self::new`]. [`Self::new_pre_allegretto`]
260    /// exists for compatibility reasons and should only be used for hardforks
261    /// pre allegretto.
262    pub fn verify_pre_allegretto(&self, namespace: &[u8]) -> bool {
263        let payload = Self::signature_payload_from_parts_pre_allegretto(
264            self.epoch,
265            &self.commitment,
266            &self.acks,
267            &self.reveals,
268        );
269        self.dealer
270            .verify(Some(namespace), &payload, &self.dealer_signature)
271    }
272
273    /// Returns the payload that was signed by the dealer, formed from raw parts.
274    fn signature_payload_from_parts(
275        n_players: u16,
276        epoch: Epoch,
277        commitment: &Public<MinSig>,
278        acks: &Vec<Ack>,
279        reveals: &Vec<group::Share>,
280    ) -> Vec<u8> {
281        let mut buf = Vec::with_capacity(
282            UInt(n_players).encode_size()
283                + UInt(epoch).encode_size()
284                + commitment.encode_size()
285                + acks.encode_size()
286                + reveals.encode_size(),
287        );
288        UInt(n_players).write(&mut buf);
289        UInt(epoch).write(&mut buf);
290        commitment.write(&mut buf);
291        acks.write(&mut buf);
292        reveals.write(&mut buf);
293        buf
294    }
295
296    /// Returns the payload that was signed by the dealer, formed from raw parts.
297    ///
298    /// This method constructs a signature without the number players. This is
299    /// incorrect and addressed by [`Self::new`]. [`Self::new_pre_allegretto`]
300    /// exists for compatibility reasons and should only be used for hardforks
301    /// pre allegretto.
302    fn signature_payload_from_parts_pre_allegretto(
303        epoch: Epoch,
304        commitment: &Public<MinSig>,
305        acks: &Vec<Ack>,
306        reveals: &Vec<group::Share>,
307    ) -> Vec<u8> {
308        let mut buf = Vec::with_capacity(
309            UInt(epoch).encode_size()
310                + commitment.encode_size()
311                + acks.encode_size()
312                + reveals.encode_size(),
313        );
314        UInt(epoch).write(&mut buf);
315        commitment.write(&mut buf);
316        acks.write(&mut buf);
317        reveals.write(&mut buf);
318        buf
319    }
320
321    pub fn acks(&self) -> &[Ack] {
322        &self.acks
323    }
324
325    pub fn dealer(&self) -> &PublicKey {
326        &self.dealer
327    }
328
329    pub fn signature(&self) -> &Signature {
330        &self.dealer_signature
331    }
332
333    pub fn epoch(&self) -> Epoch {
334        self.epoch
335    }
336
337    pub fn commitment(&self) -> &Public<MinSig> {
338        &self.commitment
339    }
340
341    pub fn reveals(&self) -> &[group::Share] {
342        &self.reveals
343    }
344}
345
346impl Write for IntermediateOutcome {
347    fn write(&self, buf: &mut impl bytes::BufMut) {
348        UInt(self.n_players).write(buf);
349        self.dealer.write(buf);
350        self.dealer_signature.write(buf);
351        UInt(self.epoch).write(buf);
352        self.commitment.write(buf);
353        self.acks.write(buf);
354        self.reveals.write(buf);
355    }
356}
357
358impl EncodeSize for IntermediateOutcome {
359    fn encode_size(&self) -> usize {
360        UInt(self.n_players).encode_size()
361            + self.dealer.encode_size()
362            + self.dealer_signature.encode_size()
363            + UInt(self.epoch).encode_size()
364            + self.commitment.encode_size()
365            + self.acks.encode_size()
366            + self.reveals.encode_size()
367    }
368}
369
370impl Read for IntermediateOutcome {
371    type Cfg = ();
372
373    fn read_cfg(
374        buf: &mut impl bytes::Buf,
375        _cfg: &Self::Cfg,
376    ) -> Result<Self, commonware_codec::Error> {
377        let n_players: u16 = UInt::read(buf)?.into();
378
379        // Ensure is not 0 because otherwise `quorum(0)` would panic.
380        if n_players == 0 {
381            return Err(commonware_codec::Error::Invalid(
382                "n_players",
383                "cannot be zero",
384            ));
385        }
386
387        let dealer = PublicKey::read(buf)?;
388        let dealer_signature = Signature::read(buf)?;
389        let epoch = UInt::read(buf)?.into();
390        let commitment = Public::<MinSig>::read_cfg(buf, &(quorum(n_players as u32) as usize))?;
391
392        let acks = Vec::read_cfg(buf, &(RangeCfg::from(0..=n_players as usize), ()))?;
393        let reveals =
394            Vec::<group::Share>::read_cfg(buf, &(RangeCfg::from(0..=n_players as usize), ()))?;
395
396        Ok(Self {
397            n_players,
398            dealer,
399            dealer_signature,
400            epoch,
401            commitment,
402            acks,
403            reveals,
404        })
405    }
406}
407
408#[cfg(test)]
409mod tests {
410    use commonware_codec::{DecodeExt as _, Encode as _};
411    use commonware_cryptography::{
412        PrivateKeyExt as _, Signer as _,
413        bls12381::{dkg, primitives::variant::MinSig},
414        ed25519::{PrivateKey, PublicKey},
415    };
416    use commonware_utils::{set::Ordered, union};
417    use rand::{SeedableRng as _, rngs::StdRng};
418
419    const ACK_NAMESPACE: &[u8] = b"_DKG_ACK";
420    const OUTCOME_NAMESPACE: &[u8] = b"_DKG_OUTCOME";
421
422    use crate::{Ack, PublicOutcome};
423
424    use super::IntermediateOutcome;
425
426    fn four_private_keys() -> Ordered<PrivateKey> {
427        vec![
428            PrivateKey::from_seed(0),
429            PrivateKey::from_seed(1),
430            PrivateKey::from_seed(2),
431            PrivateKey::from_seed(3),
432        ]
433        .into()
434    }
435
436    fn four_public_keys() -> Ordered<PublicKey> {
437        vec![
438            PrivateKey::from_seed(0).public_key(),
439            PrivateKey::from_seed(1).public_key(),
440            PrivateKey::from_seed(2).public_key(),
441            PrivateKey::from_seed(3).public_key(),
442        ]
443        .into()
444    }
445
446    #[test]
447    fn dealing_outcome_roundtrip() {
448        let (_, commitment, shares) = dkg::Dealer::<_, MinSig>::new(
449            &mut StdRng::from_seed([0; 32]),
450            None,
451            four_public_keys(),
452        );
453
454        let acks = vec![
455            Ack::new(
456                &union(b"test", ACK_NAMESPACE),
457                four_private_keys()[0].clone(),
458                four_public_keys()[0].clone(),
459                42,
460                &four_public_keys()[0],
461                &commitment,
462            ),
463            Ack::new(
464                &union(b"test", ACK_NAMESPACE),
465                four_private_keys()[1].clone(),
466                four_public_keys()[1].clone(),
467                42,
468                &four_public_keys()[0],
469                &commitment,
470            ),
471            Ack::new(
472                &union(b"test", ACK_NAMESPACE),
473                four_private_keys()[2].clone(),
474                four_public_keys()[2].clone(),
475                42,
476                &four_public_keys()[0],
477                &commitment,
478            ),
479        ];
480        let reveals = vec![shares[3].clone()];
481        let dealing_outcome = IntermediateOutcome::new(
482            4,
483            &four_private_keys()[0],
484            &union(b"test", OUTCOME_NAMESPACE),
485            42,
486            commitment,
487            acks,
488            reveals,
489        );
490
491        let bytes = dealing_outcome.encode();
492        assert_eq!(
493            IntermediateOutcome::decode(&mut bytes.as_ref()).unwrap(),
494            dealing_outcome,
495        );
496    }
497
498    #[test]
499    fn dealing_outcome_roundtrip_without_reveals() {
500        let (_, commitment, _) = dkg::Dealer::<_, MinSig>::new(
501            &mut StdRng::from_seed([0; 32]),
502            None,
503            four_public_keys(),
504        );
505
506        let acks = vec![
507            Ack::new(
508                &union(b"test", ACK_NAMESPACE),
509                four_private_keys()[0].clone(),
510                four_public_keys()[0].clone(),
511                42,
512                &four_public_keys()[0],
513                &commitment,
514            ),
515            Ack::new(
516                &union(b"test", ACK_NAMESPACE),
517                four_private_keys()[1].clone(),
518                four_public_keys()[1].clone(),
519                42,
520                &four_public_keys()[0],
521                &commitment,
522            ),
523            Ack::new(
524                &union(b"test", ACK_NAMESPACE),
525                four_private_keys()[2].clone(),
526                four_public_keys()[2].clone(),
527                42,
528                &four_public_keys()[0],
529                &commitment,
530            ),
531            Ack::new(
532                &union(b"test", ACK_NAMESPACE),
533                four_private_keys()[3].clone(),
534                four_public_keys()[3].clone(),
535                42,
536                &four_public_keys()[0],
537                &commitment,
538            ),
539        ];
540        let reveals = vec![];
541        let dealing_outcome = IntermediateOutcome::new(
542            4,
543            &four_private_keys()[0],
544            &union(b"test", OUTCOME_NAMESPACE),
545            42,
546            commitment,
547            acks,
548            reveals,
549        );
550
551        let bytes = dealing_outcome.encode();
552        assert_eq!(
553            IntermediateOutcome::decode(&mut bytes.as_ref()).unwrap(),
554            dealing_outcome,
555        );
556    }
557
558    #[test]
559    fn public_outcome_roundtrip() {
560        let (_, commitment, _) = dkg::Dealer::<_, MinSig>::new(
561            &mut StdRng::from_seed([0; 32]),
562            None,
563            four_public_keys(),
564        );
565        let public_outcome = PublicOutcome {
566            epoch: 42,
567            participants: four_public_keys(),
568            public: commitment,
569        };
570        let bytes = public_outcome.encode();
571        assert_eq!(
572            PublicOutcome::decode(&mut bytes.as_ref()).unwrap(),
573            public_outcome,
574        );
575    }
576}