frost377/
keygen.rs

1use std::collections::BTreeMap;
2
3/// keygen: implements the keygen step of FROST
4///
5/// see: https://eprint.iacr.org/2020/852.pdf (page 12)
6// TODO:
7// - more documentation
8// - rework API design, think about api misuse potential
9// - add secure delete/zeroize-on-drop
10use ark_ff::{PrimeField, UniformRand, Zero};
11use rand::thread_rng;
12
13#[derive(Debug, Clone)]
14pub struct RoundOne {
15    /// commitments to <ai0, ai1, ai2 ...
16    commitments: Vec<decaf377::Element>,
17    from_participant: u32,
18
19    // proof of knowledge to ai0
20    ri: decaf377::Element,
21    ui: decaf377::Fr,
22}
23
24#[derive(Debug, Clone)]
25pub struct RoundTwo {
26    pub for_participant: u32,
27    pub from_participant: u32,
28    pub share: Share,
29}
30
31#[derive(Debug, Clone)]
32pub struct Share {
33    pub x: u32,
34    pub y: decaf377::Fr,
35}
36
37#[derive(Debug, Clone)]
38pub struct DKGOutput {
39    pub group_public_key: decaf377::Element,
40    pub private_share: decaf377::Fr,
41    pub participant_index: u32,
42}
43
44#[derive(Clone)]
45pub struct Participant {
46    secret_coeffs: Vec<decaf377::Fr>,
47    pub commitments: Vec<decaf377::Element>,
48    pub index: u32,
49
50    my_share: Share,
51    counterparty_shares: Vec<Share>,
52    counterparty_commitments: BTreeMap<u32, Vec<decaf377::Element>>,
53}
54
55// evaluates the polynomial specified by `coeffs` using Horner's method
56// (https://en.wikipedia.org/wiki/Horner%27s_method) at x
57fn evaluate_polynomial(x: decaf377::Fr, coeffs: &[decaf377::Fr]) -> decaf377::Fr {
58    coeffs
59        .iter()
60        .rev()
61        .fold(decaf377::Fr::zero(), |acc, coeff| acc * x + coeff)
62}
63
64impl Participant {
65    pub fn new(index: u32, t: u32) -> Self {
66        let rng = &mut thread_rng();
67        let secret_coeffs = (0..t).map(|_| decaf377::Fr::rand(rng)).collect::<Vec<_>>();
68        let public_commitments = secret_coeffs
69            .iter()
70            .map(|coeff| coeff * decaf377::basepoint())
71            .collect::<Vec<_>>();
72
73        Participant {
74            commitments: public_commitments,
75            my_share: Share {
76                x: index,
77                y: evaluate_polynomial(decaf377::Fr::from(index), &secret_coeffs),
78            },
79            secret_coeffs,
80            index,
81            counterparty_shares: Vec::new(),
82            counterparty_commitments: BTreeMap::new(),
83        }
84    }
85    pub fn round_one(&self) -> RoundOne {
86        let aio_commitment = self.secret_coeffs[0] * decaf377::basepoint();
87
88        // compute a proof of knowledge for ai0
89        let k = decaf377::Fr::rand(&mut thread_rng());
90        let ri = k * decaf377::basepoint();
91        let ci = blake2b_simd::Params::default()
92            .personal(b"keygen_challenge")
93            .to_state()
94            .update(&self.index.to_le_bytes())
95            .update("TODO: ANTI-REPLAY CONTEXT".as_bytes())
96            .update(aio_commitment.vartime_compress().0.as_ref())
97            .update(ri.vartime_compress().0.as_ref())
98            .finalize();
99
100        let ui = k + self.secret_coeffs[0] * decaf377::Fr::from_le_bytes_mod_order(ci.as_bytes());
101        RoundOne {
102            commitments: self.commitments.clone(),
103            ri,
104            ui,
105            from_participant: self.index,
106        }
107    }
108    pub fn verify_roundone(&mut self, others: Vec<RoundOne>) -> Result<(), anyhow::Error> {
109        let context_string = "TODO: ANTI-REPLAY CONTEXT";
110        for participant in others.iter() {
111            if participant.from_participant == self.index {
112                continue;
113            }
114            // verify RoundOne.ri = g^ui * theta_0^-cl
115            let ci = blake2b_simd::Params::default()
116                .personal(b"keygen_challenge")
117                .to_state()
118                .update(&participant.from_participant.to_le_bytes())
119                .update(context_string.as_bytes())
120                .update(participant.commitments[0].vartime_compress().0.as_ref())
121                .update(participant.ri.vartime_compress().0.as_ref())
122                .finalize();
123
124            let ci =
125                participant.commitments[0] * -decaf377::Fr::from_le_bytes_mod_order(ci.as_bytes());
126
127            // verify ui*G + Ci0*-ci = Ri
128            if participant.ri != (decaf377::basepoint() * participant.ui) + ci {
129                return Err(anyhow::anyhow!("could not verify participant's key share"));
130            }
131
132            // store this participant's commitments
133            self.counterparty_commitments.insert(
134                participant.from_participant,
135                participant.commitments.clone(),
136            );
137        }
138        Ok(())
139    }
140
141    pub fn round_two(&self, counterparty_index: u32) -> RoundTwo {
142        let fi = evaluate_polynomial(decaf377::Fr::from(counterparty_index), &self.secret_coeffs);
143
144        RoundTwo {
145            for_participant: counterparty_index,
146            from_participant: self.index,
147            share: Share {
148                x: counterparty_index,
149                y: fi,
150            },
151        }
152    }
153
154    pub fn verify_and_add_roundtwo_response(
155        &mut self,
156        counterparty_response: &RoundTwo,
157    ) -> Result<(), anyhow::Error> {
158        let commitments = self
159            .counterparty_commitments
160            .get(&counterparty_response.from_participant)
161            .ok_or(anyhow::anyhow!("counterparty commitments not found"))?;
162
163        // verify that the round two response is correct
164        counterparty_response.verify(commitments)?;
165
166        self.counterparty_shares
167            .push(counterparty_response.share.clone());
168
169        Ok(())
170    }
171
172    pub fn finalize(&self) -> Result<DKGOutput, anyhow::Error> {
173        // compute the group's public key
174        let mut group_public_key = self.commitments[0];
175        for commitment in self.counterparty_commitments.values() {
176            group_public_key = group_public_key + commitment[0];
177        }
178
179        // compute the private share
180        let mut private_share = self.my_share.y;
181        for other_share in self.counterparty_shares.iter() {
182            private_share = private_share + other_share.y;
183        }
184
185        Ok(DKGOutput {
186            group_public_key,
187            private_share,
188            participant_index: self.index,
189        })
190    }
191}
192
193impl RoundTwo {
194    pub fn verify(
195        &self,
196        counterparty_commitments: &[decaf377::Element],
197    ) -> Result<(), anyhow::Error> {
198        // step 2: verify the counterparty's shares, abort if the check fails
199        // compute fl(i)*G
200        let gfli = self.share.y * decaf377::basepoint();
201
202        // verify fl(i)*G = sum(Cl(k) * i^k)
203        let result = counterparty_commitments
204            .iter()
205            .rev()
206            .fold(decaf377::Element::default(), |acc, commitment| {
207                acc * decaf377::Fr::from(self.for_participant) + commitment
208            });
209        if result != gfli {
210            Err(anyhow::anyhow!("verification failed"))?
211        }
212
213        Ok(())
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    #[test]
222    fn test_roundone_generate_verify() {
223        let t = 3;
224        let n = 5;
225
226        let mut participants = Vec::new();
227        for i in 0..n {
228            participants.push(Participant::new(i, t));
229        }
230
231        let mut round1_messages = Vec::new();
232        for participant in participants.iter() {
233            round1_messages.push(participant.round_one());
234        }
235        for mut participant in participants {
236            participant
237                .verify_roundone(round1_messages.clone())
238                .unwrap();
239        }
240    }
241    #[test]
242    fn test_full_dkg() {
243        let t = 3;
244        let n = 5;
245
246        let mut participants = Vec::new();
247        for i in 1..n + 1 {
248            participants.push(Participant::new(i, t));
249        }
250
251        let mut round1_messages = Vec::new();
252        for participant in participants.iter() {
253            round1_messages.push(participant.round_one());
254        }
255        for participant in participants.iter_mut() {
256            participant
257                .verify_roundone(round1_messages.clone())
258                .unwrap();
259        }
260
261        let other_participants = participants.clone();
262
263        // each Pi sends to each other participant Pl (l, fi(l))
264        for (i, participant) in participants.iter_mut().enumerate() {
265            for (l, participant_other) in other_participants.iter().enumerate() {
266                if participant.index == participant_other.index {
267                    continue;
268                }
269                let round2_message = participant_other.round_two(participant.index);
270                println!("verifying round two message: {:?}", round2_message);
271                participant
272                    .verify_and_add_roundtwo_response(&round2_message)
273                    .unwrap();
274            }
275        }
276
277        let dkg_pubkey = participants[0].finalize().unwrap().group_public_key;
278        for participant in participants.iter() {
279            if participant.finalize().unwrap().group_public_key != dkg_pubkey {
280                panic!("group public key mismatch");
281            }
282        }
283    }
284}