Skip to main content

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::{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
16use super::receive_from_peer_with_progress;
17
18// Implementation of the classic Pedersen DKG for G2.
19
20struct DkgG2 {
21    num_peers: NumPeers,
22    identity: PeerId,
23    polynomial: Vec<Scalar>,
24    hash_commitments: BTreeMap<PeerId, sha256::Hash>,
25    commitments: BTreeMap<PeerId, Vec<G2Projective>>,
26    sk_shares: BTreeMap<PeerId, Scalar>,
27}
28
29impl DkgG2 {
30    fn new(num_peers: NumPeers, identity: PeerId) -> Self {
31        let polynomial = (0..num_peers.threshold())
32            .map(|_| Scalar::random(&mut OsRng))
33            .collect::<Vec<Scalar>>();
34
35        let commitment = polynomial.iter().map(g2).collect::<Vec<G2Projective>>();
36
37        DkgG2 {
38            num_peers,
39            identity,
40            polynomial,
41            hash_commitments: once((identity, commitment.consensus_hash_sha256())).collect(),
42            commitments: once((identity, commitment)).collect(),
43            sk_shares: BTreeMap::new(),
44        }
45    }
46
47    fn commitment(&self) -> Vec<G2Projective> {
48        self.polynomial.iter().map(g2).collect()
49    }
50
51    fn initial_message(&self) -> DkgMessageG2 {
52        DkgMessageG2::Hash(self.commitment().consensus_hash_sha256())
53    }
54
55    /// Runs a single step of the DKG algorithm
56    fn step(&mut self, peer: PeerId, msg: DkgMessageG2) -> anyhow::Result<DkgStepG2> {
57        trace!(?peer, ?msg, "Running DKG G2 step");
58        match msg {
59            DkgMessageG2::Hash(hash) => {
60                ensure!(
61                    self.hash_commitments.insert(peer, hash).is_none(),
62                    "DKG G2: peer {peer} sent us two hash commitments."
63                );
64
65                if self.hash_commitments.len() == self.num_peers.total() {
66                    return Ok(DkgStepG2::Broadcast(DkgMessageG2::Commitment(
67                        self.commitment(),
68                    )));
69                }
70            }
71            DkgMessageG2::Commitment(polynomial) => {
72                ensure!(
73                    *self.hash_commitments.get(&peer).with_context(|| format!(
74                        "DKG G2: hash commitment not found for peer {peer}"
75                    ))? == polynomial.consensus_hash_sha256(),
76                    "DKG G2: polynomial commitment from peer {peer} is of wrong degree."
77                );
78
79                ensure!(
80                    self.num_peers.threshold() == polynomial.len(),
81                    "DKG G2: polynomial commitment from peer {peer} is of wrong degree."
82                );
83
84                ensure!(
85                    self.commitments.insert(peer, polynomial).is_none(),
86                    "DKG G2: peer {peer} sent us two commitments."
87                );
88
89                // Once everyone has send their commitments, send out the key shares...
90
91                if self.commitments.len() == self.num_peers.total() {
92                    let mut messages = vec![];
93
94                    for peer in self.num_peers.peer_ids() {
95                        let s = eval_poly_scalar(&self.polynomial, &scalar(&peer));
96
97                        if peer == self.identity {
98                            self.sk_shares.insert(self.identity, s);
99                        } else {
100                            messages.push((peer, DkgMessageG2::Share(s)));
101                        }
102                    }
103
104                    return Ok(DkgStepG2::Messages(messages));
105                }
106            }
107            DkgMessageG2::Share(s) => {
108                let polynomial = self.commitments.get(&peer).with_context(|| {
109                    format!("DKG G2: polynomial commitment not found for peer {peer}.")
110                })?;
111
112                let checksum: G2Projective = polynomial
113                    .iter()
114                    .zip((0..).map(|k| scalar(&self.identity).pow(&[k, 0, 0, 0])))
115                    .map(|(c, x)| c * x)
116                    .reduce(|a, b| a + b)
117                    .expect("DKG G2: polynomial commitment from peer is empty.");
118
119                ensure!(g2(&s) == checksum, "DKG G2: share from {peer} is invalid.");
120
121                ensure!(
122                    self.sk_shares.insert(peer, s).is_none(),
123                    "Peer {peer} sent us two sk shares."
124                );
125
126                if self.sk_shares.len() == self.num_peers.total() {
127                    let sks = self.sk_shares.values().sum();
128
129                    let pks = (0..self.num_peers.threshold())
130                        .map(|i| {
131                            self.commitments
132                                .values()
133                                .map(|coefficients| coefficients[i])
134                                .reduce(|a, b| a + b)
135                                .expect("DKG G2: polynomial commitments are empty.")
136                        })
137                        .collect();
138
139                    return Ok(DkgStepG2::Result((pks, sks)));
140                }
141            }
142        }
143
144        Ok(DkgStepG2::Messages(vec![]))
145    }
146}
147
148/// Runs the DKG G2 algorithm with our peers. We do not handle any unexpected
149/// messages and all peers are expected to be cooperative.
150pub async fn run_dkg_g2(
151    num_peers: NumPeers,
152    identity: PeerId,
153    connections: &DynP2PConnections<P2PMessage>,
154) -> anyhow::Result<(Vec<G2Projective>, Scalar)> {
155    let mut dkg = DkgG2::new(num_peers, identity);
156
157    connections.send(
158        Recipient::Everyone,
159        P2PMessage::DkgG2(dkg.initial_message()),
160    );
161
162    loop {
163        for peer in num_peers.peer_ids().filter(|p| *p != identity) {
164            let message = receive_from_peer_with_progress(connections, peer, "DKG G2")
165                .await
166                .context("Unexpected shutdown of p2p connections during dkg g2")?;
167
168            let message = match message {
169                P2PMessage::DkgG2(message) => message,
170                _ => bail!("Received unexpected message during DKG G2: {message:?}"),
171            };
172
173            match dkg.step(peer, message)? {
174                DkgStepG2::Broadcast(message) => {
175                    connections.send(Recipient::Everyone, P2PMessage::DkgG2(message));
176                }
177                DkgStepG2::Messages(messages) => {
178                    for (peer, message) in messages {
179                        connections.send(Recipient::Peer(peer), P2PMessage::DkgG2(message));
180                    }
181                }
182                DkgStepG2::Result(result) => {
183                    return Ok(result);
184                }
185            }
186        }
187    }
188}
189
190fn eval_poly_scalar(coefficients: &[Scalar], x: &Scalar) -> Scalar {
191    coefficients
192        .iter()
193        .copied()
194        .rev()
195        .reduce(|acc, coefficient| acc * x + coefficient)
196        .expect("We have at least one coefficient")
197}
198
199enum DkgStepG2 {
200    Broadcast(DkgMessageG2),
201    Messages(Vec<(PeerId, DkgMessageG2)>),
202    Result((Vec<G2Projective>, Scalar)),
203}
204
205#[cfg(test)]
206mod tests;