Skip to main content

ferveo_nucypher_temp6/
refresh.rs

1use std::{collections::HashMap, ops::Mul};
2
3use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup, Group};
4use ark_ff::{Field, Zero};
5use ark_poly::{
6    univariate::DensePolynomial, DenseUVPolynomial, EvaluationDomain,
7    Polynomial,
8};
9use ark_std::{One, UniformRand};
10use ferveo_common::{serialization, Keypair, PublicKey};
11use ferveo_tdec::{
12    prepare_combine_simple, BlindedKeyShare, CiphertextHeader,
13    DecryptionSharePrecomputed, DecryptionShareSimple, DomainPoint,
14    ShareCommitment,
15};
16use rand_core::RngCore;
17use serde::{de::DeserializeOwned, Deserialize, Serialize};
18use serde_with::serde_as;
19use subproductdomain::fast_multiexp;
20use zeroize::ZeroizeOnDrop;
21
22use crate::{batch_to_projective_g1, Error, Result};
23
24type InnerBlindedKeyShare<E> = ferveo_tdec::BlindedKeyShare<E>;
25
26/// Blinded key share held by a participant in the DKG protocol
27// TODO: What about the commented macros?
28#[derive(
29    Debug,
30    Clone, //PartialEq, Eq, ZeroizeOnDrop, Serialize, Deserialize,
31)]
32pub struct UpdatableBlindedKeyShare<E: Pairing>(
33    // #[serde(bound(
34    //     serialize = "ferveo_tdec::PrivateKeyShare<E>: Serialize",
35    //     deserialize = "ferveo_tdec::PrivateKeyShare<E>: DeserializeOwned"
36    // ))]
37    pub InnerBlindedKeyShare<E>,
38);
39
40impl<E: Pairing> UpdatableBlindedKeyShare<E> {
41    pub fn new(blinded_key_share: InnerBlindedKeyShare<E>) -> Self {
42        Self(blinded_key_share)
43    }
44
45    /// From PSS paper, section 4.2.3, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf)
46    pub fn apply_share_updates(
47        &self,
48        update_transcripts: &HashMap<u32, UpdateTranscript<E>>,
49        index: u32,
50    ) -> Self {
51        // Current participant receives update transcripts from other participants
52        let share_updates_for_index: Vec<_> = update_transcripts
53            .values()
54            .map(|update_transcript_from_producer| {
55                let update_for_participant: ShareUpdate<E> =
56                    update_transcript_from_producer
57                        .updates
58                        .get(&index)
59                        .cloned()
60                        .unwrap();
61                // Validate share update against the target validator public key
62                update_for_participant
63                    .verify(&PublicKey {
64                        encryption_key: self.0.validator_public_key,
65                    })
66                    .unwrap();
67                update_for_participant
68            })
69            .collect();
70
71        // TODO: Validate commitments from share update
72        // FIXME: Don't forget!!!!! - #200
73        let updated_key_share = share_updates_for_index
74            .iter()
75            .fold(self.0.blinded_key_share, |acc, delta| {
76                (acc + delta.update).into()
77            });
78        Self(BlindedKeyShare {
79            validator_public_key: self.0.validator_public_key,
80            blinded_key_share: updated_key_share,
81        })
82    }
83
84    pub fn create_decryption_share_simple(
85        &self,
86        ciphertext_header: &CiphertextHeader<E>,
87        aad: &[u8],
88        validator_keypair: &Keypair<E>,
89    ) -> Result<DecryptionShareSimple<E>> {
90        let decryption_share = self
91            .0
92            .create_decryption_share_simple(
93                ciphertext_header,
94                aad,
95                validator_keypair,
96            )
97            .unwrap();
98        Ok(decryption_share)
99    }
100
101    // TODO: Move to BlindedKeyShare
102    /// In precomputed variant, we offload some of the decryption related computation to the server-side:
103    /// We use the `prepare_combine_simple` function to precompute the lagrange coefficients
104    pub fn create_decryption_share_precomputed(
105        &self,
106        ciphertext_header: &CiphertextHeader<E>,
107        aad: &[u8],
108        validator_keypair: &Keypair<E>,
109        share_index: u32,
110        domain_points_map: &HashMap<u32, DomainPoint<E>>,
111    ) -> Result<DecryptionSharePrecomputed<E>> {
112        // We need to turn the domain points into a vector, and sort it by share index
113        let mut domain_points = domain_points_map
114            .iter()
115            .map(|(share_index, domain_point)| (*share_index, *domain_point))
116            .collect::<Vec<_>>();
117        domain_points.sort_by_key(|(share_index, _)| *share_index);
118
119        // Now, we have to pass the domain points to the `prepare_combine_simple` function
120        // and use the resulting lagrange coefficients to create the decryption share
121
122        let only_domain_points = domain_points
123            .iter()
124            .map(|(_, domain_point)| *domain_point)
125            .collect::<Vec<_>>();
126        let lagrange_coeffs = prepare_combine_simple::<E>(&only_domain_points);
127
128        // Before we pick the lagrange coefficient for the current share index, we need
129        // to map the share index to the index in the domain points vector
130        // Given that we sorted the domain points by share index, the first element in the vector
131        // will correspond to the smallest share index, second to the second smallest, and so on
132
133        let sorted_share_indices = domain_points
134            .iter()
135            .enumerate()
136            .map(|(adjusted_share_index, (share_index, _))| {
137                (*share_index, adjusted_share_index)
138            })
139            .collect::<HashMap<u32, usize>>();
140        let adjusted_share_index = *sorted_share_indices
141            .get(&share_index)
142            .ok_or(Error::InvalidShareIndex(share_index))?;
143
144        // Finally, pick the lagrange coefficient for the current share index
145        let lagrange_coeff = &lagrange_coeffs[adjusted_share_index];
146        let private_key_share = self.0.unblind(validator_keypair);
147        DecryptionSharePrecomputed::create(
148            share_index as usize,
149            &validator_keypair.decryption_key,
150            &private_key_share.unwrap(),
151            ciphertext_header,
152            aad,
153            lagrange_coeff,
154        )
155        .map_err(|e| e.into())
156    }
157}
158
159/// An update to a private key share generated by a participant in a share refresh operation.
160#[serde_as]
161#[derive(
162    Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ZeroizeOnDrop,
163)]
164pub struct ShareUpdate<E: Pairing> {
165    #[serde_as(as = "serialization::SerdeAs")]
166    pub update: E::G2Affine,
167
168    #[serde_as(as = "serialization::SerdeAs")]
169    pub commitment: E::G1Affine,
170}
171
172impl<E: Pairing> ShareUpdate<E> {
173    // TODO: Use multipairings? - #192
174    // TODO: Unit tests
175    pub fn verify(
176        &self,
177        target_validator_public_key: &PublicKey<E>,
178    ) -> Result<bool> {
179        let public_key_point: E::G2Affine =
180            target_validator_public_key.encryption_key;
181        let is_valid = E::pairing(E::G1::generator(), self.update)
182            == E::pairing(self.commitment, public_key_point);
183        if is_valid {
184            Ok(true)
185        } else {
186            Err(Error::InvalidShareUpdate)
187        }
188    }
189}
190
191#[derive(Clone, Debug, PartialEq, Eq)]
192pub struct UpdateTranscript<E: Pairing> {
193    /// Used in Feldman commitment to the update polynomial
194    pub coeffs: Vec<E::G1Affine>,
195
196    /// The share updates to be dealt to each validator
197    pub updates: HashMap<u32, ShareUpdate<E>>,
198}
199
200impl<E: Pairing> UpdateTranscript<E> {
201    /// From PSS paper, section 4.2.1, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf)
202    pub fn create_refresh_updates(
203        domain_points_and_keys: &HashMap<u32, (DomainPoint<E>, PublicKey<E>)>,
204        threshold: u32,
205        rng: &mut impl RngCore,
206    ) -> UpdateTranscript<E> {
207        // Update polynomial has root at 0
208        prepare_share_updates_with_root::<E>(
209            domain_points_and_keys,
210            &DomainPoint::<E>::zero(),
211            threshold,
212            rng,
213        )
214        // TODO: Cast return elements into ShareRefreshUpdate - #193
215    }
216
217    pub fn create_recovery_updates(
218        domain_points_and_keys: &HashMap<u32, (DomainPoint<E>, PublicKey<E>)>,
219        x_r: &DomainPoint<E>,
220        threshold: u32,
221        rng: &mut impl RngCore,
222    ) -> UpdateTranscript<E> {
223        // Update polynomial has root at x_r
224        prepare_share_updates_with_root::<E>(
225            domain_points_and_keys,
226            x_r,
227            threshold,
228            rng,
229        )
230        // TODO: Cast return elements into ShareRecoveryUpdate - #193
231    }
232
233    // TODO: Unit tests, but only after #193
234    pub fn verify_recovery(
235        &self,
236        validator_public_keys: &HashMap<u32, PublicKey<E>>,
237        domain: &ark_poly::GeneralEvaluationDomain<E::ScalarField>,
238        root: E::ScalarField,
239    ) -> Result<bool> {
240        // TODO: Make sure input validators and transcript validators match
241
242        // TODO: Validate that update polynomial commitments have proper length
243
244        // Validate consistency between share updates, validator keys and polynomial commitments.
245        // Let's first reconstruct the expected update commitments from the polynomial commitments:
246        let mut reconstructed_commitments =
247            batch_to_projective_g1::<E>(&self.coeffs);
248        domain.fft_in_place(&mut reconstructed_commitments);
249
250        for (index, update) in self.updates.iter() {
251            // Next, validate share updates against their corresponding target validators
252            update
253                .verify(validator_public_keys.get(index).unwrap())
254                .unwrap();
255
256            // Finally, validate update commitments against update polynomial commitments
257            let expected_commitment = reconstructed_commitments
258                .get(*index as usize)
259                .ok_or(Error::InvalidShareIndex(*index))?;
260            assert_eq!(expected_commitment.into_affine(), update.commitment);
261            // TODO: Error handling of everything in this block
262        }
263
264        // Validate update polynomial commitments C_i are consistent with the type of update
265        // * For refresh  (root 0): f(0) = 0  ==>  a_0 = 0  ==>  C_0 = [0]G = 1
266        // * For recovery (root z): f(z) = 0  ==>  sum{a_i * z^i} = 0  ==>  [sum{...}]G = 1  ==> sum{[z^i]C_i} = 1
267
268        if root.is_zero() {
269            // Refresh
270            assert!(self.coeffs[0].is_zero());
271            // TODO: Check remaining are not zero? Only if we disallow producing zero coeffs
272        } else {
273            // Recovery
274            // TODO: There's probably a much better way to do this
275            let mut reverse_coeffs = self.coeffs.iter().rev();
276            let mut acc: E::G1Affine = *reverse_coeffs.next().unwrap();
277            for &coeff in reverse_coeffs {
278                let b = acc.mul(root).into_affine();
279                acc = (coeff + b).into();
280            }
281            assert!(acc.is_zero());
282        }
283
284        // TODO: Handle errors properly
285        Ok(true)
286    }
287
288    // TODO: Unit tests
289    pub fn verify_refresh(
290        &self,
291        validator_public_keys: &HashMap<u32, PublicKey<E>>,
292        domain: &ark_poly::GeneralEvaluationDomain<E::ScalarField>,
293    ) -> Result<bool> {
294        self.verify_recovery(
295            validator_public_keys,
296            domain,
297            E::ScalarField::zero(),
298        )
299    }
300}
301
302// HandoverTranscript, a.k.a. "The Baton", represents the message an incoming
303// node produces to initiate a handover with an outgoing node.
304// After the handover, the incoming node replaces the outgoing node in an
305// existing cohort, securely obtaining a new blinded key share, but under the
306// incoming node's private key.
307#[serde_as]
308#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
309pub struct HandoverTranscript<E: Pairing> {
310    pub share_index: u32,
311    #[serde_as(as = "serialization::SerdeAs")]
312    pub double_blind_share: E::G2,
313
314    #[serde_as(as = "serialization::SerdeAs")]
315    pub commitment_to_share: E::G2,
316
317    #[serde_as(as = "serialization::SerdeAs")]
318    pub commitment_to_g1: E::G1,
319
320    #[serde_as(as = "serialization::SerdeAs")]
321    pub commitment_to_g2: E::G2,
322
323    #[serde(bound(
324        serialize = "ferveo_common::PublicKey<E>: Serialize",
325        deserialize = "ferveo_common::PublicKey<E>: DeserializeOwned"
326    ))]
327    pub incoming_pubkey: PublicKey<E>,
328
329    #[serde(bound(
330        serialize = "ferveo_common::PublicKey<E>: Serialize",
331        deserialize = "ferveo_common::PublicKey<E>: DeserializeOwned"
332    ))]
333    pub outgoing_pubkey: PublicKey<E>,
334}
335
336impl<E: Pairing> HandoverTranscript<E> {
337    pub fn new(
338        share_index: u32,
339        outgoing_blinded_share: &BlindedKeyShare<E>,
340        outgoing_pubkey: PublicKey<E>,
341        incoming_validator_keypair: &Keypair<E>,
342        rng: &mut impl RngCore,
343    ) -> Self {
344        // t
345        let random_scalar = E::ScalarField::rand(rng);
346        // d_j
347        let incoming_decryption_key = incoming_validator_keypair.decryption_key;
348        // d_j * Y_i
349        let double_blind_share = outgoing_blinded_share
350            .blinded_key_share
351            .mul(incoming_decryption_key);
352        // t * d_j * ek_i
353        let commitment_to_share = outgoing_pubkey
354            .encryption_key
355            .mul(incoming_decryption_key.mul(random_scalar));
356
357        Self {
358            share_index,
359            double_blind_share,
360            commitment_to_share,
361            commitment_to_g1: E::G1::generator().mul(random_scalar),
362            commitment_to_g2: E::G2::generator().mul(random_scalar),
363            incoming_pubkey: incoming_validator_keypair.public_key(),
364            outgoing_pubkey,
365        }
366    }
367
368    // See similarity with transcript check #4 (do_verify_full in pvss)
369    pub fn validate(
370        &self,
371        share_commitment: ShareCommitment<E>,
372    ) -> Result<bool> {
373        // e(comm_G1, double_blind_share) == e(A_i, comm_share)
374        //   or equivalently:
375        // e(-comm_G1, double_blind_share) · e(A_i, comm_share) == 1
376        let commitment_to_g1_inv = -self.commitment_to_g1;
377        let mut is_valid = E::multi_pairing(
378            [commitment_to_g1_inv, share_commitment.0.into()],
379            [self.double_blind_share, self.commitment_to_share],
380        )
381        .0 == E::TargetField::one();
382
383        // e(comm_G1, gen_G2) == e(gen_G1, comm_G2)
384        //   or equivalently:
385        // e(-comm_G1, gen_G2) · e(gen_G1, comm_G2) == 1
386        is_valid = is_valid
387            && E::multi_pairing(
388                [commitment_to_g1_inv, E::G1::generator()],
389                [E::G2::generator(), self.commitment_to_g2],
390            )
391            .0 == E::TargetField::one();
392
393        if is_valid {
394            Ok(true)
395        } else {
396            Err(Error::InvalidShareUpdate) // TODO: review error
397        }
398    }
399
400    pub fn finalize(
401        &self,
402        departing_validator_keypair: &Keypair<E>,
403        share_commitment: ShareCommitment<E>,
404    ) -> Result<BlindedKeyShare<E>> {
405        let is_valid = &self.validate(share_commitment).unwrap();
406        if !is_valid {
407            return Err(Error::InvalidShareUpdate); // TODO: Make this more specific
408        }
409        let new_blinded_share_element = &self.double_blind_share.mul(
410            departing_validator_keypair
411                .decryption_key
412                .inverse()
413                .unwrap(),
414        );
415        Ok(BlindedKeyShare::<E> {
416            validator_public_key: self.incoming_pubkey.encryption_key,
417            blinded_key_share: new_blinded_share_element.into_affine(),
418        })
419    }
420}
421
422/// Prepare share updates with a given root (0 for refresh, some x coord for recovery)
423/// This is a helper function for `ShareUpdate::create_share_updates_for_recovery` and `ShareUpdate::create_share_updates_for_refresh`
424/// It generates a new random polynomial with a defined root and evaluates it at each of the participants' indices.
425/// The result is a map of share updates.
426// TODO: Use newtype type for (DomainPoint<E>, PublicKey<E>) - #162
427fn prepare_share_updates_with_root<E: Pairing>(
428    domain_points_and_keys: &HashMap<u32, (DomainPoint<E>, PublicKey<E>)>,
429    root: &DomainPoint<E>,
430    threshold: u32,
431    rng: &mut impl RngCore,
432) -> UpdateTranscript<E> {
433    // Generate a new random update polynomial with defined root
434    let update_poly =
435        make_random_polynomial_with_root::<E>(threshold - 1, root, rng);
436
437    // Commit to the update polynomial
438    let g = E::G1::generator();
439    let coeff_commitments = fast_multiexp(&update_poly.coeffs, g);
440
441    // Now, we need to evaluate the polynomial at each of participants' indices
442    let share_updates: HashMap<u32, ShareUpdate<E>> = domain_points_and_keys
443        .iter()
444        .map(|(share_index, tuple)| {
445            let (x_i, pubkey_i) = tuple;
446            let eval = update_poly.evaluate(x_i);
447            // TODO: Reconsider coordinates representation #195
448            let update =
449                E::G2::from(pubkey_i.encryption_key).mul(eval).into_affine();
450            let commitment = g.mul(eval).into_affine();
451            let share_update = ShareUpdate { update, commitment };
452            (*share_index, share_update)
453        })
454        .collect::<HashMap<u32, ShareUpdate<E>>>();
455
456    UpdateTranscript {
457        coeffs: coeff_commitments,
458        updates: share_updates,
459    }
460}
461
462/// Generate a random polynomial with a given root
463fn make_random_polynomial_with_root<E: Pairing>(
464    degree: u32,
465    root: &DomainPoint<E>,
466    rng: &mut impl RngCore,
467) -> DensePolynomial<DomainPoint<E>> {
468    // [c_0, c_1, ..., c_{degree}] (Random polynomial)
469    let mut poly =
470        DensePolynomial::<DomainPoint<E>>::rand(degree as usize, rng);
471
472    // [0, c_1, ... , c_{degree}]  (We zeroize the free term)
473    poly[0] = DomainPoint::<E>::zero();
474
475    // Now, we calculate a new free term so that `poly(root) = 0`
476    let new_c_0 = DomainPoint::<E>::zero() - poly.evaluate(root);
477    poly[0] = new_c_0;
478
479    // Evaluating the polynomial at the root should result in 0
480    debug_assert!(poly.evaluate(root) == DomainPoint::<E>::zero());
481    debug_assert!(poly.coeffs.len() == (degree + 1) as usize);
482
483    poly
484}
485
486#[cfg(test)]
487mod tests_refresh {
488    use std::{collections::HashMap, ops::Mul};
489
490    use ark_ec::CurveGroup;
491    use ark_poly::EvaluationDomain;
492    use ark_std::{test_rng, UniformRand, Zero};
493    use ferveo_common::Keypair;
494    use ferveo_tdec::{
495        lagrange_basis_at, test_common::setup_simple, DomainPoint,
496    };
497    use itertools::{zip_eq, Itertools};
498    use test_case::test_case;
499
500    use crate::{
501        test_common::*, HandoverTranscript, UpdatableBlindedKeyShare,
502        UpdateTranscript,
503    };
504
505    type ScalarField =
506        <ark_bls12_381::Bls12_381 as ark_ec::pairing::Pairing>::ScalarField;
507    type G2 = <ark_bls12_381::Bls12_381 as ark_ec::pairing::Pairing>::G2;
508
509    // TODO: Part of recovery tests - #193
510    // /// Using tdec test utilities here instead of PVSS to test the internals of the shared key recovery
511    // fn create_updated_private_key_shares<R: RngCore>(
512    //     rng: &mut R,
513    //     threshold: u32,
514    //     x_r: &Fr,
515    //     remaining_participants: &[PrivateDecryptionContextSimple<E>],
516    // ) -> HashMap<u32, UpdatedPrivateKeyShare<E>> {
517    //     // Each participant prepares an update for each other participant
518    //     let domain_points_and_keys = remaining_participants
519    //         .iter()
520    //         .map(|c| {
521    //             let ctxt = &c.public_decryption_contexts[c.index];
522    //             (c.index as u32, (ctxt.domain, ctxt.validator_public_key))
523    //         })
524    //         .collect::<HashMap<_, _>>();
525    //     let share_updates = remaining_participants
526    //         .iter()
527    //         .map(|p| {
528    //             let share_updates = UpdateTranscript::create_recovery_updates(
529    //                 &domain_points_and_keys,
530    //                 x_r,
531    //                 threshold,
532    //                 rng,
533    //             );
534    //             (p.index as u32, share_updates.updates)
535    //         })
536    //         .collect::<HashMap<u32, _>>();
537
538    //     // Participants share updates and update their shares
539    //     let updated_private_key_shares = remaining_participants
540    //         .iter()
541    //         .map(|p| {
542    //             // Current participant receives updates from other participants
543    //             let updates_for_participant: Vec<_> = share_updates
544    //                 .values()
545    //                 .map(|updates| {
546    //                     updates.get(&(p.index as u32)).cloned().unwrap()
547    //                 })
548    //                 .collect();
549
550    //             // And updates their share
551    //             let updated_share =
552    //                 PrivateKeyShare(p.private_key_share.clone())
553    //                     .create_updated_key_share(&updates_for_participant);
554    //             (p.index as u32, updated_share)
555    //         })
556    //         .collect::<HashMap<u32, _>>();
557
558    //     updated_private_key_shares
559    // }
560
561    /// `x_r` is the point at which the share is to be recovered
562    fn combine_private_shares_at(
563        x_r: &DomainPoint<E>,
564        domain_points: &HashMap<u32, DomainPoint<E>>,
565        shares: &HashMap<u32, ferveo_tdec::PrivateKeyShare<E>>,
566    ) -> ferveo_tdec::PrivateKeyShare<E> {
567        let mut domain_points_ = vec![];
568        let mut updated_shares_ = vec![];
569        for share_index in shares.keys().sorted() {
570            domain_points_.push(*domain_points.get(share_index).unwrap());
571            updated_shares_.push(shares.get(share_index).unwrap().0);
572        }
573
574        // Interpolate new shares to recover y_r
575        let lagrange = lagrange_basis_at::<E>(&domain_points_, x_r);
576        let prods =
577            zip_eq(updated_shares_, lagrange).map(|(y_j, l)| y_j.mul(l));
578        let y_r = prods.fold(G2::zero(), |acc, y_j| acc + y_j);
579        ferveo_tdec::PrivateKeyShare(y_r.into_affine())
580    }
581
582    /// Ñ parties (where t <= Ñ <= N) jointly execute a "share recovery" algorithm, and the output is 1 new share.
583    /// The new share is intended to restore a previously existing share, e.g., due to loss or corruption.
584    // FIXME: This test is currently broken, and adjusted to allow compilation
585    #[ignore = "Re-introduce recovery tests - #193"]
586    #[test_case(4, 4; "number of shares (validators) is a power of 2")]
587    #[test_case(7, 7; "number of shares (validators) is not a power of 2")]
588    fn tdec_simple_variant_share_recovery_at_selected_point(
589        shares_num: u32,
590        _validators_num: u32,
591    ) {
592        let rng = &mut test_rng();
593        let security_threshold = shares_num * 2 / 3;
594
595        let (_, _, mut contexts) = setup_simple::<E>(
596            shares_num as usize,
597            security_threshold as usize,
598            rng,
599        );
600
601        // Prepare participants
602
603        // First, save the soon-to-be-removed participant
604        let selected_participant = contexts.pop().unwrap();
605        let _x_r = selected_participant
606            .public_decryption_contexts
607            .last()
608            .unwrap()
609            .domain;
610        let original_private_key_share = selected_participant.private_key_share;
611
612        // Remove the selected participant from the contexts and all nested structures
613        let mut remaining_participants = contexts;
614        for p in &mut remaining_participants {
615            p.public_decryption_contexts.pop().unwrap();
616        }
617
618        // Each participant prepares an update for each other participant, and uses it to create a new share fragment
619        // let updated_private_key_shares = create_updated_private_key_shares(
620        //     rng,
621        //     security_threshold,
622        //     &x_r,
623        //     &remaining_participants,
624        // );
625        // We only need `security_threshold` updates to recover the original share
626        // let updated_private_key_shares = updated_private_key_shares
627        //     .into_iter()
628        //     .take(security_threshold as usize)
629        //     .collect::<HashMap<_, _>>();
630
631        // Now, we have to combine new share fragments into a new share
632        let _domain_points = remaining_participants
633            .into_iter()
634            .map(|ctxt| {
635                (
636                    ctxt.index as u32,
637                    ctxt.public_decryption_contexts[ctxt.index].domain,
638                )
639            })
640            .collect::<HashMap<u32, _>>();
641        // let new_private_key_share =
642        //     PrivateKeyShare::recover_share_from_updated_private_shares(
643        //         &x_r,
644        //         &domain_points,
645        //         &updated_private_key_shares,
646        //     )
647        //     .unwrap();
648
649        // The new share should be the same as the original
650        // assert_eq!(new_private_key_share, original_private_key_share);
651
652        // But if we don't have enough private share updates, the resulting private share will be incorrect
653        // let not_enough_shares = updated_private_key_shares
654        //     .into_iter()
655        //     .take(security_threshold as usize - 1)
656        //     .collect::<HashMap<_, _>>();
657        // let incorrect_private_key_share =
658        //     PrivateKeyShare::recover_share_from_updated_private_shares(
659        //         &x_r,
660        //         &domain_points,
661        //         &not_enough_shares,
662        //     )
663        //     .unwrap();
664        assert_ne!(original_private_key_share, original_private_key_share);
665    }
666
667    /// Ñ parties (where t <= Ñ <= N) jointly execute a "share recovery" algorithm, and the output is 1 new share.
668    /// The new share is independent of the previously existing shares. We can use this to on-board a new participant into an existing cohort.
669    // FIXME: This test is currently broken, and adjusted to allow compilation
670    #[ignore = "Re-introduce recovery tests - #193"]
671    #[test_case(4; "number of shares (validators) is a power of 2")]
672    #[test_case(7; "number of shares (validators) is not a power of 2")]
673    fn tdec_simple_variant_share_recovery_at_random_point(shares_num: u32) {
674        let rng = &mut test_rng();
675        let security_threshold = shares_num * 2 / 3;
676
677        let (_, shared_private_key, mut contexts) = setup_simple::<E>(
678            shares_num as usize,
679            security_threshold as usize,
680            rng,
681        );
682
683        // Prepare participants
684
685        // Remove one participant from the contexts and all nested structures
686        let removed_participant = contexts.pop().unwrap();
687        let mut remaining_participants = contexts.clone();
688        for p in &mut remaining_participants {
689            p.public_decryption_contexts.pop().unwrap();
690        }
691
692        // Now, we're going to recover a new share at a random point and check that the shared secret is still the same
693
694        // Our random point
695        let x_r = ScalarField::rand(rng);
696
697        // Each remaining participant prepares an update for every other participant, and uses it to create a new share fragment
698        // let share_recovery_updates = create_updated_private_key_shares(
699        //     rng,
700        //     security_threshold,
701        //     &x_r,
702        //     &remaining_participants,
703        // );
704        // We only need `threshold` updates to recover the original share
705        // let share_recovery_updates = share_recovery_updates
706        //     .into_iter()
707        //     .take(security_threshold as usize)
708        //     .collect::<HashMap<_, _>>();
709        let domain_points = &mut remaining_participants
710            .into_iter()
711            .map(|ctxt| {
712                (
713                    ctxt.index as u32,
714                    ctxt.public_decryption_contexts[ctxt.index].domain,
715                )
716            })
717            .collect::<HashMap<_, _>>();
718
719        // Now, we have to combine new share fragments into a new share
720        // let recovered_private_key_share =
721        //     PrivateKeyShare::recover_share_from_updated_private_shares(
722        //         &x_r,
723        //         domain_points,
724        //         &share_recovery_updates,
725        //     )
726        //     .unwrap();
727
728        // Finally, let's recreate the shared private key from some original shares and the recovered one
729        let _private_shares = contexts
730            .into_iter()
731            .map(|ctxt| (ctxt.index as u32, ctxt.private_key_share))
732            .collect::<HashMap<u32, _>>();
733
734        // Need to update these to account for recovered private key share
735        domain_points.insert(removed_participant.index as u32, x_r);
736        // private_shares.insert(
737        //     removed_participant.index as u32,
738        //     recovered_private_key_share.0.clone(),
739        // );
740
741        // This is a workaround for a type mismatch - We need to convert the private shares to updated private shares
742        // This is just to test that we are able to recover the shared private key from the updated private shares
743        // let updated_private_key_shares = private_shares
744        //     .into_iter()
745        //     .map(|(share_index, share)| {
746        //         (share_index, UpdatedPrivateKeyShare(share))
747        //     })
748        // .collect::<HashMap<u32, _>>();
749        // let new_shared_private_key =
750        //     PrivateKeyShare::recover_share_from_updated_private_shares(
751        //         &ScalarField::zero(),
752        //         domain_points,
753        //         &updated_private_key_shares,
754        //     )
755        //     .unwrap();
756        assert_ne!(shared_private_key, shared_private_key);
757    }
758
759    /// Ñ parties (where t <= Ñ <= N) jointly execute a "share refresh" algorithm.
760    /// The output is M new shares (with M <= Ñ), with each of the M new shares substituting the
761    /// original share (i.e., the original share is deleted).
762    #[test_case(4, 3; "N is a power of 2, t is 1 + 50%")]
763    #[test_case(4, 4; "N is a power of 2, t=N")]
764    #[test_case(30, 16; "N is not a power of 2, t is 1 + 50%")]
765    #[test_case(30, 30; "N is not a power of 2, t=N")]
766    fn tdec_simple_variant_share_refreshing(
767        shares_num: usize,
768        security_threshold: usize,
769    ) {
770        let rng = &mut test_rng();
771        let (_, shared_private_key, contexts) =
772            setup_simple::<E>(shares_num, security_threshold, rng);
773
774        let fft_domain =
775            ark_poly::GeneralEvaluationDomain::<ScalarField>::new(shares_num)
776                .unwrap();
777
778        let domain_points_and_keys = &contexts
779            .iter()
780            .map(|ctxt| {
781                (
782                    ctxt.index as u32,
783                    (
784                        ctxt.public_decryption_contexts[ctxt.index].domain,
785                        ctxt.public_decryption_contexts[ctxt.index]
786                            .validator_public_key,
787                    ),
788                )
789            })
790            .collect::<HashMap<u32, _>>();
791        let validator_keys_map = &contexts
792            .iter()
793            .map(|ctxt| {
794                (
795                    ctxt.index as u32,
796                    ctxt.public_decryption_contexts[ctxt.index]
797                        .validator_public_key,
798                )
799            })
800            .collect::<HashMap<u32, _>>();
801
802        // Each participant prepares an update transcript for each other participant:
803        let update_transcripts_by_producer = contexts
804            .iter()
805            .map(|p| {
806                let updates_transcript =
807                    UpdateTranscript::<E>::create_refresh_updates(
808                        domain_points_and_keys,
809                        security_threshold as u32,
810                        rng,
811                    );
812                (p.index as u32, updates_transcript)
813            })
814            .collect::<HashMap<u32, UpdateTranscript<E>>>();
815
816        // Participants validate first all the update transcripts.
817        // TODO: Find a better way to ensure they're always validated
818        for update_transcript in update_transcripts_by_producer.values() {
819            update_transcript
820                .verify_refresh(validator_keys_map, &fft_domain)
821                .unwrap();
822        }
823
824        // Participants refresh their shares with the updates from each other:
825        let refreshed_shares = contexts
826            .iter()
827            .map(|p| {
828                let participant_index = p.index as u32;
829                let blinded_key_share =
830                    p.public_decryption_contexts[p.index].blinded_key_share;
831
832                // And creates a new, refreshed share
833                let updated_blinded_key_share =
834                    UpdatableBlindedKeyShare(blinded_key_share)
835                        .apply_share_updates(
836                            &update_transcripts_by_producer,
837                            participant_index,
838                        );
839
840                let validator_keypair = ferveo_common::Keypair {
841                    decryption_key: p.setup_params.b,
842                };
843                let updated_private_share = updated_blinded_key_share
844                    .0
845                    .unblind(&validator_keypair)
846                    .unwrap();
847
848                (participant_index, updated_private_share)
849            })
850            // We only need `threshold` refreshed shares to recover the original share
851            .take(security_threshold)
852            .collect::<HashMap<u32, ferveo_tdec::PrivateKeyShare<E>>>();
853
854        let domain_points = domain_points_and_keys
855            .iter()
856            .map(|(share_index, (domain_point, _))| {
857                (*share_index, *domain_point)
858            })
859            .collect::<HashMap<u32, DomainPoint<E>>>();
860
861        let x_r = ScalarField::zero();
862        let new_shared_private_key =
863            combine_private_shares_at(&x_r, &domain_points, &refreshed_shares);
864        assert_eq!(shared_private_key, new_shared_private_key);
865    }
866
867    // TODO: Simple handover transcript unit test
868
869    /// 2 parties follow a handover protocol. The output is a new blind share
870    /// that replaces the original share, using the same domain point
871    /// but different validator key.
872    #[test_case(4; "N is not a power of 2, t=N")]
873    fn tdec_simple_variant_share_handover(shares_num: usize) {
874        // Test setup
875        let rng = &mut test_rng();
876        let security_threshold = shares_num;
877        let (_, shared_private_key, private_contexts) =
878            setup_simple::<E>(shares_num, security_threshold, rng);
879        let domain_points = &private_contexts
880            .iter()
881            .map(|ctxt| {
882                (
883                    ctxt.index as u32,
884                    ctxt.public_decryption_contexts[ctxt.index].domain,
885                )
886            })
887            .collect::<HashMap<u32, DomainPoint<E>>>();
888
889        // New participant that will receive the handover
890        let incoming_validator_keypair = Keypair::<E>::new(rng);
891
892        // For simplicity, we're going to do the handover with the last participant
893        let departing_participant = private_contexts.last().unwrap();
894        let handover_slot_index: u32 = departing_participant.index as u32;
895        let departing_public_context = departing_participant
896            .public_decryption_contexts
897            .get(handover_slot_index as usize)
898            .unwrap();
899
900        let departing_blinded_share =
901            departing_public_context.blinded_key_share;
902        let departing_public_key = ferveo_common::PublicKey {
903            encryption_key: departing_public_context
904                .blinded_key_share
905                .validator_public_key,
906        };
907
908        // Incoming node creates a handover transcript
909        let handover_transcript = HandoverTranscript::<E>::new(
910            handover_slot_index,
911            &departing_blinded_share,
912            departing_public_key,
913            &incoming_validator_keypair,
914            rng,
915        );
916
917        // Make sure handover transcript is valid. This is publicly verifiable.
918        assert!(handover_transcript
919            .validate(departing_public_context.share_commitment)
920            .unwrap());
921
922        // This portion shows that handover can be finalized by the departing participant,
923        // and that the new blinded share contains the same private key share.
924        // TODO: This is a low-level check for now. This will be part of the handover protocol.
925        let departing_validator_private_key =
926            departing_participant.setup_params.b;
927        let departing_validator_keypair = Keypair::<E> {
928            decryption_key: departing_validator_private_key,
929        };
930        let new_blinded_share = handover_transcript
931            .finalize(
932                &departing_validator_keypair,
933                departing_public_context.share_commitment,
934            )
935            .unwrap();
936
937        let old_private_share = departing_blinded_share
938            .unblind(&departing_validator_keypair)
939            .unwrap();
940        let new_private_share = new_blinded_share
941            .unblind(&incoming_validator_keypair)
942            .unwrap();
943        assert_eq!(new_private_share, old_private_share);
944
945        // We check that the private share from the other participants plus the
946        // new_private_share obtained after handover can be combined to
947        // reconstruct the shared private key.
948        let other_participants = private_contexts
949            .iter()
950            .filter(|p| p.index as u32 != handover_slot_index);
951
952        let mut shares_map = other_participants
953            .map(|p| {
954                let participant_index = p.index as u32;
955                let blinded_key_share =
956                    p.public_decryption_contexts[p.index].blinded_key_share;
957                let validator_keypair = ferveo_common::Keypair {
958                    decryption_key: p.setup_params.b,
959                };
960
961                let private_share =
962                    blinded_key_share.unblind(&validator_keypair).unwrap();
963
964                (participant_index, private_share)
965            })
966            .collect::<HashMap<u32, ferveo_tdec::PrivateKeyShare<E>>>();
967
968        shares_map.insert(handover_slot_index, new_private_share);
969
970        let x_r = ScalarField::zero();
971        let new_shared_private_key =
972            combine_private_shares_at(&x_r, domain_points, &shares_map);
973        assert_eq!(shared_private_key, new_shared_private_key);
974    }
975}