Skip to main content

ferveo_nucypher/
lib.rs

1#![warn(rust_2018_idioms)]
2
3#[cfg(feature = "bindings-wasm")]
4extern crate alloc;
5
6#[cfg(feature = "bindings-python")]
7pub mod bindings_python;
8
9#[cfg(feature = "bindings-wasm")]
10pub mod bindings_wasm;
11
12pub mod api;
13pub mod dkg;
14pub mod primitives;
15pub mod pvss;
16pub mod refresh;
17pub mod validator;
18
19#[cfg(test)]
20mod test_common;
21
22pub use dkg::*;
23pub use primitives::*;
24pub use pvss::*;
25pub use refresh::*;
26pub use validator::*;
27
28#[derive(Debug, thiserror::Error)]
29pub enum Error {
30    #[error(transparent)]
31    ThresholdEncryptionError(#[from] ferveo_tdec::Error),
32
33    /// DKG validator set must contain the validator with the given address
34    #[error("Expected validator to be a part of the DKG validator set: {0}")]
35    DealerNotInValidatorSet(EthereumAddress),
36
37    /// DKG received an unknown dealer. Dealer must be the part of the DKG validator set.
38    #[error("DKG received an unknown dealer: {0}")]
39    UnknownDealer(EthereumAddress),
40
41    /// DKG received a PVSS transcript from a dealer that has already been dealt.
42    #[error("DKG received a PVSS transcript from a dealer that has already been dealt: {0}")]
43    DuplicateDealer(EthereumAddress),
44
45    /// DKG received an invalid transcript for which optimistic verification failed
46    #[error("DKG received an invalid transcript from validator: {0}")]
47    InvalidPvssTranscript(EthereumAddress),
48
49    /// Not enough validators to perform the DKG for a given number of shares
50    #[error("Not enough validators (expected {0}, got {1})")]
51    InsufficientValidators(u32, u32),
52
53    /// Transcript aggregate doesn't match the received PVSS instances
54    #[error("Transcript aggregate doesn't match the received PVSS instances")]
55    InvalidTranscriptAggregate,
56
57    /// The validator public key doesn't match the one in the DKG
58    #[error("Validator public key mismatch")]
59    ValidatorPublicKeyMismatch,
60
61    #[error(transparent)]
62    BincodeError(#[from] bincode::Error),
63
64    #[error(transparent)]
65    ArkSerializeError(#[from] ark_serialize::SerializationError),
66
67    /// Invalid byte length
68    #[error("Invalid byte length. Expected {0}, got {1}")]
69    InvalidByteLength(usize, usize),
70
71    /// Invalid variant
72    #[error("Invalid variant: {0}")]
73    InvalidVariant(String),
74
75    /// DKG parameters validation failed
76    #[error("Invalid DKG parameters: number of shares {0}, threshold {1}")]
77    InvalidDkgParameters(u32, u32),
78
79    /// Failed to access a share for a given share index
80    #[error("Invalid share index: {0}")]
81    InvalidShareIndex(u32),
82
83    /// Failed to verify a share update
84    #[error("Invalid share update")]
85    InvalidShareUpdate,
86
87    /// Failed to produce a precomputed variant decryption share
88    #[error("Invalid DKG parameters for precomputed variant: number of shares {0}, threshold {1}")]
89    InvalidDkgParametersForPrecomputedVariant(u32, u32),
90
91    /// DKG may not contain duplicated share indices
92    #[error("Duplicated share index: {0}")]
93    DuplicatedShareIndex(u32),
94
95    /// Creating a transcript aggregate requires at least one transcript
96    #[error("No transcripts to aggregate")]
97    NoTranscriptsToAggregate,
98
99    /// The number of messages may not be greater than the number of validators
100    #[error("Invalid aggregate verification parameters: number of validators {0}, number of messages: {1}")]
101    InvalidAggregateVerificationParameters(u32, u32),
102
103    /// Too many transcripts received by the DKG
104    #[error("Too many transcripts. Expected: {0}, got: {1}")]
105    TooManyTranscripts(u32, u32),
106
107    /// Received a duplicated transcript from a validator
108    #[error("Received a duplicated transcript from validator: {0}")]
109    DuplicateTranscript(EthereumAddress),
110}
111
112pub type Result<T> = std::result::Result<T, Error>;
113
114#[cfg(test)]
115mod test_dkg_full {
116    use std::collections::HashMap;
117
118    use ark_bls12_381::{Bls12_381 as E, Fr, G1Affine};
119    use ark_ec::{AffineRepr, CurveGroup};
120    use ark_ff::{UniformRand, Zero};
121    use ark_std::test_rng;
122    use ferveo_common::Keypair;
123    use ferveo_tdec::{
124        self, DecryptionSharePrecomputed, DecryptionShareSimple, SecretBox,
125        ShareCommitment, SharedSecret,
126    };
127    use itertools::{izip, Itertools};
128    use rand::{seq::SliceRandom, Rng};
129    use test_case::test_case;
130
131    use super::*;
132    use crate::test_common::*;
133
134    pub fn create_shared_secret_simple_tdec(
135        dkg: &PubliclyVerifiableDkg<E>,
136        aad: &[u8],
137        ciphertext_header: &ferveo_tdec::CiphertextHeader<E>,
138        validator_keypairs: &[Keypair<E>],
139        transcripts: &[PubliclyVerifiableSS<E>],
140    ) -> (
141        AggregatedTranscript<E>,
142        Vec<DecryptionShareSimple<E>>,
143        SharedSecret<E>,
144    ) {
145        let server_aggregate =
146            AggregatedTranscript::from_transcripts(transcripts).unwrap();
147        assert!(server_aggregate
148            .aggregate
149            .verify_aggregation(dkg, transcripts)
150            .unwrap());
151
152        let decryption_shares: Vec<DecryptionShareSimple<E>> =
153            validator_keypairs
154                .iter()
155                .map(|validator_keypair| {
156                    let validator = dkg
157                        .get_validator(&validator_keypair.public_key())
158                        .unwrap();
159                    server_aggregate
160                        .aggregate
161                        .create_decryption_share_simple(
162                            ciphertext_header,
163                            aad,
164                            validator_keypair,
165                            validator.share_index,
166                        )
167                        .unwrap()
168                })
169                // We take only the first `security_threshold` decryption shares
170                .take(dkg.dkg_params.security_threshold() as usize)
171                .collect();
172
173        let domain_points = &dkg.domain_points()[..decryption_shares.len()];
174        assert_eq!(domain_points.len(), decryption_shares.len());
175
176        let lagrange_coeffs =
177            ferveo_tdec::prepare_combine_simple::<E>(domain_points);
178        let shared_secret = ferveo_tdec::share_combine_simple::<E>(
179            &decryption_shares,
180            &lagrange_coeffs,
181        );
182        (server_aggregate, decryption_shares, shared_secret)
183    }
184
185    #[test_case(4, 3; "N is a power of 2, t is 1 + 50%")]
186    #[test_case(4, 4; "N is a power of 2, t=N")]
187    #[test_case(30, 16; "N is not a power of 2, t is 1 + 50%")]
188    #[test_case(30, 30; "N is not a power of 2, t=N")]
189    fn test_dkg_simple_tdec(shares_num: u32, security_threshold: u32) {
190        let rng = &mut test_rng();
191        let validators_num = shares_num; // TODO: #197
192        let (dkg, validator_keypairs, messages) =
193            setup_dealt_dkg_with_n_validators(
194                security_threshold,
195                shares_num,
196                validators_num,
197            );
198        let transcripts = messages
199            .iter()
200            .take(shares_num as usize)
201            .map(|m| m.1.clone())
202            .collect::<Vec<_>>();
203        let local_aggregate =
204            AggregatedTranscript::from_transcripts(&transcripts).unwrap();
205        assert!(local_aggregate
206            .aggregate
207            .verify_aggregation(&dkg, &transcripts)
208            .unwrap());
209        let ciphertext = ferveo_tdec::encrypt::<E>(
210            SecretBox::new(MSG.to_vec()),
211            AAD,
212            &local_aggregate.public_key,
213            rng,
214        )
215        .unwrap();
216        let (_, _, shared_secret) = create_shared_secret_simple_tdec(
217            &dkg,
218            AAD,
219            &ciphertext.header().unwrap(),
220            validator_keypairs.as_slice(),
221            &transcripts,
222        );
223
224        let plaintext = ferveo_tdec::decrypt_with_shared_secret(
225            &ciphertext,
226            AAD,
227            &shared_secret,
228        )
229        .unwrap();
230        assert_eq!(plaintext, MSG);
231    }
232
233    #[test_case(4, 3; "N is a power of 2, t is 1 + 50%")]
234    #[test_case(4, 4; "N is a power of 2, t=N")]
235    #[test_case(30, 16; "N is not a power of 2, t is 1 + 50%")]
236    #[test_case(30, 30; "N is not a power of 2, t=N")]
237    fn test_dkg_simple_tdec_precomputed(
238        shares_num: u32,
239        security_threshold: u32,
240    ) {
241        let rng = &mut test_rng();
242        let validators_num = shares_num; // TODO: #197
243        let (dkg, validator_keypairs, messages) =
244            setup_dealt_dkg_with_n_transcript_dealt(
245                security_threshold,
246                shares_num,
247                validators_num,
248                shares_num,
249            );
250        let transcripts = messages
251            .iter()
252            .take(shares_num as usize)
253            .map(|m| m.1.clone())
254            .collect::<Vec<_>>();
255        let local_aggregate =
256            AggregatedTranscript::from_transcripts(&transcripts).unwrap();
257        assert!(local_aggregate
258            .aggregate
259            .verify_aggregation(&dkg, &transcripts)
260            .unwrap());
261        let ciphertext = ferveo_tdec::encrypt::<E>(
262            SecretBox::new(MSG.to_vec()),
263            AAD,
264            &local_aggregate.public_key,
265            rng,
266        )
267        .unwrap();
268
269        // In precomputed variant, client selects a specific subset of validators to create
270        // decryption shares
271        let selected_keypairs = validator_keypairs
272            .choose_multiple(rng, security_threshold as usize)
273            .collect::<Vec<_>>();
274        let selected_validators = selected_keypairs
275            .iter()
276            .map(|keypair| {
277                dkg.get_validator(&keypair.public_key())
278                    .expect("Validator not found")
279            })
280            .collect::<Vec<_>>();
281        let selected_domain_points = selected_validators
282            .iter()
283            .filter_map(|v| {
284                dkg.get_domain_point(v.share_index)
285                    .ok()
286                    .map(|domain_point| (v.share_index, domain_point))
287            })
288            .collect::<HashMap<u32, ferveo_tdec::DomainPoint<E>>>();
289
290        let mut decryption_shares: Vec<DecryptionSharePrecomputed<E>> =
291            selected_keypairs
292                .iter()
293                .map(|validator_keypair| {
294                    let validator = dkg
295                        .get_validator(&validator_keypair.public_key())
296                        .unwrap();
297                    local_aggregate
298                        .aggregate
299                        .create_decryption_share_precomputed(
300                            &ciphertext.header().unwrap(),
301                            AAD,
302                            validator_keypair,
303                            validator.share_index,
304                            &selected_domain_points,
305                        )
306                        .unwrap()
307                })
308                .collect();
309        // Order of decryption shares is not important
310        decryption_shares.shuffle(rng);
311
312        // Decrypt with precomputed variant
313        let shared_secret =
314            ferveo_tdec::share_combine_precomputed::<E>(&decryption_shares);
315        let plaintext = ferveo_tdec::decrypt_with_shared_secret(
316            &ciphertext,
317            AAD,
318            &shared_secret,
319        )
320        .unwrap();
321        assert_eq!(plaintext, MSG);
322    }
323
324    #[test_case(4, 3; "N is a power of 2, t is 1 + 50%")]
325    #[test_case(4, 4; "N is a power of 2, t=N")]
326    #[test_case(30, 16; "N is not a power of 2, t is 1 + 50%")]
327    #[test_case(30, 30; "N is not a power of 2, t=N")]
328    fn test_dkg_simple_tdec_share_verification(
329        shares_num: u32,
330        security_threshold: u32,
331    ) {
332        let rng = &mut test_rng();
333        let (dkg, validator_keypairs, messages) =
334            setup_dealt_dkg_with(security_threshold, shares_num);
335        let transcripts = messages
336            .iter()
337            .take(shares_num as usize)
338            .map(|m| m.1.clone())
339            .collect::<Vec<_>>();
340        let local_aggregate =
341            AggregatedTranscript::from_transcripts(&transcripts).unwrap();
342        assert!(local_aggregate
343            .aggregate
344            .verify_aggregation(&dkg, &transcripts)
345            .unwrap());
346        let ciphertext = ferveo_tdec::encrypt::<E>(
347            SecretBox::new(MSG.to_vec()),
348            AAD,
349            &local_aggregate.public_key,
350            rng,
351        )
352        .unwrap();
353
354        let (local_aggregate, decryption_shares, _) =
355            create_shared_secret_simple_tdec(
356                &dkg,
357                AAD,
358                &ciphertext.header().unwrap(),
359                validator_keypairs.as_slice(),
360                &transcripts,
361            );
362
363        izip!(
364            &local_aggregate.aggregate.shares,
365            &validator_keypairs,
366            &decryption_shares,
367        )
368        .for_each(
369            |(aggregated_share, validator_keypair, decryption_share)| {
370                assert!(decryption_share.verify(
371                    aggregated_share,
372                    &validator_keypair.public_key().encryption_key,
373                    &ciphertext,
374                ));
375            },
376        );
377
378        // Testing red-path decryption share verification
379        let decryption_share = decryption_shares[0].clone();
380
381        // Should fail because of the bad decryption share
382        let mut with_bad_decryption_share = decryption_share.clone();
383        with_bad_decryption_share.decryption_share = TargetField::zero();
384        assert!(!with_bad_decryption_share.verify(
385            &local_aggregate.aggregate.shares[0],
386            &validator_keypairs[0].public_key().encryption_key,
387            &ciphertext,
388        ));
389
390        // Should fail because of the bad checksum
391        let mut with_bad_checksum = decryption_share;
392        with_bad_checksum.validator_checksum.checksum = G1Affine::zero();
393        assert!(!with_bad_checksum.verify(
394            &local_aggregate.aggregate.shares[0],
395            &validator_keypairs[0].public_key().encryption_key,
396            &ciphertext,
397        ));
398    }
399
400    // FIXME: This test is currently broken, and adjusted to allow compilation
401    // Also, see test cases in other tests that include threshold as a parameter
402    #[ignore = "Re-introduce recovery tests - #193"]
403    #[test_case(4, 4; "number of shares (validators) is a power of 2")]
404    #[test_case(7, 7; "number of shares (validators) is not a power of 2")]
405    #[test_case(4, 6; "number of validators greater than the number of shares")]
406    fn test_dkg_simple_tdec_share_recovery(
407        shares_num: u32,
408        validators_num: u32,
409    ) {
410        let rng = &mut test_rng();
411        let security_threshold = shares_num;
412        let (dkg, validator_keypairs, messages) =
413            setup_dealt_dkg_with_n_validators(
414                security_threshold,
415                shares_num,
416                validators_num,
417            );
418        let transcripts = messages
419            .iter()
420            .take(shares_num as usize)
421            .map(|m| m.1.clone())
422            .collect::<Vec<_>>();
423        let local_aggregate =
424            AggregatedTranscript::from_transcripts(&transcripts).unwrap();
425        assert!(local_aggregate
426            .aggregate
427            .verify_aggregation(&dkg, &transcripts)
428            .unwrap());
429        let ciphertext = ferveo_tdec::encrypt::<E>(
430            SecretBox::new(MSG.to_vec()),
431            AAD,
432            &local_aggregate.public_key,
433            rng,
434        )
435        .unwrap();
436
437        // Create an initial shared secret
438        let (_, _, old_shared_secret) = create_shared_secret_simple_tdec(
439            &dkg,
440            AAD,
441            &ciphertext.header().unwrap(),
442            validator_keypairs.as_slice(),
443            &transcripts,
444        );
445
446        // TODO: Rewrite this test so that the offboarding of validator
447        // is done by recreating a DKG instance with a new set of
448        // validators from the Coordinator, rather than modifying the
449        // existing DKG instance.
450
451        // Remove one participant from the contexts and all nested structure
452        let removed_validator_index = rng.gen_range(0..validators_num);
453        let mut remaining_validators = dkg.validators.clone();
454        remaining_validators.remove(&removed_validator_index);
455
456        // Remember to remove one domain point too
457        let mut domain_points = dkg.domain_point_map();
458        domain_points.remove(&removed_validator_index);
459
460        // Now, we're going to recover a new share at a random point,
461        // and check that the shared secret is still the same.
462
463        // Our random point:
464        let x_r = Fr::rand(rng);
465
466        // Each participant prepares an update for every other participant
467        // let share_updates = remaining_validators
468        //     .keys()
469        //     .map(|v_addr| {
470        //         let deltas_i =
471        //             crate::refresh::UpdateTranscript::create_recovery_updates(
472        //                 &dkg.domain_and_key_map(),
473        //                 &x_r,
474        //                 dkg.dkg_params.security_threshold(),
475        //                 rng,
476        //             )
477        //             .updates;
478        //         (v_addr.clone(), deltas_i)
479        //     })
480        //     .collect::<HashMap<_, _>>();
481
482        // Participants share updates and update their shares
483
484        // Now, every participant separately:
485        // let updated_shares: HashMap<u32, _> = remaining_validators
486        //     .values()
487        //     .map(|validator| {
488        //         // Current participant receives updates from other participants
489        //         let updates_for_validator: Vec<_> = share_updates
490        //             .values()
491        //             .map(|updates| updates.get(&validator.share_index).unwrap())
492        //             .cloned()
493        //             .collect();
494
495        //         // Each validator uses their decryption key to update their share
496        //         let validator_keypair = validator_keypairs
497        //             .get(validator.share_index as usize)
498        //             .unwrap();
499
500        //         // Creates updated private key shares
501        //         let updated_key_share =
502        //             AggregatedTranscript::from_transcripts(&transcripts)
503        //                 .unwrap()
504        //                 .aggregate
505        //                 .create_updated_private_key_share(
506        //                     validator_keypair,
507        //                     validator.share_index,
508        //                     updates_for_validator.as_slice(),
509        //                 )
510        //                 .unwrap();
511        //         (validator.share_index, updated_key_share)
512        //     })
513        //     .collect();
514
515        // // Now, we have to combine new share fragments into a new share
516        // let recovered_key_share =
517        //     PrivateKeyShare::recover_share_from_updated_private_shares(
518        //         &x_r,
519        //         &domain_points,
520        //         &updated_shares,
521        //     )
522        //     .unwrap();
523
524        // Get decryption shares from remaining participants
525        let decryption_shares = remaining_validators
526            .values()
527            .map(|validator| {
528                let validator_keypair = validator_keypairs
529                    .get(validator.share_index as usize)
530                    .unwrap();
531                let decryption_share =
532                    AggregatedTranscript::from_transcripts(&transcripts)
533                        .unwrap()
534                        .aggregate
535                        .create_decryption_share_simple(
536                            &ciphertext.header().unwrap(),
537                            AAD,
538                            validator_keypair,
539                            validator.share_index,
540                        )
541                        .unwrap();
542                (validator.share_index, decryption_share)
543            })
544            // We take only the first `security_threshold - 1` decryption shares
545            .take((dkg.dkg_params.security_threshold() - 1) as usize)
546            .collect::<HashMap<u32, _>>();
547
548        // Create a decryption share from a recovered private key share
549        // let new_validator_decryption_key = Fr::rand(rng);
550        // let new_decryption_share = DecryptionShareSimple::create(
551        //     &new_validator_decryption_key,
552        //     &recovered_key_share.0,
553        //     &ciphertext.header().unwrap(),
554        //     AAD,
555        //     &dkg.pvss_params.g_inv(),
556        // )
557        // .unwrap();
558        // decryption_shares.insert(removed_validator_index, new_decryption_share);
559        domain_points.insert(removed_validator_index, x_r);
560
561        // We need to make sure that the domain points and decryption shares are ordered
562        // by the share index, so that the lagrange basis is calculated correctly
563
564        let mut domain_points_ = vec![];
565        let mut decryption_shares_ = vec![];
566        for share_index in decryption_shares.keys().sorted() {
567            domain_points_.push(
568                *domain_points
569                    .get(share_index)
570                    .ok_or(Error::InvalidShareIndex(*share_index))
571                    .unwrap(),
572            );
573            decryption_shares_.push(
574                decryption_shares
575                    .get(share_index)
576                    .ok_or(Error::InvalidShareIndex(*share_index))
577                    .unwrap()
578                    .clone(),
579            );
580        }
581        assert_eq!(domain_points_.len(), security_threshold as usize);
582        assert_eq!(decryption_shares_.len(), security_threshold as usize);
583
584        let lagrange =
585            ferveo_tdec::prepare_combine_simple::<E>(&domain_points_);
586        let new_shared_secret = ferveo_tdec::share_combine_simple::<E>(
587            &decryption_shares_,
588            &lagrange,
589        );
590        assert_eq!(
591            old_shared_secret, new_shared_secret,
592            "Shared secret reconstruction failed"
593        );
594    }
595
596    #[test_case(4, 3; "N is a power of 2, t is 1 + 50%")]
597    #[test_case(4, 4; "N is a power of 2, t=N")]
598    #[test_case(30, 16; "N is not a power of 2, t is 1 + 50%")]
599    #[test_case(30, 30; "N is not a power of 2, t=N")]
600    fn test_dkg_simple_tdec_share_refreshing(
601        shares_num: u32,
602        security_threshold: u32,
603    ) {
604        let rng = &mut test_rng();
605        let (dkg, validator_keypairs, messages) =
606            setup_dealt_dkg_with(security_threshold, shares_num);
607        let transcripts = messages
608            .iter()
609            .take(shares_num as usize)
610            .map(|m| m.1.clone())
611            .collect::<Vec<_>>();
612
613        // Initially, each participant creates a transcript, which is
614        // combined into a joint AggregateTranscript.
615        let local_aggregate =
616            AggregatedTranscript::from_transcripts(&transcripts).unwrap();
617        assert!(local_aggregate
618            .aggregate
619            .verify_aggregation(&dkg, &transcripts)
620            .unwrap());
621
622        // Ciphertext created from the aggregate public key
623        let ciphertext = ferveo_tdec::encrypt::<E>(
624            SecretBox::new(MSG.to_vec()),
625            AAD,
626            &local_aggregate.public_key,
627            rng,
628        )
629        .unwrap();
630
631        // The set of transcripts (or equivalently, the AggregateTranscript),
632        // represents a (blinded) shared secret.
633        let (_, _, old_shared_secret) = create_shared_secret_simple_tdec(
634            &dkg,
635            AAD,
636            &ciphertext.header().unwrap(),
637            validator_keypairs.as_slice(),
638            &transcripts,
639        );
640
641        // When the share refresh protocol is necessary, each participant
642        // prepares an UpdateTranscript, containing updates for each other.
643        let mut update_transcripts: HashMap<u32, UpdateTranscript<E>> =
644            HashMap::new();
645        let mut validator_map: HashMap<u32, _> = HashMap::new();
646
647        for validator in dkg.validators.values() {
648            update_transcripts.insert(
649                validator.share_index,
650                dkg.generate_refresh_transcript(rng).unwrap(),
651            );
652            validator_map.insert(
653                validator.share_index,
654                validator_keypairs
655                    .get(validator.share_index as usize)
656                    .unwrap()
657                    .public_key(),
658            );
659        }
660
661        // Participants distribute UpdateTranscripts and update their shares
662        // accordingly. The result is a new, joint AggregatedTranscript.
663        let new_aggregate = local_aggregate
664            .aggregate
665            .refresh(&update_transcripts, &validator_map)
666            .unwrap();
667
668        // TODO: Assert new aggregate is different than original, including coefficients
669        assert_ne!(local_aggregate.aggregate, new_aggregate);
670
671        // TODO: Show that all participants obtain the same new aggregate transcript.
672
673        // Get decryption shares, now with the refreshed aggregate transcript:
674        let decryption_shares: Vec<DecryptionShareSimple<E>> =
675            validator_keypairs
676                .iter()
677                .map(|validator_keypair| {
678                    let validator = dkg
679                        .get_validator(&validator_keypair.public_key())
680                        .unwrap();
681                    new_aggregate
682                        .create_decryption_share_simple(
683                            &ciphertext.header().unwrap(),
684                            AAD,
685                            validator_keypair,
686                            validator.share_index,
687                        )
688                        .unwrap()
689                })
690                // We take only the first `security_threshold` decryption shares
691                .take(dkg.dkg_params.security_threshold() as usize)
692                .collect();
693
694        // Order of decryption shares is not important, but since we are using low-level
695        // API here to performa a refresh for testing purpose, we will not shuffle
696        // the shares this time
697        // decryption_shares.shuffle(rng);
698
699        let lagrange = ferveo_tdec::prepare_combine_simple::<E>(
700            &dkg.domain_points()[..security_threshold as usize],
701        );
702        let new_shared_secret = ferveo_tdec::share_combine_simple::<E>(
703            &decryption_shares[..security_threshold as usize],
704            &lagrange,
705        );
706        assert_eq!(old_shared_secret, new_shared_secret);
707    }
708
709    #[test_case(4, 3; "N is a power of 2, t is 1 + 50%")]
710    #[test_case(4, 4; "N is a power of 2, t=N")]
711    #[test_case(30, 16; "N is not a power of 2, t is 1 + 50%")]
712    #[test_case(30, 30; "N is not a power of 2, t=N")]
713    fn test_dkg_simple_tdec_handover(shares_num: u32, security_threshold: u32) {
714        let rng = &mut test_rng();
715        let (dkg, validator_keypairs, messages) =
716            setup_dealt_dkg_with(security_threshold, shares_num);
717
718        // // TODO: Auxiliary debugging for validator keypairs. See issue below -- #203
719        // for (i, v) in validator_keypairs.iter().enumerate() {
720        //     println!("Validator {:?}: {:?}", i, v.public_key());
721        // }
722        // //
723
724        let transcripts = messages
725            .iter()
726            .take(shares_num as usize)
727            .map(|m| m.1.clone())
728            .collect::<Vec<_>>();
729
730        // Initially, each participant creates a transcript, which is
731        // combined into a joint AggregateTranscript.
732        let local_aggregate =
733            AggregatedTranscript::from_transcripts(&transcripts).unwrap();
734        assert!(local_aggregate
735            .aggregate
736            .verify_aggregation(&dkg, &transcripts)
737            .unwrap());
738
739        // Ciphertext created from the aggregate public key
740        let ciphertext = ferveo_tdec::encrypt::<E>(
741            SecretBox::new(MSG.to_vec()),
742            AAD,
743            &local_aggregate.public_key,
744            rng,
745        )
746        .unwrap();
747
748        // The set of transcripts (or equivalently, the AggregateTranscript),
749        // represents a (blinded) shared secret.
750        let (_, _, old_shared_secret) = create_shared_secret_simple_tdec(
751            &dkg,
752            AAD,
753            &ciphertext.header().unwrap(),
754            validator_keypairs.as_slice(),
755            &transcripts,
756        );
757
758        // Let's choose a random validator to handover
759        let handover_slot_index = rng.gen_range(0..shares_num);
760        // TODO: #203 Investigate why if we move this line after the next one (i.e. after generating a random keypair),
761        // the keypair produced is repeated from the initial validator keypairs. This only fails for the N=4 case (wtf?)
762
763        // New participant that will receive the handover
764        let incoming_validator_keypair = Keypair::<E>::new(rng);
765        // println!("Validator {:?}*: {:?}", handover_slot_index, incoming_validator_keypair.public_key());
766
767        // TODO: Rewrite this test so that the offboarding of validator
768        // is done by recreating a DKG instance with a new set of
769        // validators from the Coordinator, rather than modifying the
770        // existing DKG instance.
771
772        // Get departing validator's public key and blinded share
773        let departing_validator =
774            dkg.validators.get(&handover_slot_index).unwrap();
775        let departing_public_key = departing_validator.public_key;
776        assert_eq!(departing_validator.share_index, handover_slot_index);
777        assert_ne!(
778            departing_public_key,
779            incoming_validator_keypair.public_key()
780        );
781
782        // Incoming node creates a handover transcript
783        let handover_transcript = dkg
784            .generate_handover_transcript(
785                &local_aggregate,
786                handover_slot_index,
787                &incoming_validator_keypair,
788                rng,
789            )
790            .unwrap();
791
792        // Make sure handover transcript is valid. This is publicly verifiable.
793        // We're doing this for testing purposes, but in practice, this is done
794        // by the departing participant when using the high-level API.
795        let share_commitments = get_share_commitments_from_poly_commitments::<E>(
796            &local_aggregate.aggregate.coeffs,
797            &dkg.domain,
798        );
799        let share_commitment = ShareCommitment::<E>(
800            share_commitments
801                .get(handover_slot_index as usize)
802                .ok_or(Error::InvalidShareIndex(handover_slot_index))
803                .unwrap()
804                .into_affine(),
805        );
806        assert!(handover_transcript.validate(share_commitment).unwrap());
807
808        // The departing validator uses the handover transcript produced by the
809        // incoming validator to create a new aggregate transcript.
810        // This part is showing the high-level API for handover finalization.
811        let departing_keypair = validator_keypairs
812            .get(handover_slot_index as usize)
813            .unwrap();
814        assert_eq!(
815            departing_validator.public_key,
816            departing_keypair.public_key()
817        );
818
819        let aggregate_after_handover = local_aggregate
820            .aggregate
821            .finalize_handover(&handover_transcript, departing_keypair)
822            .unwrap();
823
824        // If we use a different keypair, we should get an error
825        let error = local_aggregate
826            .aggregate
827            .finalize_handover(
828                &handover_transcript,
829                &incoming_validator_keypair,
830            )
831            .unwrap_err();
832        assert_eq!(
833            error.to_string(),
834            Error::ValidatorPublicKeyMismatch.to_string()
835        );
836
837        // New aggregate is different than original...
838        assert_ne!(local_aggregate.aggregate, aggregate_after_handover);
839
840        // ...but let's look a bit deeper:
841        // - Polynomial coefficients are the same, which makes sense since the private shares are not changing
842        assert_eq!(
843            local_aggregate.aggregate.coeffs,
844            aggregate_after_handover.coeffs
845        );
846        // - The shares vector is different ...
847        assert_ne!(
848            local_aggregate.aggregate.shares,
849            aggregate_after_handover.shares
850        );
851        // ... but actually they only differ at the handover index
852        for i in 0..shares_num {
853            let share_before = local_aggregate.aggregate.shares.get(i as usize);
854            let share_after = aggregate_after_handover.shares.get(i as usize);
855            if i == handover_slot_index {
856                assert_ne!(share_before, share_after);
857            } else {
858                assert_eq!(share_before, share_after);
859            }
860        }
861
862        // Get decryption shares, now with the aggregate transcript after handover:
863        let decryption_shares: Vec<DecryptionShareSimple<E>> =
864            validator_keypairs
865                .iter()
866                .enumerate()
867                .map(|(index, validator_keypair)| {
868                    let keypair = if index == handover_slot_index as usize {
869                        &incoming_validator_keypair
870                    } else {
871                        validator_keypair
872                    };
873                    aggregate_after_handover
874                        .create_decryption_share_simple(
875                            &ciphertext.header().unwrap(),
876                            AAD,
877                            keypair,
878                            index as u32,
879                        )
880                        .unwrap()
881                })
882                // We take only the first `security_threshold` decryption shares
883                .take(dkg.dkg_params.security_threshold() as usize)
884                .collect();
885
886        let lagrange = ferveo_tdec::prepare_combine_simple::<E>(
887            &dkg.domain_points()[..security_threshold as usize],
888        );
889        let new_shared_secret = ferveo_tdec::share_combine_simple::<E>(
890            &decryption_shares[..security_threshold as usize],
891            &lagrange,
892        );
893        assert_eq!(old_shared_secret, new_shared_secret);
894    }
895}