fedimint_server/config/
dkg_g2.rs

1use std::collections::BTreeMap;
2use std::iter::once;
3
4use anyhow::{Context, bail, ensure};
5use bls12_381::{G2Projective, Scalar};
6use fedimint_core::bitcoin::hashes::sha256;
7use fedimint_core::config::{DkgMessageG2, P2PMessage};
8use fedimint_core::encoding::Encodable as _;
9use fedimint_core::net::peers::{DynP2PConnections, Recipient};
10use fedimint_core::{NumPeers, PeerId};
11use fedimint_server_core::config::{g2, scalar};
12use group::ff::Field;
13use rand::rngs::OsRng;
14use tracing::trace;
15
16// Implementation of the classic Pedersen DKG for G2.
17
18struct DkgG2 {
19    num_peers: NumPeers,
20    identity: PeerId,
21    polynomial: Vec<Scalar>,
22    hash_commitments: BTreeMap<PeerId, sha256::Hash>,
23    commitments: BTreeMap<PeerId, Vec<G2Projective>>,
24    sk_shares: BTreeMap<PeerId, Scalar>,
25}
26
27impl DkgG2 {
28    fn new(num_peers: NumPeers, identity: PeerId) -> Self {
29        let polynomial = (0..num_peers.threshold())
30            .map(|_| Scalar::random(&mut OsRng))
31            .collect::<Vec<Scalar>>();
32
33        let commitment = polynomial.iter().map(g2).collect::<Vec<G2Projective>>();
34
35        DkgG2 {
36            num_peers,
37            identity,
38            polynomial,
39            hash_commitments: once((identity, commitment.consensus_hash_sha256())).collect(),
40            commitments: once((identity, commitment)).collect(),
41            sk_shares: BTreeMap::new(),
42        }
43    }
44
45    fn commitment(&self) -> Vec<G2Projective> {
46        self.polynomial.iter().map(g2).collect()
47    }
48
49    fn initial_message(&self) -> DkgMessageG2 {
50        DkgMessageG2::Hash(self.commitment().consensus_hash_sha256())
51    }
52
53    /// Runs a single step of the DKG algorithm
54    fn step(&mut self, peer: PeerId, msg: DkgMessageG2) -> anyhow::Result<DkgStepG2> {
55        trace!(?peer, ?msg, "Running DKG G2 step");
56        match msg {
57            DkgMessageG2::Hash(hash) => {
58                ensure!(
59                    self.hash_commitments.insert(peer, hash).is_none(),
60                    "DKG G2: peer {peer} sent us two hash commitments."
61                );
62
63                if self.hash_commitments.len() == self.num_peers.total() {
64                    return Ok(DkgStepG2::Broadcast(DkgMessageG2::Commitment(
65                        self.commitment(),
66                    )));
67                }
68            }
69            DkgMessageG2::Commitment(polynomial) => {
70                ensure!(
71                    *self.hash_commitments.get(&peer).with_context(|| format!(
72                        "DKG G2: hash commitment not found for peer {peer}"
73                    ))? == polynomial.consensus_hash_sha256(),
74                    "DKG G2: polynomial commitment from peer {peer} is of wrong degree."
75                );
76
77                ensure!(
78                    self.num_peers.threshold() == polynomial.len(),
79                    "DKG G2: polynomial commitment from peer {peer} is of wrong degree."
80                );
81
82                ensure!(
83                    self.commitments.insert(peer, polynomial).is_none(),
84                    "DKG G2: peer {peer} sent us two commitments."
85                );
86
87                // Once everyone has send their commitments, send out the key shares...
88
89                if self.commitments.len() == self.num_peers.total() {
90                    let mut messages = vec![];
91
92                    for peer in self.num_peers.peer_ids() {
93                        let s = eval_poly_scalar(&self.polynomial, &scalar(&peer));
94
95                        if peer == self.identity {
96                            self.sk_shares.insert(self.identity, s);
97                        } else {
98                            messages.push((peer, DkgMessageG2::Share(s)));
99                        }
100                    }
101
102                    return Ok(DkgStepG2::Messages(messages));
103                }
104            }
105            DkgMessageG2::Share(s) => {
106                let polynomial = self.commitments.get(&peer).with_context(|| {
107                    format!("DKG G2: polynomial commitment not found for peer {peer}.")
108                })?;
109
110                let checksum: G2Projective = polynomial
111                    .iter()
112                    .zip((0..).map(|k| scalar(&self.identity).pow(&[k, 0, 0, 0])))
113                    .map(|(c, x)| c * x)
114                    .reduce(|a, b| a + b)
115                    .expect("DKG G2: polynomial commitment from peer is empty.");
116
117                ensure!(g2(&s) == checksum, "DKG G2: share from {peer} is invalid.");
118
119                ensure!(
120                    self.sk_shares.insert(peer, s).is_none(),
121                    "Peer {peer} sent us two sk shares."
122                );
123
124                if self.sk_shares.len() == self.num_peers.total() {
125                    let sks = self.sk_shares.values().sum();
126
127                    let pks = (0..self.num_peers.threshold())
128                        .map(|i| {
129                            self.commitments
130                                .values()
131                                .map(|coefficients| coefficients[i])
132                                .reduce(|a, b| a + b)
133                                .expect("DKG G2: polynomial commitments are empty.")
134                        })
135                        .collect();
136
137                    return Ok(DkgStepG2::Result((pks, sks)));
138                }
139            }
140        }
141
142        Ok(DkgStepG2::Messages(vec![]))
143    }
144}
145
146/// Runs the DKG G2 algorithm with our peers. We do not handle any unexpected
147/// messages and all peers are expected to be cooperative.
148pub async fn run_dkg_g2(
149    num_peers: NumPeers,
150    identity: PeerId,
151    connections: &DynP2PConnections<P2PMessage>,
152) -> anyhow::Result<(Vec<G2Projective>, Scalar)> {
153    let mut dkg = DkgG2::new(num_peers, identity);
154
155    connections.send(
156        Recipient::Everyone,
157        P2PMessage::DkgG2(dkg.initial_message()),
158    );
159
160    loop {
161        for peer in num_peers.peer_ids().filter(|p| *p != identity) {
162            let message = connections
163                .receive_from_peer(peer)
164                .await
165                .context("Unexpected shutdown of p2p connections during dkg g2")?;
166
167            let message = match message {
168                P2PMessage::DkgG2(message) => message,
169                _ => bail!("Received unexpected message during DKG G2: {message:?}"),
170            };
171
172            match dkg.step(peer, message)? {
173                DkgStepG2::Broadcast(message) => {
174                    connections.send(Recipient::Everyone, P2PMessage::DkgG2(message));
175                }
176                DkgStepG2::Messages(messages) => {
177                    for (peer, message) in messages {
178                        connections.send(Recipient::Peer(peer), P2PMessage::DkgG2(message));
179                    }
180                }
181                DkgStepG2::Result(result) => {
182                    return Ok(result);
183                }
184            }
185        }
186    }
187}
188
189fn eval_poly_scalar(coefficients: &[Scalar], x: &Scalar) -> Scalar {
190    coefficients
191        .iter()
192        .copied()
193        .rev()
194        .reduce(|acc, coefficient| acc * x + coefficient)
195        .expect("We have at least one coefficient")
196}
197
198enum DkgStepG2 {
199    Broadcast(DkgMessageG2),
200    Messages(Vec<(PeerId, DkgMessageG2)>),
201    Result((Vec<G2Projective>, Scalar)),
202}
203
204#[cfg(test)]
205mod tests {
206    use std::collections::{BTreeMap, VecDeque};
207
208    use fedimint_core::{NumPeersExt, PeerId};
209    use fedimint_server_core::config::{eval_poly_g2, g2};
210    use group::Curve;
211
212    use super::{DkgG2, DkgStepG2};
213
214    #[test_log::test]
215    fn test_dkg_g2() {
216        let peers = (0..7_u16).map(PeerId::from).collect::<Vec<PeerId>>();
217
218        let mut dkgs = peers
219            .iter()
220            .map(|peer| (*peer, DkgG2::new(peers.to_num_peers(), *peer)))
221            .collect::<BTreeMap<PeerId, DkgG2>>();
222
223        let mut steps = dkgs
224            .iter()
225            .map(|(peer, dkg)| (*peer, DkgStepG2::Broadcast(dkg.initial_message())))
226            .collect::<VecDeque<(PeerId, DkgStepG2)>>();
227
228        let mut keys = BTreeMap::new();
229
230        while keys.len() < peers.len() {
231            match steps.pop_front().unwrap() {
232                (send_peer, DkgStepG2::Broadcast(message)) => {
233                    for receive_peer in peers.iter().filter(|p| **p != send_peer) {
234                        let step = dkgs
235                            .get_mut(receive_peer)
236                            .unwrap()
237                            .step(send_peer, message.clone());
238
239                        steps.push_back((*receive_peer, step.unwrap()));
240                    }
241                }
242                (send_peer, DkgStepG2::Messages(messages)) => {
243                    for (receive_peer, message) in messages {
244                        let step = dkgs
245                            .get_mut(&receive_peer)
246                            .unwrap()
247                            .step(send_peer, message);
248
249                        steps.push_back((receive_peer, step.unwrap()));
250                    }
251                }
252                (send_peer, DkgStepG2::Result(step_keys)) => {
253                    keys.insert(send_peer, step_keys);
254                }
255            }
256        }
257
258        assert!(steps.is_empty());
259
260        for (peer, (poly_g2, sks)) in keys {
261            assert_eq!(poly_g2.len(), 5);
262            assert_eq!(eval_poly_g2(&poly_g2, &peer), g2(&sks).to_affine());
263        }
264    }
265}