dkls23/keygen/
quorum_change.rs

1// Copyright (c) Silence Laboratories Pte. Ltd. All Rights Reserved.
2// This software is licensed under the Silence Laboratories License Agreement.
3
4//! Module for implementing the Quorum Change Protocol.
5//! The protocol supports:
6//! - Adding new participants
7//! - Removing existing participants
8use k256::{
9    elliptic_curve::{group::GroupEncoding, subtle::ConstantTimeEq, Group},
10    NonZeroScalar, ProjectivePoint, Scalar,
11};
12use rand::prelude::*;
13use rand_chacha::ChaCha20Rng;
14use sha2::{Digest, Sha256};
15
16use sl_mpc_mate::{
17    coord::*,
18    math::{
19        feldman_verify, polynomial_coeff_multipliers, GroupPolynomial,
20        Polynomial,
21    },
22    message::MsgId,
23    SessionId,
24};
25use sl_oblivious::{
26    endemic_ot::{EndemicOTMsg1, EndemicOTReceiver, EndemicOTSender},
27    soft_spoken::{build_pprf, eval_pprf},
28};
29
30use crate::{
31    keygen::{
32        broadcast_4,
33        constants::*,
34        get_all_but_one_session_id, get_base_ot_session_id,
35        messages::*,
36        utils::{
37            check_secret_recovery, get_birkhoff_coefficients,
38            get_lagrange_coeff,
39        },
40        KeygenError, Keyshare,
41    },
42    pairs::Pairs,
43    proto::{tags::*, *},
44    setup::{QuorumChangeSetupMessage, ABORT_MESSAGE_TAG},
45    Seed,
46};
47
48#[cfg(feature = "multi-thread")]
49use tokio::task::block_in_place;
50
51#[cfg(not(feature = "multi-thread"))]
52fn block_in_place<F, R>(f: F) -> R
53where
54    F: FnOnce() -> R,
55{
56    f()
57}
58
59/// Executes the Quorum Change Protocol.
60///
61/// This function orchestrates the quorum change process, allowing participants to:
62/// - Add new participants to the quorum
63/// - Remove existing participants
64/// - Change the threshold value
65/// - Modify participant ranks
66///
67/// # Type Parameters
68///
69/// * `T` - A type implementing the `QuorumChangeSetupMessage` trait
70/// * `R` - A type implementing the `Relay` trait for message communication
71///
72/// # Arguments
73///
74/// * `setup` - The protocol setup configuration containing participant information
75/// * `seed` - The random seed for cryptographic operations
76/// * `relay` - The message relay for communication between parties
77///
78/// # Returns
79///
80/// * `Ok(Some(Keyshare))` - The new key share if the protocol succeeds
81/// * `Ok(None)` - If the participant is not part of the new quorum
82/// * `Err(KeygenError)` - If the protocol fails
83///
84/// # Errors
85///
86/// This function may return the following errors:
87/// * `KeygenError::AbortProtocol` - If the protocol is aborted by a participant
88/// * `KeygenError::SendMessage` - If there's an error sending messages
89/// * Other `KeygenError` variants for various protocol failures
90pub async fn run<T, R>(
91    setup: T,
92    seed: Seed,
93    relay: R,
94) -> Result<Option<Keyshare>, KeygenError>
95where
96    T: QuorumChangeSetupMessage<Keyshare, ProjectivePoint>,
97    R: Relay,
98{
99    let abort_msg = create_abort_message(&setup);
100    let mut relay = FilteredMsgRelay::new(relay);
101
102    let result = match run_inner(setup, seed, &mut relay).await {
103        Ok(share) => Ok(share),
104        Err(KeygenError::AbortProtocol(p)) => {
105            Err(KeygenError::AbortProtocol(p))
106        }
107        Err(KeygenError::SendMessage) => Err(KeygenError::SendMessage),
108        Err(err) => {
109            // ignore error of sending abort message
110            let _ = relay.send(abort_msg).await;
111            Err(err)
112        }
113    };
114
115    let _ = relay.close().await;
116
117    result
118}
119
120/// Internal implementation of the Quorum Change Protocol.
121///
122/// This function contains the core logic for the quorum change protocol,
123/// handling the cryptographic operations and message exchanges between participants.
124///
125/// # Type Parameters
126///
127/// * `T` - A type implementing the `QuorumChangeSetupMessage` trait
128/// * `R` - A type implementing the `Relay` trait for message communication
129///
130/// # Arguments
131///
132/// * `setup` - The protocol setup configuration
133/// * `seed` - The random seed for cryptographic operations
134/// * `relay` - The message relay for communication between parties
135///
136/// # Returns
137///
138/// * `Ok(Some(Keyshare))` - The new key share if the protocol succeeds
139/// * `Ok(None)` - If the participant is not part of the new quorum
140/// * `Err(KeygenError)` - If the protocol fails
141#[allow(non_snake_case)]
142pub(crate) async fn run_inner<T, R>(
143    setup: T,
144    seed: Seed,
145    relay: &mut FilteredMsgRelay<R>,
146) -> Result<Option<Keyshare>, KeygenError>
147where
148    T: QuorumChangeSetupMessage<Keyshare, ProjectivePoint>,
149    R: Relay,
150{
151    let mut rng = ChaCha20Rng::from_seed(seed);
152
153    let mut scheme = crate::proto::Scheme::new(&mut rng);
154
155    let expected_public_key = setup.expected_public_key();
156    assert!(expected_public_key != &ProjectivePoint::IDENTITY);
157
158    let NEW_T = setup.new_threshold() as usize;
159    let NEW_N = setup.new_party_indices().len();
160
161    let new_x_i_list: Vec<NonZeroScalar> = (1..=NEW_N as u32)
162        .map(Scalar::from)
163        .map(|s| NonZeroScalar::new(s).unwrap())
164        .collect();
165
166    let my_party_index = setup.participant_index();
167    let my_old_party_id = setup.old_keyshare().map(|k| k.party_id);
168    let my_party_is_old = my_old_party_id.is_some();
169    let my_new_party_id = setup.new_party_id(my_party_index);
170    let my_party_is_new = my_new_party_id.is_some();
171
172    relay.ask_messages(&setup, ABORT_MESSAGE_TAG, false).await?;
173
174    let _r0 = relay
175        .ask_messages_from_slice(
176            &setup,
177            QC_MSG_R0,
178            setup.old_party_indices(),
179            false,
180        )
181        .await?;
182
183    relay.ask_messages(&setup, QC_MSG_R1, false).await?;
184
185    let _p2p_1 = if my_party_is_new {
186        relay
187            .ask_messages_from_slice(
188                &setup,
189                QC_MSG_P2P_1,
190                setup.old_party_indices(),
191                true,
192            )
193            .await?
194    } else {
195        0
196    };
197
198    let _p2p_2 = if my_party_is_new {
199        relay
200            .ask_messages_from_slice(
201                &setup,
202                QC_MSG_P2P_2,
203                setup.old_party_indices(),
204                true,
205            )
206            .await?
207    } else {
208        0
209    };
210
211    let _r2 = relay
212        .ask_messages_from_slice(
213            &setup,
214            QC_MSG_R2,
215            setup.old_party_indices(),
216            false,
217        )
218        .await?;
219
220    let sid_i = SessionId::new(rng.gen());
221
222    let mut old_party_ids = Pairs::new();
223
224    if let Some(party_id) = my_old_party_id {
225        // Broadcast our old-party-id
226        relay
227            .send(SignedMessage::build(
228                &setup.msg_id(None, QC_MSG_R0),
229                setup.message_ttl().as_secs() as _,
230                0,
231                setup.signer(),
232                |msg: &mut u8, _| {
233                    *msg = party_id;
234                },
235            ))
236            .await?;
237        old_party_ids.push(my_party_index, party_id);
238    }
239
240    Round::new(_r0, QC_MSG_R0, relay)
241        .of_signed_messages(
242            &setup,
243            KeygenError::AbortProtocol,
244            |&party_id: &u8, index| {
245                old_party_ids.push(index, party_id);
246                Ok(())
247            },
248        )
249        .await?;
250
251    // only for old parties
252    // calculate additive share s_i_0 of participant_i,
253    // \sum_{i=0}^{n-1} s_i_0 = private_key
254    let s_i_0 = setup
255        .old_keyshare()
256        .map(|keyshare| {
257            let my_old_party_id = keyshare.party_id as usize;
258
259            let s_i = keyshare.s_i();
260            let old_rank_list = keyshare.rank_list();
261            let old_x_i_list = keyshare.x_i_list();
262            let x_i = old_x_i_list[my_old_party_id];
263
264            assert!(
265                setup.old_party_indices().len()
266                    >= keyshare.threshold as usize
267            );
268
269            let old_party_id_list = old_party_ids.remove_ids();
270
271            let all_ranks_zero = old_rank_list.iter().all(|&r| r == 0);
272
273            let lambda = if all_ranks_zero {
274                get_lagrange_coeff(&x_i, &old_x_i_list, &old_party_id_list)
275            } else {
276                get_birkhoff_coefficients(
277                    &old_rank_list,
278                    &old_x_i_list,
279                    &old_party_id_list,
280                )
281                .get(&my_old_party_id)
282                .copied()
283                .unwrap_or_default()
284            };
285
286            lambda * s_i
287        })
288        .unwrap_or_default();
289
290    // only for old parties
291    let mut polynomial = Polynomial::random(&mut rng, NEW_T - 1);
292    polynomial.set_constant(s_i_0);
293
294    let big_p_i_poly = polynomial.commit();
295    let r1_i = rng.gen();
296
297    let commitment1_i = if my_party_is_old {
298        hash_commitment_1(&sid_i, my_party_index, &big_p_i_poly, &r1_i)
299    } else {
300        [0u8; 32]
301    };
302
303    // Broadcast 1 from all parties to all
304    let (sid_i_list, enc_pub_keys, commitment1_list, _) = broadcast_4(
305        &setup,
306        relay,
307        QC_MSG_R1,
308        (sid_i, scheme.public_key().to_vec(), commitment1_i, ()),
309    )
310    .await?;
311
312    for (receiver, pub_key) in enc_pub_keys.into_iter().enumerate() {
313        if receiver != setup.participant_index() {
314            scheme
315                .receiver_public_key(receiver, &pub_key)
316                .map_err(|_| KeygenError::InvalidMessage)?;
317        }
318    }
319
320    let final_session_id: [u8; 32] = sid_i_list
321        .iter()
322        .fold(Sha256::new(), |hash, sid| hash.chain_update(sid))
323        .finalize()
324        .into();
325
326    // Old party_i sends p2p commit values to new parties
327    let mut p_i_list: Pairs<Scalar, u8> = Pairs::new();
328    if my_party_is_old && my_party_is_new {
329        let my_old_party_id = my_old_party_id.unwrap();
330        let my_new_party_id = my_new_party_id.unwrap();
331        let my_new_rank = setup.new_participant_rank(my_new_party_id);
332        let x_i = new_x_i_list[my_new_party_id as usize];
333        let p_i_i = block_in_place(|| {
334            polynomial.derivative_at(my_new_rank as usize, &x_i)
335        });
336        p_i_list.push(my_old_party_id, p_i_i);
337    }
338
339    // blind commitment2 values for receiver_ids
340    let mut r2_j_list: Pairs<[u8; 32], u8> = Pairs::new();
341    let mut p_i_j_list: Pairs<Scalar, u8> = Pairs::new();
342    if my_party_is_old {
343        for &receiver_index in setup.new_party_indices() {
344            if receiver_index == my_party_index {
345                continue;
346            }
347            let receiver_id = setup.new_party_id(receiver_index).unwrap();
348
349            let r2_j: [u8; 32] = rng.gen();
350            r2_j_list.push(receiver_id, r2_j);
351
352            let party_j_rank = setup.new_participant_rank(receiver_id);
353            let x_j = new_x_i_list[receiver_id as usize];
354            let p_i_j = block_in_place(|| {
355                polynomial.derivative_at(party_j_rank as usize, &x_j)
356            });
357            p_i_j_list.push(receiver_id, p_i_j);
358
359            let commitment_2_i = hash_commitment_2(
360                &final_session_id,
361                my_party_index,
362                receiver_index,
363                &p_i_j,
364                &r2_j,
365            );
366
367            let mut enc_msg1 = EncryptedMessage::<QCP2PMsg1>::new(
368                &setup.msg_id(Some(receiver_index), QC_MSG_P2P_1),
369                setup.message_ttl().as_secs() as u32,
370                0,
371                0,
372                &scheme,
373            );
374
375            let (msg1, _) = enc_msg1.payload(&scheme);
376            msg1.commitment_2_i = commitment_2_i;
377
378            // send out P2P message. We call feed() in the loop
379            // and following send_broadcast() will call .send() that
380            // implies feed() + flush()
381            relay
382                .feed(
383                    enc_msg1
384                        .encrypt(&mut scheme, receiver_index)
385                        .ok_or(KeygenError::SendMessage)?,
386                )
387                .await
388                .map_err(|_| KeygenError::SendMessage)?;
389        }
390    }
391
392    // new_party collects all old_t commitments2 from old parties
393    let mut commitment2_list: Pairs<[u8; 32], u8> = Pairs::new();
394
395    Round::new(_p2p_1, QC_MSG_P2P_1, relay)
396        .of_encrypted_messages(
397            &setup,
398            &mut scheme,
399            0,
400            KeygenError::AbortProtocol,
401            |p2p_msg1: &QCP2PMsg1, from_party_index, _, _| {
402                let from_party_id =
403                    *old_party_ids.find_pair(from_party_index);
404
405                commitment2_list.push(from_party_id, p2p_msg1.commitment_2_i);
406
407                Ok(None)
408            },
409        )
410        .await?;
411
412    // Old party_i sends p2p decommit2 values to new parties
413    // and broadcast decommit1
414    let decommit_data = if let Some(keyshare) = setup.old_keyshare() {
415        for &receiver_index in setup.new_party_indices() {
416            if receiver_index == my_party_index {
417                continue;
418            }
419            let receiver_id = setup.new_party_id(receiver_index).unwrap();
420
421            let p_i_j = p_i_j_list.find_pair(receiver_id);
422            let r2_j = r2_j_list.find_pair(receiver_id);
423
424            let mut enc_msg2 = EncryptedMessage::<QCP2PMsg2>::new(
425                &setup.msg_id(Some(receiver_index), QC_MSG_P2P_2),
426                setup.message_ttl().as_secs() as u32,
427                0,
428                0,
429                &scheme,
430            );
431
432            let (msg2, _) = enc_msg2.payload(&scheme);
433            msg2.p_i = encode_scalar(p_i_j);
434            msg2.r_2_i = *r2_j;
435            msg2.root_chain_code = keyshare.root_chain_code;
436
437            // send out R2 P2P message. We call feed() in the loop
438            // and following send_broadcast() will call .send() that
439            // implies feed() + flush()
440            relay
441                .feed(
442                    enc_msg2
443                        .encrypt(&mut scheme, receiver_index)
444                        .ok_or(KeygenError::SendMessage)?,
445                )
446                .await
447                .map_err(|_| KeygenError::SendMessage)?;
448        }
449
450        // Broadcast 2 from old parties to all
451        let (big_p_j_poly_list, r1_j_list, _, _) =
452            Round::new(_r2, QC_MSG_R2, relay)
453                .broadcast_4(&setup, (big_p_i_poly.clone(), r1_i, (), ()))
454                .await?;
455
456        // checks for old party
457        for &old_party_index in setup.old_party_indices() {
458            let r1_j = r1_j_list.find_pair(old_party_index);
459            let sid_j = &sid_i_list[old_party_index];
460            let commitment1 = &commitment1_list[old_party_index];
461            let big_p_i_poly = big_p_j_poly_list.find_pair(old_party_index);
462
463            if big_p_i_poly.coeffs.len() != NEW_T {
464                return Err(KeygenError::InvalidMessage);
465            }
466
467            if big_p_i_poly.points().any(|p| p.is_identity().into()) {
468                return Err(KeygenError::InvalidPolynomialPoint);
469            }
470
471            let commit_hash1 =
472                hash_commitment_1(sid_j, old_party_index, big_p_i_poly, r1_j);
473            if commit_hash1.ct_ne(commitment1).into() {
474                return Err(KeygenError::InvalidCommitmentHash);
475            }
476        }
477
478        let mut big_p_vec = GroupPolynomial::identity(NEW_T);
479        for (_, v) in &big_p_j_poly_list {
480            big_p_vec.add_mut(v); // big_f_vec += v;
481        }
482
483        if &big_p_vec.get_constant() != expected_public_key {
484            return Err(KeygenError::PublicKeyMismatch);
485        }
486
487        // complete the protocol for an old party that is not in the list of new parties
488        if !my_party_is_new {
489            return Ok(None);
490        }
491
492        Some(big_p_j_poly_list)
493    } else {
494        None
495    };
496
497    let mut root_chain_code_list = setup
498        .old_keyshare()
499        .map(|share| {
500            Pairs::new_with_item(share.party_id, share.root_chain_code)
501        })
502        .unwrap_or_default();
503
504    // new_party processes all old_t decommits2 from old parties
505    // and processes decommit1
506    Round::new(_p2p_2, QC_MSG_P2P_2, relay)
507        .of_encrypted_messages(
508            &setup,
509            &mut scheme,
510            0,
511            KeygenError::AbortProtocol,
512            |p2p_msg2: &QCP2PMsg2, from_party_index, _, _| {
513                let from_party_id =
514                    *old_party_ids.find_pair(from_party_index);
515
516                let p_j_i = decode_scalar(&p2p_msg2.p_i)
517                    .ok_or(KeygenError::InvalidMessage)?;
518
519                let commitment2 = commitment2_list.find_pair(from_party_id);
520
521                let commit_hash_2 = hash_commitment_2(
522                    &final_session_id,
523                    from_party_index,
524                    my_party_index,
525                    &p_j_i,
526                    &p2p_msg2.r_2_i,
527                );
528
529                if commit_hash_2.ct_ne(commitment2).into() {
530                    return Err(KeygenError::InvalidCommitmentHash);
531                }
532
533                p_i_list.push(from_party_id, p_j_i);
534
535                root_chain_code_list
536                    .push(from_party_id, p2p_msg2.root_chain_code);
537
538                Ok(None)
539            },
540        )
541        .await?;
542
543    // check that root_chain_code_list contains the same elements
544    let root_chain_code_list = root_chain_code_list.remove_ids();
545    let root_chain_code = root_chain_code_list[0];
546    if !root_chain_code_list
547        .iter()
548        .all(|&item| item == root_chain_code)
549    {
550        return Err(KeygenError::InvalidQuorumChange);
551    };
552
553    let big_p_j_poly_list = if let Some(decommit_data) = decommit_data {
554        decommit_data
555    } else {
556        // only for new parties, not for old parties
557
558        let (big_p_j_poly_list, r1_j_list, _, _) =
559            Round::new(_r2, QC_MSG_R2, relay)
560                .recv_broadcast_4::<_, _, _, (), ()>(
561                    &setup,
562                    &[big_p_i_poly.external_size(), 32, 0, 0],
563                )
564                .await?;
565
566        // checks for new party
567        for &old_party_index in setup.old_party_indices() {
568            let r1_j = r1_j_list.find_pair(old_party_index);
569            let sid_j = &sid_i_list[old_party_index];
570            let commitment1 = &commitment1_list[old_party_index];
571            let big_p_i_vec: &GroupPolynomial<ProjectivePoint> =
572                big_p_j_poly_list.find_pair(old_party_index);
573
574            if big_p_i_vec.coeffs.len() != NEW_T {
575                return Err(KeygenError::InvalidMessage);
576            }
577
578            if big_p_i_vec.points().any(|p| p.is_identity().into()) {
579                return Err(KeygenError::InvalidPolynomialPoint);
580            }
581
582            let commit_hash1 =
583                hash_commitment_1(sid_j, old_party_index, big_p_i_vec, r1_j);
584            if commit_hash1.ct_ne(commitment1).into() {
585                return Err(KeygenError::InvalidCommitmentHash);
586            }
587        }
588
589        big_p_j_poly_list
590    };
591
592    let mut big_p_poly = GroupPolynomial::identity(NEW_T);
593
594    // sort by old_party_id
595    let mut big_p_j_poly_list_sorted_by_old_id = Pairs::new();
596    for &old_party_index in setup.old_party_indices() {
597        let old_party_id = old_party_ids.find_pair(old_party_index);
598        big_p_j_poly_list_sorted_by_old_id.push(
599            *old_party_id,
600            big_p_j_poly_list.find_pair(old_party_index).clone(),
601        );
602    }
603
604    let big_p_j_poly_list = big_p_j_poly_list_sorted_by_old_id.remove_ids();
605    let p_i_list = p_i_list.remove_ids();
606    for v in &big_p_j_poly_list {
607        big_p_poly.add_mut(v); // big_f_vec += v;
608    }
609
610    if big_p_j_poly_list.len() != p_i_list.len() {
611        return Err(KeygenError::FailedFelmanVerify);
612    }
613
614    let my_party_id = my_new_party_id.unwrap();
615    let my_rank = setup.new_participant_rank(my_party_id);
616
617    // check that P_j(x_i) = p_j_i * G
618    for (big_p_j, p_j_i) in big_p_j_poly_list.iter().zip(&p_i_list) {
619        let coeffs =
620            block_in_place(|| big_p_j.derivative_coeffs(my_rank as usize));
621        let valid = feldman_verify(
622            coeffs,
623            &new_x_i_list[my_party_id as usize],
624            p_j_i,
625            &ProjectivePoint::GENERATOR,
626        );
627        if !valid {
628            return Err(KeygenError::FailedFelmanVerify);
629        }
630    }
631
632    let p_i = p_i_list.iter().sum();
633
634    // check if p_i is correct, P(x_i) = p_i * G
635    let big_p_i = ProjectivePoint::GENERATOR * p_i;
636    let x_i = new_x_i_list[my_party_id as usize];
637    let coeff_multipliers =
638        polynomial_coeff_multipliers(&x_i, my_rank as usize, NEW_T);
639
640    let expected_point: ProjectivePoint = big_p_poly
641        .points()
642        .zip(coeff_multipliers)
643        .map(|(point, coeff)| point * &coeff)
644        .sum();
645
646    if expected_point != big_p_i {
647        return Err(KeygenError::BigSMismatch);
648    }
649
650    let public_key = big_p_poly.get_constant();
651
652    if &public_key != expected_public_key {
653        return Err(KeygenError::PublicKeyMismatch);
654    }
655
656    let big_s_list: Vec<ProjectivePoint> = new_x_i_list
657        .iter()
658        .enumerate()
659        .map(|(party_id, x_i)| {
660            let party_rank = setup.new_participant_rank(party_id as u8);
661
662            let coeff_multipliers =
663                polynomial_coeff_multipliers(x_i, party_rank as usize, NEW_T);
664
665            big_p_poly
666                .points()
667                .zip(coeff_multipliers)
668                .map(|(point, coeff)| point * &coeff)
669                .sum()
670        })
671        .collect();
672
673    let mut rank_list = vec![];
674    for &party_index in setup.new_party_indices() {
675        let party_id = setup.new_party_id(party_index).unwrap();
676        rank_list.push(setup.new_participant_rank(party_id));
677    }
678
679    if !rank_list.iter().all(|&r| r == 0) {
680        // check that rank_list is correct and participants can sign
681        check_secret_recovery(
682            &new_x_i_list,
683            &rank_list,
684            &big_s_list,
685            &public_key,
686        )?;
687    }
688
689    let mut new_keyshare = Keyshare::new(
690        NEW_N as u8,
691        NEW_T as u8,
692        my_party_id,
693        setup.keyshare_extra(),
694    );
695
696    new_keyshare.info_mut().final_session_id = final_session_id;
697    new_keyshare.info_mut().root_chain_code = root_chain_code;
698    new_keyshare.info_mut().public_key = encode_point(&public_key);
699    new_keyshare.info_mut().s_i = encode_scalar(&p_i);
700    new_keyshare.info_mut().key_id =
701        setup.derive_key_id(&public_key.to_bytes());
702
703    for p in 0..NEW_N {
704        let each = new_keyshare.each_mut(p as u8);
705
706        each.x_i = encode_scalar(&new_x_i_list[p]);
707        each.big_s = encode_point(&big_s_list[p]);
708        each.rank = rank_list[p];
709    }
710
711    /////////////////////////////////
712    // new parties create OT seeds //
713    /////////////////////////////////
714    let _ot1 = relay
715        .ask_messages_from_slice(
716            &setup,
717            QC_MSG_OT1,
718            setup.new_party_indices(),
719            true,
720        )
721        .await?;
722
723    let _ot2 = relay
724        .ask_messages_from_slice(
725            &setup,
726            QC_MSG_OT2,
727            setup.new_party_indices(),
728            true,
729        )
730        .await?;
731
732    let mut base_ot_receivers: Pairs<EndemicOTReceiver> = Pairs::new();
733    for &receiver_index in setup.new_party_indices() {
734        if receiver_index == my_party_index {
735            continue;
736        }
737
738        let receiver_id = setup.new_party_id(receiver_index).unwrap();
739
740        let sid = get_base_ot_session_id(
741            my_party_id,
742            receiver_id,
743            &new_keyshare.final_session_id,
744        );
745
746        let mut enc_ot_msg1 = EncryptedMessage::<EndemicOTMsg1>::new(
747            &setup.msg_id(Some(receiver_index), QC_MSG_OT1),
748            setup.message_ttl().as_secs() as u32,
749            0,
750            0,
751            &scheme,
752        );
753        let (msg1, _) = enc_ot_msg1.payload(&scheme);
754
755        let receiver = EndemicOTReceiver::new(&sid, msg1, &mut rng);
756        base_ot_receivers.push(receiver_id, receiver);
757
758        // send out P2P message. We call feed() in the loop
759        // and following send_broadcast() will call .send() that
760        // implies feed() + flush()
761        relay
762            .feed(
763                enc_ot_msg1
764                    .encrypt(&mut scheme, receiver_index)
765                    .ok_or(KeygenError::SendMessage)?,
766            )
767            .await
768            .map_err(|_| KeygenError::SendMessage)?;
769    }
770
771    Round::new(_ot1, QC_MSG_OT1, relay)
772        .of_encrypted_messages(
773            &setup,
774            &mut scheme,
775            0,
776            KeygenError::AbortProtocol,
777            |base_ot_msg1: &EndemicOTMsg1, receiver_index, _, scheme| {
778                let receiver_id = setup.new_party_id(receiver_index).unwrap();
779
780                let mut enc_buf = EncryptedMessage::<QCOTMsg2>::new(
781                    &setup.msg_id(Some(receiver_index), QC_MSG_OT2),
782                    setup.message_ttl().as_secs() as _,
783                    0,
784                    0,
785                    scheme,
786                );
787
788                let (msg3, _trailer) = enc_buf.payload(scheme);
789
790                let sender_ot_seed = {
791                    let sid = get_base_ot_session_id(
792                        receiver_id,
793                        my_party_id,
794                        &new_keyshare.final_session_id,
795                    );
796
797                    block_in_place(|| {
798                        EndemicOTSender::process(
799                            &sid,
800                            base_ot_msg1,
801                            &mut msg3.base_ot_msg2,
802                            &mut rng,
803                        )
804                    })
805                    .map_err(|_| KeygenError::InvalidMessage)?
806                };
807
808                let all_but_one_session_id = get_all_but_one_session_id(
809                    my_party_id as usize,
810                    receiver_id as usize,
811                    &new_keyshare.final_session_id,
812                );
813
814                build_pprf(
815                    &all_but_one_session_id,
816                    &sender_ot_seed,
817                    &mut new_keyshare.other_mut(receiver_id).send_ot_seed,
818                    &mut msg3.pprf_output,
819                );
820
821                if receiver_id > my_party_id {
822                    rng.fill_bytes(&mut msg3.seed_i_j);
823                    new_keyshare.each_mut(receiver_id - 1).zeta_seed =
824                        msg3.seed_i_j;
825                };
826
827                Ok(Some(
828                    enc_buf
829                        .encrypt(scheme, receiver_index)
830                        .ok_or(KeygenError::SendMessage)?,
831                ))
832            },
833        )
834        .await?;
835
836    Round::new(_ot2, QC_MSG_OT2, relay)
837        .of_encrypted_messages(
838            &setup,
839            &mut scheme,
840            0,
841            KeygenError::AbortProtocol,
842            |msg3: &QCOTMsg2, party_index, _, _| {
843                let party_id = setup.new_party_id(party_index).unwrap();
844
845                let receiver = base_ot_receivers.pop_pair(party_id);
846                let receiver_output =
847                    block_in_place(|| receiver.process(&msg3.base_ot_msg2))
848                        .map_err(|_| KeygenError::InvalidMessage)?;
849                let all_but_one_session_id = get_all_but_one_session_id(
850                    party_id as usize,
851                    my_party_id as usize,
852                    &new_keyshare.final_session_id,
853                );
854
855                block_in_place(|| {
856                    eval_pprf(
857                        &all_but_one_session_id,
858                        &receiver_output,
859                        &msg3.pprf_output,
860                        &mut new_keyshare.other_mut(party_id).recv_ot_seed,
861                    )
862                })
863                .map_err(KeygenError::PPRFError)?;
864
865                if party_id < my_party_id {
866                    new_keyshare.each_mut(party_id).zeta_seed = msg3.seed_i_j;
867                }
868
869                Ok(None)
870            },
871        )
872        .await?;
873
874    Ok(Some(new_keyshare))
875}
876
877/// Computes the hash commitment for the first round of the protocol.
878///
879/// This function generates a commitment to the polynomial coefficients
880/// and random value used in the first round of the quorum change protocol.
881///
882/// # Arguments
883///
884/// * `session_id` - The session identifier
885/// * `party_index` - The index of the party generating the commitment
886/// * `big_f_i_vec` - The polynomial commitment vector
887/// * `r1_i` - The random value for the commitment
888///
889/// # Returns
890///
891/// A 32-byte hash commitment
892fn hash_commitment_1(
893    session_id: &[u8],
894    party_index: usize,
895    big_f_i_vec: &GroupPolynomial<ProjectivePoint>,
896    r1_i: &[u8; 32],
897) -> [u8; 32] {
898    let mut hasher = Sha256::new();
899    hasher.update(QC_LABEL);
900    hasher.update(session_id);
901    hasher.update((party_index as u64).to_be_bytes());
902    for point in big_f_i_vec.points() {
903        hasher.update(point.to_bytes());
904    }
905    hasher.update(r1_i);
906    hasher.update(QC_COMMITMENT_1_LABEL);
907
908    hasher.finalize().into()
909}
910
911/// Computes the hash commitment for the second round of the protocol.
912///
913/// This function generates a commitment to the share values and random
914/// value used in the second round of the quorum change protocol.
915///
916/// # Arguments
917///
918/// * `session_id` - The session identifier
919/// * `from_party_i_index` - The index of the sending party
920/// * `to_party_j_index` - The index of the receiving party
921/// * `p_i_j` - The share value being committed
922/// * `r2_i` - The random value for the commitment
923///
924/// # Returns
925///
926/// A 32-byte hash commitment
927fn hash_commitment_2(
928    session_id: &[u8],
929    from_party_i_index: usize,
930    to_party_j_index: usize,
931    p_i_j: &Scalar,
932    r2_i: &[u8; 32],
933) -> [u8; 32] {
934    let mut hasher = Sha256::new();
935    hasher.update(QC_LABEL);
936    hasher.update(session_id);
937    hasher.update((from_party_i_index as u64).to_be_bytes());
938    hasher.update((to_party_j_index as u64).to_be_bytes());
939    hasher.update(p_i_j.to_bytes());
940    hasher.update(r2_i);
941    hasher.update(QC_COMMITMENT_2_LABEL);
942
943    hasher.finalize().into()
944}
945
946/// Processes message receivers for the quorum change protocol.
947///
948/// This function handles the distribution of messages to the appropriate
949/// receivers based on the protocol setup and message type.
950///
951/// # Type Parameters
952///
953/// * `S` - A type implementing the `QuorumChangeSetupMessage` trait
954/// * `F` - A closure type for processing message receivers
955///
956/// # Arguments
957///
958/// * `setup` - The protocol setup configuration
959/// * `msg_receiver` - A closure that processes each message receiver
960pub fn message_receivers<S, F>(setup: &S, mut msg_receiver: F)
961where
962    S: QuorumChangeSetupMessage<Keyshare, ProjectivePoint>,
963    F: FnMut(MsgId, &S::MessageVerifier),
964{
965    let my_party_index = setup.participant_index();
966    let my_party_is_old = setup.old_keyshare().is_some();
967    let my_new_party_id = setup.new_party_id(my_party_index);
968    let my_party_is_new = my_new_party_id.is_some();
969
970    let _old = setup.old_party_indices();
971    let new = setup.new_party_indices();
972
973    setup.all_other_parties().for_each(|receiver_idx| {
974        let receiver = setup.verifier(receiver_idx);
975
976        msg_receiver(setup.msg_id(None, ABORT_MESSAGE_TAG), receiver);
977
978        if my_party_is_old {
979            msg_receiver(setup.msg_id(None, QC_MSG_R0), receiver);
980        }
981
982        msg_receiver(setup.msg_id(None, QC_MSG_R1), receiver);
983
984        if my_party_is_old && new.contains(&receiver_idx) {
985            msg_receiver(
986                setup.msg_id(Some(receiver_idx), QC_MSG_P2P_1),
987                receiver,
988            );
989        }
990
991        if my_party_is_old && new.contains(&receiver_idx) {
992            msg_receiver(
993                setup.msg_id(Some(receiver_idx), QC_MSG_P2P_2),
994                receiver,
995            );
996        }
997
998        if my_party_is_old {
999            msg_receiver(setup.msg_id(None, QC_MSG_R2), receiver);
1000        }
1001
1002        if my_party_is_new && new.contains(&receiver_idx) {
1003            msg_receiver(
1004                setup.msg_id(Some(receiver_idx), QC_MSG_OT1),
1005                receiver,
1006            );
1007        }
1008
1009        if my_party_is_new && new.contains(&receiver_idx) {
1010            msg_receiver(
1011                setup.msg_id(Some(receiver_idx), QC_MSG_OT2),
1012                receiver,
1013            );
1014        }
1015    })
1016}
1017
1018#[cfg(test)]
1019mod tests {
1020    use super::*;
1021    use std::sync::Arc;
1022
1023    use tokio::task::JoinSet;
1024
1025    use sl_mpc_mate::{
1026        coord::{
1027            adversary::{EvilMessageRelay, EvilPlay},
1028            MessageRelayService, SimpleMessageRelay,
1029        },
1030        message::MsgId,
1031    };
1032
1033    use crate::{
1034        keygen::utils::{
1035            gen_keyshares, setup_quorum_change,
1036            setup_quorum_change_extend_parties,
1037            setup_quorum_change_threshold,
1038        },
1039        setup::quorum_change::SetupMessage as QuorumChangeSetupMessage,
1040        sign::{run as run_dsg, setup_dsg},
1041    };
1042
1043    async fn sim<S, R>(
1044        old_keyshares: &[Arc<Keyshare>],
1045        new_threshold: u8,
1046        new_ranks: Vec<u8>,
1047        coord: S,
1048    ) -> Vec<Option<Arc<Keyshare>>>
1049    where
1050        S: MessageRelayService<MessageRelay = R>,
1051        R: Relay + Send + 'static,
1052    {
1053        let parties =
1054            setup_quorum_change(old_keyshares, new_threshold, &new_ranks);
1055
1056        sim_parties(parties, coord).await
1057    }
1058
1059    async fn sim_extend<S, R>(
1060        old_keyshares: &[Arc<Keyshare>],
1061        new_threshold: u8,
1062        new_parties_len: u8,
1063        new_ranks: Vec<u8>,
1064        coord: S,
1065    ) -> Vec<Option<Arc<Keyshare>>>
1066    where
1067        S: MessageRelayService<MessageRelay = R>,
1068        R: Relay + Send + 'static,
1069    {
1070        let parties = setup_quorum_change_extend_parties(
1071            old_keyshares,
1072            new_threshold,
1073            new_parties_len,
1074            &new_ranks,
1075        );
1076        sim_parties(parties, coord).await
1077    }
1078
1079    async fn sim_only_change_threshold<S, R>(
1080        old_keyshares: &[Arc<Keyshare>],
1081        new_threshold: u8,
1082        new_ranks: Vec<u8>,
1083        coord: S,
1084    ) -> Vec<Option<Arc<Keyshare>>>
1085    where
1086        S: MessageRelayService<MessageRelay = R>,
1087        R: Relay + Send + 'static,
1088    {
1089        let parties = setup_quorum_change_threshold(
1090            old_keyshares,
1091            new_threshold,
1092            &new_ranks,
1093        );
1094        sim_parties(parties, coord).await
1095    }
1096
1097    async fn sim_parties<S, R>(
1098        parties: Vec<(QuorumChangeSetupMessage, [u8; 32])>,
1099        coord: S,
1100    ) -> Vec<Option<Arc<Keyshare>>>
1101    where
1102        S: MessageRelayService<MessageRelay = R>,
1103        R: Send + Relay + 'static,
1104    {
1105        let mut jset = JoinSet::new();
1106        for (setup, seed) in parties {
1107            let relay = coord.connect().await.unwrap();
1108
1109            jset.spawn(run(setup, seed, relay));
1110        }
1111
1112        let mut shares = vec![];
1113
1114        while let Some(fini) = jset.join_next().await {
1115            let fini = fini.unwrap();
1116
1117            if let Err(ref err) = fini {
1118                println!("error {}", err);
1119            }
1120
1121            let share = fini.unwrap();
1122            match share {
1123                None => shares.push(None),
1124                Some(v) => shares.push(Some(Arc::new(v))),
1125            }
1126        }
1127
1128        shares
1129    }
1130
1131    #[tokio::test(flavor = "multi_thread")]
1132    async fn quorum_change_all_new_parties() {
1133        let old_threshold = 2;
1134        let old_n = 3;
1135        let ranks = [0, 0, 0];
1136        let shares = gen_keyshares(old_threshold, old_n, Some(&ranks)).await;
1137        let expected_public_key = shares[0].public_key;
1138
1139        let shares =
1140            [shares[1].clone(), shares[0].clone(), shares[2].clone()];
1141
1142        let new_threshold = 3;
1143        let new_n = 4;
1144        let new_ranks = vec![0, 0, 1, 1];
1145        let result = sim(
1146            &shares[..old_threshold as usize],
1147            new_threshold,
1148            new_ranks,
1149            SimpleMessageRelay::new(),
1150        )
1151        .await;
1152
1153        let mut new_shares: Vec<Arc<Keyshare>> =
1154            result.iter().flatten().cloned().collect();
1155        assert_eq!(new_shares.len(), new_n as usize);
1156        assert_eq!(expected_public_key, new_shares[0].public_key);
1157
1158        // test dsg with new_shares after quorum change
1159        let coord = SimpleMessageRelay::new();
1160
1161        new_shares.sort_by_key(|share| share.party_id);
1162        let subset = &new_shares[0..new_threshold as usize];
1163
1164        let mut parties: JoinSet<Result<_, _>> = JoinSet::new();
1165        for (setup, seed) in setup_dsg(None, subset, "m") {
1166            parties.spawn(run_dsg(setup, seed, coord.connect()));
1167        }
1168
1169        while let Some(fini) = parties.join_next().await {
1170            let fini = fini.unwrap();
1171
1172            if let Err(ref err) = fini {
1173                println!("error {err:?}");
1174            }
1175            let _fini = fini.unwrap();
1176        }
1177    }
1178
1179    #[tokio::test(flavor = "multi_thread")]
1180    async fn quorum_change_extend_parties() {
1181        let old_threshold = 2;
1182        let old_n = 3;
1183        let ranks = [0, 0, 0];
1184        let shares = gen_keyshares(old_threshold, old_n, Some(&ranks)).await;
1185        let expected_public_key = shares[0].public_key;
1186
1187        let shares =
1188            [shares[1].clone(), shares[0].clone(), shares[2].clone()];
1189
1190        let new_threshold = 2;
1191        let new_parties_len = 2;
1192        let new_n = old_n + new_parties_len;
1193        let new_ranks = vec![0, 0, 0, 1, 1];
1194        let result = sim_extend(
1195            &shares,
1196            new_threshold,
1197            new_parties_len,
1198            new_ranks,
1199            SimpleMessageRelay::new(),
1200        )
1201        .await;
1202
1203        let mut new_shares: Vec<Arc<Keyshare>> =
1204            result.iter().flatten().cloned().collect();
1205        assert_eq!(new_shares.len(), new_n as usize);
1206        assert_eq!(expected_public_key, new_shares[0].public_key);
1207
1208        // test dsg with new_shares after quorum change
1209        let coord = SimpleMessageRelay::new();
1210
1211        new_shares.sort_by_key(|share| share.party_id);
1212        let subset = &new_shares[0..new_threshold as usize];
1213
1214        let mut parties: JoinSet<Result<_, _>> = JoinSet::new();
1215        for (setup, seed) in setup_dsg(None, subset, "m") {
1216            parties.spawn(run_dsg(setup, seed, coord.connect()));
1217        }
1218
1219        while let Some(fini) = parties.join_next().await {
1220            let fini = fini.unwrap();
1221
1222            if let Err(ref err) = fini {
1223                println!("error {err:?}");
1224            }
1225            let _fini = fini.unwrap();
1226        }
1227    }
1228
1229    #[tokio::test(flavor = "multi_thread")]
1230    async fn quorum_change_only_change_threshold() {
1231        let old_threshold = 2;
1232        let old_n = 4;
1233        let ranks = [0, 0, 0, 0];
1234        let mut shares =
1235            gen_keyshares(old_threshold, old_n, Some(&ranks)).await;
1236        let expected_public_key = shares[0].public_key;
1237
1238        shares.shuffle(&mut thread_rng());
1239
1240        let new_threshold = 3;
1241        let new_n = old_n;
1242        let new_ranks = vec![0, 0, 0, 0];
1243        let result = sim_only_change_threshold(
1244            &shares,
1245            new_threshold,
1246            new_ranks,
1247            SimpleMessageRelay::new(),
1248        )
1249        .await;
1250
1251        let mut new_shares: Vec<Arc<Keyshare>> =
1252            result.iter().flatten().cloned().collect();
1253        assert_eq!(new_shares.len(), new_n as usize);
1254        assert_eq!(expected_public_key, new_shares[0].public_key);
1255
1256        // test dsg with new_shares after quorum change
1257        let coord = SimpleMessageRelay::new();
1258
1259        new_shares.sort_by_key(|share| share.party_id);
1260        let subset = &new_shares[0..new_threshold as usize];
1261
1262        let mut parties: JoinSet<Result<_, _>> = JoinSet::new();
1263        for (setup, seed) in setup_dsg(None, subset, "m") {
1264            parties.spawn(run_dsg(setup, seed, coord.connect()));
1265        }
1266
1267        while let Some(fini) = parties.join_next().await {
1268            let fini = fini.unwrap();
1269
1270            if let Err(ref err) = fini {
1271                println!("error {err:?}");
1272            }
1273            let _fini = fini.unwrap();
1274        }
1275    }
1276
1277    #[tokio::test(flavor = "multi_thread")]
1278    async fn n1() {
1279        let old_threshold = 2;
1280        let old_n = 3;
1281        let ranks = [0, 0, 0];
1282        let shares = gen_keyshares(old_threshold, old_n, Some(&ranks)).await;
1283
1284        let play = EvilPlay::new().drop_message(MsgId::ZERO_ID, None);
1285
1286        let new_threshold = 2;
1287        let new_ranks = vec![0, 0, 1, 1];
1288        sim(
1289            &shares[..old_threshold as usize],
1290            new_threshold,
1291            new_ranks,
1292            EvilMessageRelay::new(play),
1293        )
1294        .await;
1295    }
1296}