1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3use 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#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
26pub enum PromotionError {
27 #[error("invalid participant (1 <= participant <= {n}, yet participant is {participant})")]
29 InvalidParticipant {
30 n: u16,
32 participant: Participant,
34 },
35
36 #[error("incorrect amount of participants. {t} <= amount <= {n}, yet amount is {amount}")]
38 IncorrectAmountOfParticipants {
39 t: u16,
41 n: u16,
43 amount: usize,
45 },
46
47 #[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#[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
86pub 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 pub fn promote<R: RngCore + CryptoRng>(
102 rng: &mut R,
103 base: ThresholdKeys<C1>,
104 ) -> (GeneratorPromotion<C1, C2>, GeneratorProof<C1>) {
105 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 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}