dkg_promote/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3// This crate requires `dleq` which doesn't support no-std via std-shims
4// #![cfg_attr(not(feature = "std"), no_std)]
5
6use core::{marker::PhantomData, ops::Deref};
7use std::{
8  io::{self, Read, Write},
9  collections::HashMap,
10};
11
12use rand_core::{RngCore, CryptoRng};
13
14use ciphersuite::{group::GroupEncoding, Ciphersuite};
15
16use transcript::{Transcript, RecommendedTranscript};
17use dleq::DLEqProof;
18
19pub use dkg::*;
20
21#[cfg(test)]
22mod tests;
23
24/// Errors encountered when promoting keys.
25#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
26pub enum PromotionError {
27  /// Invalid participant identifier.
28  #[error("invalid participant (1 <= participant <= {n}, yet participant is {participant})")]
29  InvalidParticipant {
30    /// The total amount of participants.
31    n: u16,
32    /// The specified participant.
33    participant: Participant,
34  },
35
36  /// An incorrect amount of participants was specified.
37  #[error("incorrect amount of participants. {t} <= amount <= {n}, yet amount is {amount}")]
38  IncorrectAmountOfParticipants {
39    /// The threshold required.
40    t: u16,
41    /// The total amount of participants.
42    n: u16,
43    /// The amount of participants specified.
44    amount: usize,
45  },
46
47  /// Participant provided an invalid proof.
48  #[error("invalid proof {0}")]
49  InvalidProof(Participant),
50}
51
52fn transcript<G: GroupEncoding>(key: &G, i: Participant) -> RecommendedTranscript {
53  let mut transcript = RecommendedTranscript::new(b"DKG Generator Promotion v0.2");
54  transcript.append_message(b"group_key", key.to_bytes());
55  transcript.append_message(b"participant", i.to_bytes());
56  transcript
57}
58
59/// Proof of valid promotion to another generator.
60#[derive(Clone, Copy)]
61pub struct GeneratorProof<C: Ciphersuite> {
62  share: C::G,
63  proof: DLEqProof<C::G>,
64}
65
66impl<C: Ciphersuite> GeneratorProof<C> {
67  pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
68    writer.write_all(self.share.to_bytes().as_ref())?;
69    self.proof.write(writer)
70  }
71
72  pub fn read<R: Read>(reader: &mut R) -> io::Result<GeneratorProof<C>> {
73    Ok(GeneratorProof {
74      share: <C as Ciphersuite>::read_G(reader)?,
75      proof: DLEqProof::read(reader)?,
76    })
77  }
78
79  pub fn serialize(&self) -> Vec<u8> {
80    let mut buf = vec![];
81    self.write(&mut buf).unwrap();
82    buf
83  }
84}
85
86/// Promote a set of keys from one generator to another, where the elliptic curve is the same.
87///
88/// Since the Ciphersuite trait additionally specifies a generator, this provides an O(n) way to
89/// update the generator used with keys. This outperforms the key generation protocol which is
90/// exponential.
91pub struct GeneratorPromotion<C1: Ciphersuite, C2: Ciphersuite> {
92  base: ThresholdKeys<C1>,
93  proof: GeneratorProof<C1>,
94  _c2: PhantomData<C2>,
95}
96
97impl<C1: Ciphersuite, C2: Ciphersuite<F = C1::F, G = C1::G>> GeneratorPromotion<C1, C2> {
98  /// Begin promoting keys from one generator to another.
99  ///
100  /// Returns a proof this share was properly promoted.
101  pub fn promote<R: RngCore + CryptoRng>(
102    rng: &mut R,
103    base: ThresholdKeys<C1>,
104  ) -> (GeneratorPromotion<C1, C2>, GeneratorProof<C1>) {
105    // Do a DLEqProof for the new generator
106    let proof = GeneratorProof {
107      share: C2::generator() * base.original_secret_share().deref(),
108      proof: DLEqProof::prove(
109        rng,
110        &mut transcript(&base.original_group_key(), base.params().i()),
111        &[C1::generator(), C2::generator()],
112        base.original_secret_share(),
113      ),
114    };
115
116    (GeneratorPromotion { base, proof, _c2: PhantomData::<C2> }, proof)
117  }
118
119  /// Complete promotion by taking in the proofs from all other participants.
120  pub fn complete(
121    self,
122    proofs: &HashMap<Participant, GeneratorProof<C1>>,
123  ) -> Result<ThresholdKeys<C2>, PromotionError> {
124    let params = self.base.params();
125    if proofs.len() != (usize::from(params.n()) - 1) {
126      Err(PromotionError::IncorrectAmountOfParticipants {
127        t: params.n(),
128        n: params.n(),
129        amount: proofs.len() + 1,
130      })?;
131    }
132    for i in proofs.keys().copied() {
133      if u16::from(i) > params.n() {
134        Err(PromotionError::InvalidParticipant { n: params.n(), participant: i })?;
135      }
136    }
137
138    let mut verification_shares = HashMap::new();
139    verification_shares.insert(params.i(), self.proof.share);
140    for i in 1 ..= params.n() {
141      let i = Participant::new(i).unwrap();
142      if i == params.i() {
143        continue;
144      }
145
146      let proof = proofs.get(&i).unwrap();
147      proof
148        .proof
149        .verify(
150          &mut transcript(&self.base.original_group_key(), i),
151          &[C1::generator(), C2::generator()],
152          &[self.base.original_verification_share(i), proof.share],
153        )
154        .map_err(|_| PromotionError::InvalidProof(i))?;
155      verification_shares.insert(i, proof.share);
156    }
157
158    Ok(
159      ThresholdKeys::new(
160        params,
161        self.base.interpolation().clone(),
162        self.base.original_secret_share().clone(),
163        verification_shares,
164      )
165      .unwrap(),
166    )
167  }
168}