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 #[error("Expected validator to be a part of the DKG validator set: {0}")]
35 DealerNotInValidatorSet(EthereumAddress),
36
37 #[error("DKG received an unknown dealer: {0}")]
39 UnknownDealer(EthereumAddress),
40
41 #[error("DKG received a PVSS transcript from a dealer that has already been dealt: {0}")]
43 DuplicateDealer(EthereumAddress),
44
45 #[error("DKG received an invalid transcript from validator: {0}")]
47 InvalidPvssTranscript(EthereumAddress),
48
49 #[error("Not enough validators (expected {0}, got {1})")]
51 InsufficientValidators(u32, u32),
52
53 #[error("Transcript aggregate doesn't match the received PVSS instances")]
55 InvalidTranscriptAggregate,
56
57 #[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 #[error("Invalid byte length. Expected {0}, got {1}")]
69 InvalidByteLength(usize, usize),
70
71 #[error("Invalid variant: {0}")]
73 InvalidVariant(String),
74
75 #[error("Invalid DKG parameters: number of shares {0}, threshold {1}")]
77 InvalidDkgParameters(u32, u32),
78
79 #[error("Invalid share index: {0}")]
81 InvalidShareIndex(u32),
82
83 #[error("Invalid share update")]
85 InvalidShareUpdate,
86
87 #[error("Invalid DKG parameters for precomputed variant: number of shares {0}, threshold {1}")]
89 InvalidDkgParametersForPrecomputedVariant(u32, u32),
90
91 #[error("Duplicated share index: {0}")]
93 DuplicatedShareIndex(u32),
94
95 #[error("No transcripts to aggregate")]
97 NoTranscriptsToAggregate,
98
99 #[error("Invalid aggregate verification parameters: number of validators {0}, number of messages: {1}")]
101 InvalidAggregateVerificationParameters(u32, u32),
102
103 #[error("Too many transcripts. Expected: {0}, got: {1}")]
105 TooManyTranscripts(u32, u32),
106
107 #[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 .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; 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; 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 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 decryption_shares.shuffle(rng);
311
312 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 let decryption_share = decryption_shares[0].clone();
380
381 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 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 #[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 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 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 let mut domain_points = dkg.domain_point_map();
458 domain_points.remove(&removed_validator_index);
459
460 let x_r = Fr::rand(rng);
465
466 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 .take((dkg.dkg_params.security_threshold() - 1) as usize)
546 .collect::<HashMap<u32, _>>();
547
548 domain_points.insert(removed_validator_index, x_r);
560
561 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 let local_aggregate =
616 AggregatedTranscript::from_transcripts(&transcripts).unwrap();
617 assert!(local_aggregate
618 .aggregate
619 .verify_aggregation(&dkg, &transcripts)
620 .unwrap());
621
622 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 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 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 let new_aggregate = local_aggregate
664 .aggregate
665 .refresh(&update_transcripts, &validator_map)
666 .unwrap();
667
668 assert_ne!(local_aggregate.aggregate, new_aggregate);
670
671 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 .take(dkg.dkg_params.security_threshold() as usize)
692 .collect();
693
694 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 let transcripts = messages
725 .iter()
726 .take(shares_num as usize)
727 .map(|m| m.1.clone())
728 .collect::<Vec<_>>();
729
730 let local_aggregate =
733 AggregatedTranscript::from_transcripts(&transcripts).unwrap();
734 assert!(local_aggregate
735 .aggregate
736 .verify_aggregation(&dkg, &transcripts)
737 .unwrap());
738
739 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 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 handover_slot_index = rng.gen_range(0..shares_num);
760 let incoming_validator_keypair = Keypair::<E>::new(rng);
765 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 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 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 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 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 assert_ne!(local_aggregate.aggregate, aggregate_after_handover);
839
840 assert_eq!(
843 local_aggregate.aggregate.coeffs,
844 aggregate_after_handover.coeffs
845 );
846 assert_ne!(
848 local_aggregate.aggregate.shares,
849 aggregate_after_handover.shares
850 );
851 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 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 .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}