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
20struct 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 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 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
158pub 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}