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#[derive(
29 Debug,
30 Clone, )]
32pub struct UpdatableBlindedKeyShare<E: Pairing>(
33 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 pub fn apply_share_updates(
47 &self,
48 update_transcripts: &HashMap<u32, UpdateTranscript<E>>,
49 index: u32,
50 ) -> Self {
51 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 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 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 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 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 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 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 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#[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 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 pub coeffs: Vec<E::G1Affine>,
195
196 pub updates: HashMap<u32, ShareUpdate<E>>,
198}
199
200impl<E: Pairing> UpdateTranscript<E> {
201 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 prepare_share_updates_with_root::<E>(
209 domain_points_and_keys,
210 &DomainPoint::<E>::zero(),
211 threshold,
212 rng,
213 )
214 }
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 prepare_share_updates_with_root::<E>(
225 domain_points_and_keys,
226 x_r,
227 threshold,
228 rng,
229 )
230 }
232
233 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 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 update
253 .verify(validator_public_keys.get(index).unwrap())
254 .unwrap();
255
256 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 }
263
264 if root.is_zero() {
269 assert!(self.coeffs[0].is_zero());
271 } else {
273 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 Ok(true)
286 }
287
288 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#[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 let random_scalar = E::ScalarField::rand(rng);
346 let incoming_decryption_key = incoming_validator_keypair.decryption_key;
348 let double_blind_share = outgoing_blinded_share
350 .blinded_key_share
351 .mul(incoming_decryption_key);
352 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 pub fn validate(
370 &self,
371 share_commitment: ShareCommitment<E>,
372 ) -> Result<bool> {
373 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 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) }
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); }
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
422fn 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 let update_poly =
435 make_random_polynomial_with_root::<E>(threshold - 1, root, rng);
436
437 let g = E::G1::generator();
439 let coeff_commitments = fast_multiexp(&update_poly.coeffs, g);
440
441 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 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
462fn make_random_polynomial_with_root<E: Pairing>(
464 degree: u32,
465 root: &DomainPoint<E>,
466 rng: &mut impl RngCore,
467) -> DensePolynomial<DomainPoint<E>> {
468 let mut poly =
470 DensePolynomial::<DomainPoint<E>>::rand(degree as usize, rng);
471
472 poly[0] = DomainPoint::<E>::zero();
474
475 let new_c_0 = DomainPoint::<E>::zero() - poly.evaluate(root);
477 poly[0] = new_c_0;
478
479 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 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 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 #[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 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 let mut remaining_participants = contexts;
614 for p in &mut remaining_participants {
615 p.public_decryption_contexts.pop().unwrap();
616 }
617
618 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 assert_ne!(original_private_key_share, original_private_key_share);
665 }
666
667 #[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 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 let x_r = ScalarField::rand(rng);
696
697 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 let _private_shares = contexts
730 .into_iter()
731 .map(|ctxt| (ctxt.index as u32, ctxt.private_key_share))
732 .collect::<HashMap<u32, _>>();
733
734 domain_points.insert(removed_participant.index as u32, x_r);
736 assert_ne!(shared_private_key, shared_private_key);
757 }
758
759 #[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 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 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 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 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 .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 #[test_case(4; "N is not a power of 2, t=N")]
873 fn tdec_simple_variant_share_handover(shares_num: usize) {
874 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 let incoming_validator_keypair = Keypair::<E>::new(rng);
891
892 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 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 assert!(handover_transcript
919 .validate(departing_public_context.share_commitment)
920 .unwrap());
921
922 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 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}