ferveo_pre_release/
lib.rs

1#![warn(rust_2018_idioms)]
2
3#[cfg(feature = "bindings-wasm")]
4extern crate alloc;
5
6use ark_ec::pairing::Pairing;
7use group_threshold_cryptography as tpke;
8use itertools::zip_eq;
9
10#[cfg(feature = "bindings-python")]
11pub mod bindings_python;
12
13#[cfg(feature = "bindings-wasm")]
14pub mod bindings_wasm;
15
16pub mod api;
17pub mod dkg;
18pub mod primitives;
19pub mod pvss;
20pub mod validator;
21
22mod utils;
23
24pub use dkg::*;
25pub use primitives::*;
26pub use pvss::*;
27pub use validator::*;
28
29#[derive(Debug, thiserror::Error)]
30pub enum Error {
31    #[error(transparent)]
32    ThresholdEncryptionError(#[from] tpke::Error),
33
34    /// DKG is not in a valid state to deal PVSS shares
35    #[error("Invalid DKG state to deal PVSS shares")]
36    InvalidDkgStateToDeal,
37
38    /// DKG is not in a valid state to aggregate PVSS transcripts
39    #[error("Invalid DKG state to aggregate PVSS transcripts")]
40    InvalidDkgStateToAggregate,
41
42    /// DKG is not in a valid state to verify PVSS transcripts
43    #[error("Invalid DKG state to verify PVSS transcripts")]
44    InvalidDkgStateToVerify,
45
46    /// DKG is not in a valid state to ingest PVSS transcripts
47    #[error("Invalid DKG state to ingest PVSS transcripts")]
48    InvalidDkgStateToIngest,
49
50    /// DKG validator set must contain the validator with the given address
51    #[error("Expected validator to be a part of the DKG validator set: {0}")]
52    DealerNotInValidatorSet(EthereumAddress),
53
54    /// DKG received an unknown dealer. Dealer must be the part of the DKG validator set.
55    #[error("DKG received an unknown dealer: {0}")]
56    UnknownDealer(EthereumAddress),
57
58    /// DKG received a PVSS transcript from a dealer that has already been dealt.
59    #[error("DKG received a PVSS transcript from a dealer that has already been dealt: {0}")]
60    DuplicateDealer(EthereumAddress),
61
62    /// DKG received an invalid transcript for which optimistic verification failed
63    #[error("DKG received an invalid transcript")]
64    InvalidPvssTranscript,
65
66    /// Aggregation failed because the DKG did not receive enough PVSS transcripts
67    #[error(
68        "Insufficient transcripts for aggregation (expected {0}, got {1})"
69    )]
70    InsufficientTranscriptsForAggregate(u32, u32),
71
72    /// Failed to derive a valid final key for the DKG
73    #[error("Failed to derive a valid final key for the DKG")]
74    InvalidDkgPublicKey,
75
76    /// Not enough validators to perform the DKG for a given number of shares
77    #[error("Not enough validators (expected {0}, got {1})")]
78    InsufficientValidators(u32, u32),
79
80    /// Transcript aggregate doesn't match the received PVSS instances
81    #[error("Transcript aggregate doesn't match the received PVSS instances")]
82    InvalidTranscriptAggregate,
83
84    /// DKG validators must be sorted by their Ethereum address
85    #[error("DKG validators not sorted")]
86    ValidatorsNotSorted,
87
88    /// The validator public key doesn't match the one in the DKG
89    #[error("Validator public key mismatch")]
90    ValidatorPublicKeyMismatch,
91
92    #[error(transparent)]
93    BincodeError(#[from] bincode::Error),
94
95    #[error(transparent)]
96    ArkSerializeError(#[from] ark_serialize::SerializationError),
97
98    #[error("Invalid byte length. Expected {0}, got {1}")]
99    InvalidByteLength(usize, usize),
100
101    #[error("Invalid variant: {0}")]
102    InvalidVariant(String),
103}
104
105pub type Result<T> = std::result::Result<T, Error>;
106
107pub fn make_pvss_map<E: Pairing>(
108    transcripts: &[PubliclyVerifiableSS<E>],
109    validators: &[Validator<E>],
110) -> PVSSMap<E> {
111    let mut pvss_map: PVSSMap<E> = PVSSMap::new();
112    zip_eq(transcripts, validators).for_each(|(transcript, validator)| {
113        pvss_map.insert(validator.address.clone(), transcript.clone());
114    });
115    pvss_map
116}
117
118#[cfg(test)]
119mod test_dkg_full {
120    use std::collections::HashMap;
121
122    use ark_bls12_381::{Bls12_381 as E, Fr, G1Affine};
123    use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup};
124    use ark_ff::{UniformRand, Zero};
125    use ark_poly::EvaluationDomain;
126    use ark_std::test_rng;
127    use ferveo_common::Keypair;
128    use group_threshold_cryptography as tpke;
129    use group_threshold_cryptography::{
130        DecryptionSharePrecomputed, DecryptionShareSimple, SecretBox,
131        SharedSecret,
132    };
133    use itertools::izip;
134
135    use super::*;
136    use crate::dkg::test_common::*;
137
138    type TargetField = <E as Pairing>::TargetField;
139
140    fn make_shared_secret_simple_tdec(
141        dkg: &PubliclyVerifiableDkg<E>,
142        aad: &[u8],
143        ciphertext_header: &tpke::CiphertextHeader<E>,
144        validator_keypairs: &[Keypair<E>],
145    ) -> (
146        PubliclyVerifiableSS<E, Aggregated>,
147        Vec<DecryptionShareSimple<E>>,
148        SharedSecret<E>,
149    ) {
150        let pvss_aggregated = aggregate(&dkg.vss);
151        assert!(pvss_aggregated.verify_aggregation(dkg).is_ok());
152
153        let decryption_shares: Vec<DecryptionShareSimple<E>> =
154            validator_keypairs
155                .iter()
156                .map(|validator_keypair| {
157                    let validator = dkg
158                        .get_validator(&validator_keypair.public_key())
159                        .unwrap();
160                    pvss_aggregated
161                        .make_decryption_share_simple(
162                            ciphertext_header,
163                            aad,
164                            &validator_keypair.decryption_key,
165                            validator.share_index,
166                            &dkg.pvss_params.g_inv(),
167                        )
168                        .unwrap()
169                })
170                .collect();
171
172        let domain_points = &dkg
173            .domain
174            .elements()
175            .take(decryption_shares.len())
176            .collect::<Vec<_>>();
177        assert_eq!(domain_points.len(), decryption_shares.len());
178
179        // TODO: Consider refactor this part into tpke::combine_simple and expose it
180        //  as a public API in tpke::api
181
182        let lagrange_coeffs = tpke::prepare_combine_simple::<E>(domain_points);
183        let shared_secret = tpke::share_combine_simple::<E>(
184            &decryption_shares,
185            &lagrange_coeffs,
186        );
187
188        (pvss_aggregated, decryption_shares, shared_secret)
189    }
190
191    #[test]
192    fn test_dkg_simple_tdec() {
193        let rng = &mut test_rng();
194
195        // Works for both power of 2 and non-power of 2
196        for shares_num in [4, 7] {
197            let threshold = shares_num / 2 + 1;
198            let (dkg, validator_keypairs) =
199                setup_dealt_dkg_with_n_validators(threshold, shares_num);
200            let msg = "my-msg".as_bytes().to_vec();
201            let aad: &[u8] = "my-aad".as_bytes();
202            let public_key = dkg.public_key();
203            let ciphertext = tpke::encrypt::<E>(
204                SecretBox::new(msg.clone()),
205                aad,
206                &public_key,
207                rng,
208            )
209            .unwrap();
210
211            let (_, _, shared_secret) = make_shared_secret_simple_tdec(
212                &dkg,
213                aad,
214                &ciphertext.header().unwrap(),
215                &validator_keypairs,
216            );
217
218            let plaintext = tpke::decrypt_with_shared_secret(
219                &ciphertext,
220                aad,
221                &shared_secret,
222                &dkg.pvss_params.g_inv(),
223            )
224            .unwrap();
225            assert_eq!(plaintext, msg);
226        }
227    }
228
229    #[test]
230    fn test_dkg_simple_tdec_precomputed() {
231        let rng = &mut test_rng();
232
233        // Works for both power of 2 and non-power of 2
234        for shares_num in [4, 7] {
235            // In precomputed variant, threshold must be equal to shares_num
236            let threshold = shares_num;
237            let (dkg, validator_keypairs) =
238                setup_dealt_dkg_with_n_validators(threshold, shares_num);
239            let msg = "my-msg".as_bytes().to_vec();
240            let aad: &[u8] = "my-aad".as_bytes();
241            let public_key = dkg.public_key();
242            let ciphertext = tpke::encrypt::<E>(
243                SecretBox::new(msg.clone()),
244                aad,
245                &public_key,
246                rng,
247            )
248            .unwrap();
249
250            let pvss_aggregated = aggregate(&dkg.vss);
251            pvss_aggregated.verify_aggregation(&dkg).unwrap();
252            let domain_points = dkg
253                .domain
254                .elements()
255                .take(validator_keypairs.len())
256                .collect::<Vec<_>>();
257
258            let decryption_shares: Vec<DecryptionSharePrecomputed<E>> =
259                validator_keypairs
260                    .iter()
261                    .map(|validator_keypair| {
262                        let validator = dkg
263                            .get_validator(&validator_keypair.public_key())
264                            .unwrap();
265                        pvss_aggregated
266                            .make_decryption_share_simple_precomputed(
267                                &ciphertext.header().unwrap(),
268                                aad,
269                                &validator_keypair.decryption_key,
270                                validator.share_index,
271                                &domain_points,
272                                &dkg.pvss_params.g_inv(),
273                            )
274                            .unwrap()
275                    })
276                    .collect();
277            assert_eq!(domain_points.len(), decryption_shares.len());
278
279            let shared_secret =
280                tpke::share_combine_precomputed::<E>(&decryption_shares);
281
282            // Combination works, let's decrypt
283            let plaintext = tpke::decrypt_with_shared_secret(
284                &ciphertext,
285                aad,
286                &shared_secret,
287                &dkg.pvss_params.g_inv(),
288            )
289            .unwrap();
290            assert_eq!(plaintext, msg);
291        }
292    }
293
294    #[test]
295    fn test_dkg_simple_tdec_share_verification() {
296        let rng = &mut test_rng();
297
298        let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(3, 4);
299        let msg = "my-msg".as_bytes().to_vec();
300        let aad: &[u8] = "my-aad".as_bytes();
301        let public_key = dkg.public_key();
302        let ciphertext =
303            tpke::encrypt::<E>(SecretBox::new(msg), aad, &public_key, rng)
304                .unwrap();
305
306        let (pvss_aggregated, decryption_shares, _) =
307            make_shared_secret_simple_tdec(
308                &dkg,
309                aad,
310                &ciphertext.header().unwrap(),
311                &validator_keypairs,
312            );
313
314        izip!(
315            &pvss_aggregated.shares,
316            &validator_keypairs,
317            &decryption_shares,
318        )
319        .for_each(
320            |(aggregated_share, validator_keypair, decryption_share)| {
321                assert!(decryption_share.verify(
322                    aggregated_share,
323                    &validator_keypair.public_key().encryption_key,
324                    &dkg.pvss_params.h,
325                    &ciphertext,
326                ));
327            },
328        );
329
330        // Testing red-path decryption share verification
331        let decryption_share = decryption_shares[0].clone();
332
333        // Should fail because of the bad decryption share
334        let mut with_bad_decryption_share = decryption_share.clone();
335        with_bad_decryption_share.decryption_share = TargetField::zero();
336        assert!(!with_bad_decryption_share.verify(
337            &pvss_aggregated.shares[0],
338            &validator_keypairs[0].public_key().encryption_key,
339            &dkg.pvss_params.h,
340            &ciphertext,
341        ));
342
343        // Should fail because of the bad checksum
344        let mut with_bad_checksum = decryption_share;
345        with_bad_checksum.validator_checksum.checksum = G1Affine::zero();
346        assert!(!with_bad_checksum.verify(
347            &pvss_aggregated.shares[0],
348            &validator_keypairs[0].public_key().encryption_key,
349            &dkg.pvss_params.h,
350            &ciphertext,
351        ));
352    }
353
354    #[test]
355    fn test_dkg_simple_tdec_share_recovery() {
356        let rng = &mut test_rng();
357
358        let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(3, 4);
359        let msg = "my-msg".as_bytes().to_vec();
360        let aad: &[u8] = "my-aad".as_bytes();
361        let public_key = &dkg.public_key();
362        let ciphertext =
363            tpke::encrypt::<E>(SecretBox::new(msg), aad, public_key, rng)
364                .unwrap();
365
366        // Create an initial shared secret
367        let (_, _, old_shared_secret) = make_shared_secret_simple_tdec(
368            &dkg,
369            aad,
370            &ciphertext.header().unwrap(),
371            &validator_keypairs,
372        );
373
374        // Now, we're going to recover a new share at a random point and check that the shared secret is still the same
375
376        // Our random point
377        let x_r = Fr::rand(rng);
378
379        // Remove one participant from the contexts and all nested structure
380        let removed_validator_addr =
381            dkg.validators.keys().last().unwrap().clone();
382        let mut remaining_validators = dkg.validators.clone();
383        remaining_validators.remove(&removed_validator_addr);
384
385        // Remember to remove one domain point too
386        let mut domain_points = dkg.domain.elements().collect::<Vec<_>>();
387        domain_points.pop().unwrap();
388
389        // Each participant prepares an update for each other participant
390        let share_updates = remaining_validators
391            .keys()
392            .map(|v_addr| {
393                let deltas_i = tpke::prepare_share_updates_for_recovery::<E>(
394                    &domain_points,
395                    &dkg.pvss_params.h.into_affine(),
396                    &x_r,
397                    dkg.dkg_params.security_threshold as usize,
398                    rng,
399                );
400                (v_addr.clone(), deltas_i)
401            })
402            .collect::<HashMap<_, _>>();
403
404        // Participants share updates and update their shares
405        let pvss_aggregated = aggregate(&dkg.vss);
406
407        // Now, every participant separately:
408        let updated_shares: Vec<_> = remaining_validators
409            .iter()
410            .map(|(validator_address, validator)| {
411                // Receives updates from other participants
412                let updates_for_participant =
413                    share_updates.get(validator_address).unwrap();
414
415                // Each validator uses their decryption key to update their share
416                let decryption_key = validator_keypairs
417                    .get(validator.share_index)
418                    .unwrap()
419                    .decryption_key;
420
421                // Creates updated private key shares
422                pvss_aggregated.update_private_key_share_for_recovery(
423                    &decryption_key,
424                    validator.share_index,
425                    updates_for_participant,
426                )
427            })
428            .collect();
429
430        // Now, we have to combine new share fragments into a new share
431        let new_private_key_share =
432            tpke::recover_share_from_updated_private_shares(
433                &x_r,
434                &domain_points,
435                &updated_shares,
436            );
437
438        // Get decryption shares from remaining participants
439        let mut decryption_shares: Vec<DecryptionShareSimple<E>> =
440            validator_keypairs
441                .iter()
442                .enumerate()
443                .map(|(share_index, validator_keypair)| {
444                    pvss_aggregated
445                        .make_decryption_share_simple(
446                            &ciphertext.header().unwrap(),
447                            aad,
448                            &validator_keypair.decryption_key,
449                            share_index,
450                            &dkg.pvss_params.g_inv(),
451                        )
452                        .unwrap()
453                })
454                .collect();
455
456        // Create a decryption share from a recovered private key share
457        let new_validator_decryption_key = Fr::rand(rng);
458        decryption_shares.push(
459            DecryptionShareSimple::create(
460                &new_validator_decryption_key,
461                &new_private_key_share,
462                &ciphertext.header().unwrap(),
463                aad,
464                &dkg.pvss_params.g_inv(),
465            )
466            .unwrap(),
467        );
468
469        let lagrange = tpke::prepare_combine_simple::<E>(&domain_points);
470        let new_shared_secret =
471            tpke::share_combine_simple::<E>(&decryption_shares, &lagrange);
472
473        assert_eq!(old_shared_secret, new_shared_secret);
474    }
475
476    #[test]
477    fn simple_tdec_share_refreshing() {
478        let rng = &mut test_rng();
479        let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(3, 4);
480
481        let msg = "my-msg".as_bytes().to_vec();
482        let aad: &[u8] = "my-aad".as_bytes();
483        let public_key = dkg.public_key();
484        let ciphertext =
485            tpke::encrypt::<E>(SecretBox::new(msg), aad, &public_key, rng)
486                .unwrap();
487
488        let pvss_aggregated = aggregate(&dkg.vss);
489
490        // Create an initial shared secret
491        let (_, _, old_shared_secret) = make_shared_secret_simple_tdec(
492            &dkg,
493            aad,
494            &ciphertext.header().unwrap(),
495            &validator_keypairs,
496        );
497
498        // Now, we're going to refresh the shares and check that the shared secret is the same
499
500        // Dealer computes a new random polynomial with constant term x_r = 0
501        let polynomial = tpke::make_random_polynomial_at::<E>(
502            dkg.dkg_params.security_threshold as usize,
503            &Fr::zero(),
504            rng,
505        );
506
507        // Dealer shares the polynomial with participants
508
509        // Participants computes new decryption shares
510        let new_decryption_shares: Vec<DecryptionShareSimple<E>> =
511            validator_keypairs
512                .iter()
513                .enumerate()
514                .map(|(validator_address, validator_keypair)| {
515                    pvss_aggregated
516                        .refresh_decryption_share(
517                            &ciphertext.header().unwrap(),
518                            aad,
519                            &validator_keypair.decryption_key,
520                            validator_address,
521                            &polynomial,
522                            &dkg,
523                        )
524                        .unwrap()
525                })
526                .collect();
527
528        // Create a new shared secret
529        let domain = &dkg.domain.elements().collect::<Vec<_>>();
530        // TODO: Combine `tpke::prepare_combine_simple` and `tpke::share_combine_simple` into
531        //  one function and expose it in the tpke::api?
532        let lagrange_coeffs = tpke::prepare_combine_simple::<E>(domain);
533        let new_shared_secret = tpke::share_combine_simple::<E>(
534            &new_decryption_shares,
535            &lagrange_coeffs,
536        );
537
538        assert_eq!(old_shared_secret, new_shared_secret);
539    }
540}