fedimint_server/config/
dkg.rs

1use std::collections::BTreeMap;
2use std::iter::once;
3
4use anyhow::{Context, bail, ensure};
5use async_trait::async_trait;
6use bls12_381::{G1Projective, G2Projective, Scalar};
7use fedimint_core::bitcoin::hashes::sha256;
8use fedimint_core::config::{DkgMessage, P2PMessage};
9use fedimint_core::encoding::Encodable as _;
10use fedimint_core::net::peers::{DynP2PConnections, Recipient};
11use fedimint_core::{NumPeers, PeerId};
12use fedimint_logging::LOG_NET_PEER_DKG;
13use fedimint_server_core::config::{PeerHandleOps, g1, g2, scalar};
14use group::ff::Field;
15use rand::rngs::OsRng;
16use tracing::info;
17
18use super::peer_handle::PeerHandle;
19
20// Implementation of the classic Pedersen DKG.
21
22struct Dkg {
23    num_peers: NumPeers,
24    identity: PeerId,
25    polynomial: Vec<Scalar>,
26    hash_commitments: BTreeMap<PeerId, sha256::Hash>,
27    commitments: BTreeMap<PeerId, Vec<(G1Projective, G2Projective)>>,
28    sk_shares: BTreeMap<PeerId, Scalar>,
29}
30
31impl Dkg {
32    fn new(num_peers: NumPeers, identity: PeerId) -> Self {
33        let polynomial = (0..num_peers.threshold())
34            .map(|_| Scalar::random(&mut OsRng))
35            .collect::<Vec<Scalar>>();
36
37        let commitment = polynomial
38            .iter()
39            .map(|f| (g1(f), g2(f)))
40            .collect::<Vec<(G1Projective, G2Projective)>>();
41
42        Dkg {
43            num_peers,
44            identity,
45            polynomial,
46            hash_commitments: once((identity, commitment.consensus_hash_sha256())).collect(),
47            commitments: once((identity, commitment)).collect(),
48            sk_shares: BTreeMap::new(),
49        }
50    }
51
52    fn commitment(&self) -> Vec<(G1Projective, G2Projective)> {
53        self.polynomial.iter().map(|f| (g1(f), g2(f))).collect()
54    }
55
56    fn initial_message(&self) -> DkgMessage {
57        DkgMessage::Hash(self.commitment().consensus_hash_sha256())
58    }
59
60    /// Runs a single step of the DKG algorithm
61    fn step(&mut self, peer: PeerId, msg: DkgMessage) -> anyhow::Result<DkgStep> {
62        match msg {
63            DkgMessage::Hash(hash) => {
64                ensure!(
65                    self.hash_commitments.insert(peer, hash).is_none(),
66                    "DKG: peer {peer} sent us two hash commitments."
67                );
68
69                if self.hash_commitments.len() == self.num_peers.total() {
70                    return Ok(DkgStep::Broadcast(DkgMessage::Commitment(
71                        self.commitment(),
72                    )));
73                }
74            }
75            DkgMessage::Commitment(polynomial) => {
76                ensure!(
77                    *self
78                        .hash_commitments
79                        .get(&peer)
80                        .context("DKG: hash commitment not found for peer {peer}")?
81                        == polynomial.consensus_hash_sha256(),
82                    "DKG: polynomial commitment from peer {peer} is of wrong degree."
83                );
84
85                ensure!(
86                    self.num_peers.threshold() == polynomial.len(),
87                    "DKG: polynomial commitment from peer {peer} is of wrong degree."
88                );
89
90                ensure!(
91                    self.commitments.insert(peer, polynomial).is_none(),
92                    "DKG: peer {peer} sent us two commitments."
93                );
94
95                // Once everyone has send their commitments, send out the key shares...
96
97                if self.commitments.len() == self.num_peers.total() {
98                    let mut messages = vec![];
99
100                    for peer in self.num_peers.peer_ids() {
101                        let s = eval_poly_scalar(&self.polynomial, &scalar(&peer));
102
103                        if peer == self.identity {
104                            self.sk_shares.insert(self.identity, s);
105                        } else {
106                            messages.push((peer, DkgMessage::Share(s)));
107                        }
108                    }
109
110                    return Ok(DkgStep::Messages(messages));
111                }
112            }
113            DkgMessage::Share(s) => {
114                let polynomial = self
115                    .commitments
116                    .get(&peer)
117                    .context("DKG: polynomial commitment not found for peer {peer}.")?;
118
119                let checksum: (G1Projective, G2Projective) = polynomial
120                    .iter()
121                    .zip((0..).map(|k| scalar(&self.identity).pow(&[k, 0, 0, 0])))
122                    .map(|(c, x)| (c.0 * x, c.1 * x))
123                    .reduce(|(a1, a2), (b1, b2)| (a1 + b1, a2 + b2))
124                    .expect("DKG: polynomial commitment from peer {peer} is empty.");
125
126                ensure!(
127                    (g1(&s), g2(&s)) == checksum,
128                    "DKG: share from {peer} is invalid."
129                );
130
131                ensure!(
132                    self.sk_shares.insert(peer, s).is_none(),
133                    "Peer {peer} sent us two sk shares."
134                );
135
136                if self.sk_shares.len() == self.num_peers.total() {
137                    let sks = self.sk_shares.values().sum();
138
139                    let pks = (0..self.num_peers.threshold())
140                        .map(|i| {
141                            self.commitments
142                                .values()
143                                .map(|coefficients| coefficients[i])
144                                .reduce(|(a1, a2), (b1, b2)| (a1 + b1, a2 + b2))
145                                .expect("DKG: polynomial commitments are empty.")
146                        })
147                        .collect();
148
149                    return Ok(DkgStep::Result((pks, sks)));
150                }
151            }
152        }
153
154        Ok(DkgStep::Messages(vec![]))
155    }
156}
157
158/// Runs the DKG algorithms with our peers. We do not handle any unexpected
159/// messages and all peers are expected to be cooperative.
160pub async fn run_dkg(
161    num_peers: NumPeers,
162    identity: PeerId,
163    connections: &DynP2PConnections<P2PMessage>,
164) -> anyhow::Result<(Vec<(G1Projective, G2Projective)>, Scalar)> {
165    let mut dkg = Dkg::new(num_peers, identity);
166
167    connections
168        .send(Recipient::Everyone, P2PMessage::Dkg(dkg.initial_message()))
169        .await;
170
171    loop {
172        for peer in num_peers.peer_ids().filter(|p| *p != identity) {
173            let message = connections
174                .receive_from_peer(peer)
175                .await
176                .context("Unexpected shutdown of p2p connections during dkg")?;
177
178            let message = match message {
179                P2PMessage::Dkg(message) => message,
180                _ => bail!("Received unexpected message: {message:?}"),
181            };
182
183            match dkg.step(peer, message)? {
184                DkgStep::Broadcast(message) => {
185                    connections
186                        .send(Recipient::Everyone, P2PMessage::Dkg(message))
187                        .await;
188                }
189                DkgStep::Messages(messages) => {
190                    for (peer, message) in messages {
191                        connections
192                            .send(Recipient::Peer(peer), P2PMessage::Dkg(message))
193                            .await;
194                    }
195                }
196                DkgStep::Result(result) => {
197                    return Ok(result);
198                }
199            }
200        }
201    }
202}
203
204fn eval_poly_scalar(coefficients: &[Scalar], x: &Scalar) -> Scalar {
205    coefficients
206        .iter()
207        .copied()
208        .rev()
209        .reduce(|acc, coefficient| acc * x + coefficient)
210        .expect("We have at least one coefficient")
211}
212
213enum DkgStep {
214    Broadcast(DkgMessage),
215    Messages(Vec<(PeerId, DkgMessage)>),
216    Result((Vec<(G1Projective, G2Projective)>, Scalar)),
217}
218
219#[async_trait]
220impl<'a> PeerHandleOps for PeerHandle<'a> {
221    fn num_peers(&self) -> NumPeers {
222        self.num_peers
223    }
224
225    async fn run_dkg_g1(&self) -> anyhow::Result<(Vec<G1Projective>, Scalar)> {
226        info!(
227            target: LOG_NET_PEER_DKG,
228            "Running distributed key generation for group G1..."
229        );
230
231        run_dkg(self.num_peers, self.identity, self.connections)
232            .await
233            .map(|(poly, sk)| (poly.into_iter().map(|c| c.0).collect(), sk))
234    }
235
236    async fn run_dkg_g2(&self) -> anyhow::Result<(Vec<G2Projective>, Scalar)> {
237        info!(
238            target: LOG_NET_PEER_DKG,
239            "Running distributed key generation for group G2..."
240        );
241
242        run_dkg(self.num_peers, self.identity, self.connections)
243            .await
244            .map(|(poly, sk)| (poly.into_iter().map(|c| c.1).collect(), sk))
245    }
246
247    async fn exchange_bytes(&self, bytes: Vec<u8>) -> anyhow::Result<BTreeMap<PeerId, Vec<u8>>> {
248        info!(
249            target: LOG_NET_PEER_DKG,
250            "Exchanging raw bytes..."
251        );
252
253        let mut peer_data: BTreeMap<PeerId, Vec<u8>> = BTreeMap::new();
254
255        self.connections
256            .send(Recipient::Everyone, P2PMessage::Encodable(bytes.clone()))
257            .await;
258
259        peer_data.insert(self.identity, bytes);
260
261        for peer in self.num_peers.peer_ids().filter(|p| *p != self.identity) {
262            let message = self
263                .connections
264                .receive_from_peer(peer)
265                .await
266                .context("Unexpected shutdown of p2p connections")?;
267
268            match message {
269                P2PMessage::Encodable(bytes) => {
270                    peer_data.insert(peer, bytes);
271                }
272                message => {
273                    anyhow::bail!("Invalid message from {peer}: {message:?}");
274                }
275            }
276        }
277
278        Ok(peer_data)
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use std::collections::{BTreeMap, VecDeque};
285
286    use bls12_381::{G1Projective, G2Projective};
287    use fedimint_core::{NumPeersExt, PeerId};
288    use fedimint_server_core::config::{eval_poly_g1, eval_poly_g2, g1, g2};
289    use group::Curve;
290
291    use crate::config::dkg::{Dkg, DkgStep};
292
293    #[test_log::test]
294    fn test_dkg() {
295        let peers = (0..7_u16).map(PeerId::from).collect::<Vec<PeerId>>();
296
297        let mut dkgs = peers
298            .iter()
299            .map(|peer| (*peer, Dkg::new(peers.to_num_peers(), *peer)))
300            .collect::<BTreeMap<PeerId, Dkg>>();
301
302        let mut steps = dkgs
303            .iter()
304            .map(|(peer, dkg)| (*peer, DkgStep::Broadcast(dkg.initial_message())))
305            .collect::<VecDeque<(PeerId, DkgStep)>>();
306
307        let mut keys = BTreeMap::new();
308
309        while keys.len() < peers.len() {
310            match steps.pop_front().unwrap() {
311                (send_peer, DkgStep::Broadcast(message)) => {
312                    for receive_peer in peers.iter().filter(|p| **p != send_peer) {
313                        let step = dkgs
314                            .get_mut(receive_peer)
315                            .unwrap()
316                            .step(send_peer, message.clone());
317
318                        steps.push_back((*receive_peer, step.unwrap()));
319                    }
320                }
321                (send_peer, DkgStep::Messages(messages)) => {
322                    for (receive_peer, messages) in messages {
323                        let step = dkgs
324                            .get_mut(&receive_peer)
325                            .unwrap()
326                            .step(send_peer, messages);
327
328                        steps.push_back((receive_peer, step.unwrap()));
329                    }
330                }
331                (send_peer, DkgStep::Result(step_keys)) => {
332                    keys.insert(send_peer, step_keys);
333                }
334            }
335        }
336
337        assert!(steps.is_empty());
338
339        for (peer, (poly, sks)) in keys {
340            let poly_g1: Vec<G1Projective> = poly.clone().into_iter().map(|c| c.0).collect();
341            let poly_g2: Vec<G2Projective> = poly.clone().into_iter().map(|c| c.1).collect();
342
343            assert_eq!(poly_g1.len(), 5);
344            assert_eq!(eval_poly_g1(&poly_g1, &peer), g1(&sks).to_affine());
345
346            assert_eq!(poly_g2.len(), 5);
347            assert_eq!(eval_poly_g2(&poly_g2, &peer), g2(&sks).to_affine());
348        }
349    }
350}