dkls23/keygen/
utils.rs

1// Copyright (c) Silence Laboratories Pte. Ltd. All Rights Reserved.
2// This software is licensed under the Silence Laboratories License Agreement.
3
4//! This module provides utility functions used throughout the DKG protocol implementation,
5//! including polynomial operations, key generation setup, and protocol execution helpers.
6//! It also includes test utilities for protocol simulation and verification.
7
8use std::collections::HashMap;
9
10#[cfg(any(test, feature = "test-support"))]
11use std::sync::Arc;
12
13use k256::{
14    elliptic_curve::subtle::ConstantTimeEq, NonZeroScalar, ProjectivePoint,
15    Scalar, Secp256k1,
16};
17#[cfg(any(test, feature = "test-support"))]
18use rand::prelude::*;
19#[cfg(any(test, feature = "test-support"))]
20use sha2::{Digest, Sha256};
21
22use sl_mpc_mate::math::birkhoff_coeffs;
23
24#[cfg(any(test, feature = "test-support"))]
25use crate::setup::{keygen::SetupMessage, *};
26
27#[cfg(any(test, feature = "test-support"))]
28use crate::setup::{
29    keygen::SetupMessage as KeygenSetupMessage,
30    quorum_change::SetupMessage as QuorumChangeSetupMessage,
31};
32use crate::sign::get_lagrange_coeff_list;
33
34#[cfg(any(test, feature = "test-support"))]
35use super::Keyshare;
36
37use super::KeygenError;
38
39/// Computes the Lagrange coefficient for a given point in a polynomial.
40///
41/// This function calculates the Lagrange coefficient for a specific point `x_i`
42/// given a list of points `x_i_list` and their corresponding party IDs.
43///
44/// # Arguments
45/// * `x_i` - The point for which to compute the coefficient
46/// * `x_i_list` - List of all points in the polynomial
47/// * `party_ids` - List of party IDs corresponding to the points
48///
49/// # Returns
50/// The computed Lagrange coefficient as a scalar value
51pub(crate) fn get_lagrange_coeff(
52    x_i: &NonZeroScalar,
53    x_i_list: &[NonZeroScalar],
54    party_ids: &[u8],
55) -> Scalar {
56    let mut coeff = Scalar::ONE;
57    let x_i = x_i as &Scalar;
58    for &party_id in party_ids {
59        let x_j = &x_i_list[party_id as usize] as &Scalar;
60        if x_i.ct_ne(x_j).into() {
61            let sub = x_j - x_i;
62            coeff *= x_j * &sub.invert().unwrap();
63        }
64    }
65
66    coeff
67}
68
69/// Computes Birkhoff coefficients for a set of points with ranks.
70///
71/// This function calculates the Birkhoff coefficients for a set of points with
72/// associated ranks, which are used in the Birkhoff interpolation process.
73///
74/// # Arguments
75/// * `rank_list` - List of ranks for each point
76/// * `x_i_list` - List of points in the polynomial
77/// * `party_ids` - List of party IDs corresponding to the points
78///
79/// # Returns
80/// A map of party IDs to their corresponding Birkhoff coefficients
81pub(crate) fn get_birkhoff_coefficients(
82    rank_list: &[u8],
83    x_i_list: &[NonZeroScalar],
84    party_ids: &[u8],
85) -> HashMap<usize, Scalar> {
86    let params = party_ids
87        .iter()
88        .map(|&pid| {
89            (x_i_list[pid as usize], rank_list[pid as usize] as usize)
90        })
91        .collect::<Vec<_>>();
92
93    let betta_vec = birkhoff_coeffs::<Secp256k1>(&params);
94
95    party_ids
96        .iter()
97        .zip(betta_vec)
98        .map(|(&pid, w_i)| (pid as usize, w_i))
99        .collect::<HashMap<_, _>>()
100}
101
102/// Verifies that a secret can be recovered from the given shares.
103///
104/// This function checks whether the provided shares can be used to reconstruct
105/// the original secret by verifying that the reconstructed public key matches
106/// the expected public key.
107///
108/// # Arguments
109/// * `x_i_list` - List of x-coordinates for the shares
110/// * `rank_list` - List of ranks for each share
111/// * `big_s_list` - List of public key components
112/// * `public_key` - The expected public key
113///
114/// # Returns
115/// `Ok(())` if the secret can be recovered, or an error if verification fails
116#[allow(dead_code)]
117pub(crate) fn check_secret_recovery(
118    x_i_list: &[NonZeroScalar],
119    rank_list: &[u8],
120    big_s_list: &[ProjectivePoint],
121    public_key: &ProjectivePoint,
122) -> Result<(), KeygenError> {
123    // If ranks are all zero, then we use lagrange interpolation
124    let exp_public_key = if rank_list.iter().all(|&r| r == 0) {
125        let coeff_vector = get_lagrange_coeff_list(x_i_list, |x| x);
126        big_s_list
127            .iter()
128            .zip(coeff_vector)
129            .fold(ProjectivePoint::IDENTITY, |acc, (point, betta_i)| {
130                acc + point * &betta_i
131            })
132    } else {
133        // Otherwise, we use Birkhoff interpolation
134        let mut party_params_list = x_i_list
135            .iter()
136            .zip(rank_list)
137            .zip(big_s_list)
138            .collect::<Vec<((&NonZeroScalar, &u8), &ProjectivePoint)>>();
139
140        party_params_list.sort_by_key(|((_, &n_i), _)| n_i);
141
142        let params = party_params_list
143            .iter()
144            .map(|((&x_i, &n_i), _)| (x_i, n_i as usize))
145            .collect::<Vec<_>>();
146
147        let betta_vector = birkhoff_coeffs(&params);
148
149        party_params_list
150            .into_iter()
151            .map(|((_, _), &big_s_i)| big_s_i)
152            .zip(betta_vector)
153            .fold(ProjectivePoint::IDENTITY, |acc, (point, betta_i)| {
154                acc + point * betta_i
155            })
156    };
157
158    (public_key == &exp_public_key)
159        .then_some(())
160        .ok_or(KeygenError::PublicKeyMismatch)
161}
162
163/// Generates setup messages and seeds for DKG parties.
164///
165/// This function creates the necessary setup messages and seeds for all parties
166/// participating in the DKG protocol. It handles both the case where ranks are
167/// provided and where they are not.
168///
169/// # Arguments
170/// * `instance` - Optional instance identifier for the protocol
171/// * `t` - Threshold value for the protocol
172/// * `n` - Total number of parties
173/// * `ranks` - Optional list of ranks for each party
174///
175/// # Returns
176/// A vector of tuples containing setup messages and seeds for each party
177#[cfg(any(test, feature = "test-support"))]
178pub fn setup_keygen(
179    instance: Option<[u8; 32]>,
180    t: u8,
181    n: u8,
182    ranks: Option<&[u8]>,
183) -> Vec<(KeygenSetupMessage, [u8; 32])> {
184    use std::time::Duration;
185
186    use sl_mpc_mate::message::InstanceId;
187
188    let ranks = if let Some(ranks) = ranks {
189        assert_eq!(ranks.len(), n as usize);
190        ranks.to_vec()
191    } else {
192        vec![0u8; n as usize]
193    };
194
195    let instance = instance.unwrap_or_else(rand::random);
196
197    // a signing key for each party.
198    let party_sk: Vec<NoSigningKey> = std::iter::repeat_with(|| NoSigningKey)
199        .take(n as usize)
200        .collect();
201
202    let party_vk: Vec<NoVerifyingKey> = party_sk
203        .iter()
204        .enumerate()
205        .map(|(party_id, _)| NoVerifyingKey::new(party_id))
206        .collect();
207
208    party_sk
209        .into_iter()
210        .enumerate()
211        .map(|(party_id, sk)| {
212            SetupMessage::new(
213                InstanceId::new(instance),
214                sk,
215                party_id,
216                party_vk.clone(),
217                &ranks,
218                t as usize,
219            )
220            .with_ttl(Duration::from_secs(1000)) // for dkls-metrics benchmarks
221        })
222        .map(|setup| {
223            let mixin = [setup.participant_index() as u8 + 1];
224
225            (
226                setup,
227                Sha256::new()
228                    .chain_update(instance)
229                    .chain_update(b"party-seed")
230                    .chain_update(mixin)
231                    .finalize()
232                    .into(),
233            )
234        })
235        .collect::<Vec<_>>()
236}
237
238/// Executes the DKG protocol with the given parameters.
239///
240/// This function runs the DKG protocol for a set of parties with the specified
241/// threshold and number of participants. It returns the generated key shares.
242///
243/// # Arguments
244/// * `t` - Threshold value for the protocol
245/// * `n` - Total number of parties
246/// * `ranks` - Optional list of ranks for each party
247///
248/// # Returns
249/// A vector of key shares, one for each party
250#[cfg(any(test, feature = "test-support"))]
251pub async fn gen_keyshares(
252    t: u8,
253    n: u8,
254    ranks: Option<&[u8]>,
255) -> Vec<Arc<Keyshare>> {
256    let coord = sl_mpc_mate::coord::SimpleMessageRelay::new();
257
258    let mut parties = tokio::task::JoinSet::new();
259    for (setup, seed) in setup_keygen(None, t, n, ranks) {
260        parties.spawn({
261            let relay = coord.connect();
262            crate::keygen::run(setup, seed, relay)
263        });
264    }
265
266    let mut shares = vec![];
267
268    while let Some(fini) = parties.join_next().await {
269        if let Err(ref err) = fini {
270            println!("error {err:?}");
271        } else {
272            match fini.unwrap() {
273                Err(err) => panic!("err {:?}", err),
274                Ok(share) => shares.push(Arc::new(share)),
275            }
276        }
277    }
278
279    shares.sort_by_key(|share| share.party_id);
280
281    shares
282}
283
284/// Generates setup messages and seeds for quorum change parties.
285///
286/// This function creates setup messages and seeds for all parties participating
287/// in a quorum change protocol, including both existing and new parties.
288///
289/// # Arguments
290/// * `old_keyshares` - Existing key shares from the current quorum
291/// * `new_threshold` - New threshold value for the protocol
292/// * `new_n_i_list` - List of ranks for new parties
293///
294/// # Returns
295/// A vector of tuples containing setup messages and seeds for each party
296#[cfg(any(test, feature = "test-support"))]
297pub fn setup_quorum_change(
298    old_keyshares: &[Arc<Keyshare>],
299    new_threshold: u8,
300    new_n_i_list: &[u8],
301) -> Vec<(QuorumChangeSetupMessage, [u8; 32])> {
302    let old_threshold = old_keyshares[0].threshold as usize;
303    let old_participants = old_keyshares.len();
304    assert!(old_keyshares.len() >= old_threshold);
305
306    let public_key = old_keyshares[0].public_key();
307
308    let total_parties = old_participants + new_n_i_list.len();
309
310    let old_parties = (0..old_participants).collect::<Vec<usize>>();
311    let new_parties = new_n_i_list
312        .iter()
313        .enumerate()
314        .map(|(p, &r)| ((p + old_participants), r))
315        .collect::<Vec<_>>();
316
317    let mut rng = rand::thread_rng();
318
319    let instance = rng.gen::<[u8; 32]>().into();
320
321    // a signing key for each party.
322    let party_sk: Vec<NoSigningKey> = std::iter::repeat_with(|| NoSigningKey)
323        .take(total_parties)
324        .collect();
325
326    let party_vk: Vec<NoVerifyingKey> = party_sk
327        .iter()
328        .enumerate()
329        .map(|(p, _)| NoVerifyingKey::new(p))
330        .collect();
331
332    party_sk
333        .into_iter()
334        .enumerate()
335        .map(|(p, sk)| {
336            // first old_parties_n with Keyshare
337            let keyshare = if p < old_participants {
338                Some(old_keyshares[p].clone())
339            } else {
340                None
341            };
342
343            QuorumChangeSetupMessage::new(
344                instance,
345                p,
346                &old_parties,
347                &new_parties,
348                new_threshold as usize,
349                sk,
350                party_vk.clone(),
351                public_key,
352            )
353            .with_keyshare_opt(keyshare)
354        })
355        .map(|setup| (setup, rng.gen()))
356        .collect::<Vec<_>>()
357}
358
359/// Generates setup messages and seeds for extending the quorum with new parties.
360///
361/// This function creates setup messages and seeds for a quorum change that
362/// adds new parties to the existing quorum.
363///
364/// # Arguments
365/// * `old_keyshares` - Existing key shares from the current quorum
366/// * `new_threshold` - New threshold value for the protocol
367/// * `new_participants_len` - Number of new participants to add
368/// * `new_n_i_list` - List of ranks for new parties
369///
370/// # Returns
371/// A vector of tuples containing setup messages and seeds for each party
372#[cfg(any(test, feature = "test-support"))]
373pub fn setup_quorum_change_extend_parties(
374    old_keyshares: &[Arc<Keyshare>],
375    new_threshold: u8,
376    new_participants_len: u8,
377    new_n_i_list: &[u8],
378) -> Vec<(QuorumChangeSetupMessage, [u8; 32])> {
379    let new_n = old_keyshares.len() + new_participants_len as usize;
380
381    let old_threshold = old_keyshares[0].threshold as usize;
382    let old_participants = old_keyshares.len();
383    assert!(old_keyshares.len() >= old_threshold);
384
385    let public_key = old_keyshares[0].public_key();
386
387    let total_parties = new_n;
388
389    let old_parties = (0..old_keyshares.len()).collect::<Vec<_>>();
390    let new_parties = new_n_i_list
391        .iter()
392        .enumerate()
393        .map(|(p, &r)| (p, r))
394        .collect::<Vec<_>>();
395
396    let mut rng = rand::thread_rng();
397
398    let instance = rng.gen::<[u8; 32]>().into();
399
400    // a signing key for each party.
401    let party_sk: Vec<NoSigningKey> = std::iter::repeat_with(|| NoSigningKey)
402        .take(total_parties)
403        .collect();
404    let party_vk: Vec<NoVerifyingKey> = party_sk
405        .iter()
406        .enumerate()
407        .map(|(p, _)| NoVerifyingKey::new(p))
408        .collect();
409
410    party_sk
411        .into_iter()
412        .enumerate()
413        .map(|(p, sk)| {
414            // first old_parties_n with Keyshare
415            let keyshare = if p < old_participants {
416                Some(old_keyshares[p].clone())
417            } else {
418                None
419            };
420
421            QuorumChangeSetupMessage::new(
422                instance,
423                p,
424                &old_parties,
425                &new_parties,
426                new_threshold as usize,
427                sk,
428                party_vk.clone(),
429                public_key,
430            )
431            .with_keyshare_opt(keyshare)
432        })
433        .map(|setup| (setup, rng.gen()))
434        .collect::<Vec<_>>()
435}
436
437/// Generates setup messages and seeds for changing the quorum threshold.
438///
439/// This function creates setup messages and seeds for a quorum change that
440/// modifies the threshold value while keeping the same set of parties.
441///
442/// # Arguments
443/// * `old_keyshares` - Existing key shares from the current quorum
444/// * `new_threshold` - New threshold value for the protocol
445/// * `new_n_i_list` - List of ranks for the parties
446///
447/// # Returns
448/// A vector of tuples containing setup messages and seeds for each party
449#[cfg(any(test, feature = "test-support"))]
450pub fn setup_quorum_change_threshold(
451    old_keyshares: &[Arc<Keyshare>],
452    new_threshold: u8,
453    new_n_i_list: &[u8],
454) -> Vec<(QuorumChangeSetupMessage, [u8; 32])> {
455    assert!(old_keyshares.len() >= old_keyshares[0].threshold as usize);
456
457    let total_parties = old_keyshares.len();
458
459    let old_participants = old_keyshares.len();
460
461    let public_key = old_keyshares[0].public_key();
462
463    let old_parties = (0..old_keyshares.len()).collect::<Vec<_>>();
464
465    let new_parties =
466        new_n_i_list.iter().copied().enumerate().collect::<Vec<_>>();
467
468    let mut rng = rand::thread_rng();
469
470    let instance = rng.gen::<[u8; 32]>().into();
471
472    // a signing key for each party.
473    let party_sk: Vec<NoSigningKey> = std::iter::repeat_with(|| NoSigningKey)
474        .take(total_parties)
475        .collect();
476    let party_vk: Vec<NoVerifyingKey> = party_sk
477        .iter()
478        .enumerate()
479        .map(|(p, _)| NoVerifyingKey::new(p))
480        .collect();
481
482    party_sk
483        .into_iter()
484        .enumerate()
485        .map(|(p, sk)| {
486            // first old_parties_n with Keyshare
487            let keyshare = if p < old_participants {
488                Some(old_keyshares[p].clone())
489            } else {
490                None
491            };
492
493            QuorumChangeSetupMessage::new(
494                instance,
495                p,
496                &old_parties,
497                &new_parties,
498                new_threshold as usize,
499                sk,
500                party_vk.clone(),
501                public_key,
502            )
503            .with_keyshare_opt(keyshare)
504        })
505        .map(|setup| (setup, rng.gen()))
506        .collect::<Vec<_>>()
507}