Skip to main content

ferveo_nucypher_tdec_temp6/
lib.rs

1#![warn(rust_2018_idioms)]
2use ark_ec::pairing::Pairing;
3
4// TODO: Use explicit imports - #194
5pub mod ciphertext;
6pub mod combine;
7pub mod context;
8pub mod decryption;
9pub mod hash_to_curve;
10pub mod key_share;
11pub mod secret_box;
12
13// TODO: Only show the public API, tpke::api
14// use ciphertext::*;
15// use combine::*;
16// use context::*;
17// use decryption::*;
18// use hash_to_curve::*;
19// use key_share::*;
20// use refresh::*;
21
22pub use ciphertext::*;
23pub use combine::*;
24pub use context::*;
25pub use decryption::*;
26pub use hash_to_curve::*;
27pub use key_share::*;
28pub use secret_box::*;
29
30#[cfg(feature = "api")]
31pub mod api;
32
33#[derive(Debug, thiserror::Error)]
34pub enum Error {
35    /// Ciphertext verification failed
36    /// Refers to the check 4.4.2 in the paper: https://eprint.iacr.org/2022/898.pdf
37    #[error("Ciphertext verification failed")]
38    CiphertextVerificationFailed,
39
40    /// Decryption share verification failed
41    /// Refers to the check 4.4.4 in the paper: https://eprint.iacr.org/2022/898.pdf
42    #[error("Decryption share verification failed")]
43    DecryptionShareVerificationFailed,
44
45    /// Symmetric encryption failed"
46    #[error("Symmetric encryption failed")]
47    SymmetricEncryptionError(chacha20poly1305::aead::Error),
48
49    #[error(transparent)]
50    BincodeError(#[from] bincode::Error),
51
52    #[error(transparent)]
53    ArkSerializeError(#[from] ark_serialize::SerializationError),
54}
55
56pub type DomainPoint<E> = <E as Pairing>::ScalarField;
57pub type Result<T> = std::result::Result<T, Error>;
58
59/// Factory functions for testing
60#[cfg(any(test, feature = "test-common"))]
61pub mod test_common {
62    use std::ops::Mul;
63
64    pub use ark_bls12_381::Bls12_381 as EllipticCurve;
65    use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup};
66    pub use ark_ff::UniformRand;
67    use ark_ff::{Field, Zero};
68    use ark_poly::{
69        univariate::DensePolynomial, DenseUVPolynomial, EvaluationDomain,
70        Polynomial,
71    };
72    use itertools::izip;
73    use subproductdomain::fast_multiexp;
74
75    pub use super::*;
76
77    pub fn setup_simple<E: Pairing>(
78        shares_num: usize,
79        threshold: usize,
80        rng: &mut impl rand::Rng,
81    ) -> (
82        DkgPublicKey<E>,
83        PrivateKeyShare<E>,
84        Vec<PrivateDecryptionContextSimple<E>>,
85    ) {
86        let g = E::G1Affine::generator();
87        let h = E::G2Affine::generator();
88
89        // The dealer chooses a uniformly random polynomial f of degree t-1
90        let threshold_poly =
91            DensePolynomial::<E::ScalarField>::rand(threshold - 1, rng);
92
93        // Domain, or omega Ω
94        let fft_domain =
95            ark_poly::GeneralEvaluationDomain::<E::ScalarField>::new(
96                shares_num,
97            )
98            .unwrap();
99
100        // domain points: - ω_j in Ω
101        let domain_points = fft_domain.elements().collect::<Vec<_>>();
102
103        // `evals` are evaluations of the polynomial f over the domain, omega: f(ω_j) for ω_j in Ω
104        let evals = threshold_poly.evaluate_over_domain_by_ref(fft_domain);
105
106        // A_j, share commitments of participants:  [f(ω_j)] G
107        let share_commitments = fast_multiexp(&evals.evals, g.into_group());
108
109        // Z_j, private key shares of participants (unblinded): [f(ω_j)] H
110        // NOTE: In production, these are never produced this way, as the DKG
111        // directly generates blinded shares Y_j. Only then, node j can use their
112        // validator key to unblind Y_j and obtain the private key share Z_j.
113        let privkey_shares = fast_multiexp(&evals.evals, h.into_group());
114
115        // The shared secret is the free coefficient from threshold poly
116        let a_0 = threshold_poly.coeffs[0];
117
118        // F_0, group's public key
119        let group_pubkey = g.mul(a_0);
120
121        // group's private key (NOTE: just for tests, this is NEVER constructed in production)
122        let group_privkey = h.mul(a_0);
123
124        // As in SSS, shared secret should be f(0), which is also the free coefficient
125        let secret = threshold_poly.evaluate(&E::ScalarField::zero());
126        debug_assert!(secret == a_0);
127
128        let mut private_contexts = vec![];
129        let mut public_contexts = vec![];
130
131        // (domain_point, A, Z)
132        for (index, (domain_point, share_commit, private_share)) in izip!(
133            domain_points.iter(),
134            share_commitments.iter(),
135            privkey_shares.iter()
136        )
137        .enumerate()
138        {
139            let private_key_share = PrivateKeyShare::<E>(*private_share);
140            let blinding_factor = E::ScalarField::rand(rng);
141
142            let validator_public_key = h.mul(blinding_factor).into_affine();
143            let blinded_key_share = BlindedKeyShare::<E> {
144                validator_public_key,
145                blinded_key_share: private_key_share
146                    .0
147                    .mul(blinding_factor)
148                    .into_affine(),
149            };
150
151            private_contexts.push(PrivateDecryptionContextSimple::<E> {
152                index,
153                setup_params: SetupParams {
154                    b: blinding_factor,
155                    b_inv: blinding_factor.inverse().unwrap(),
156                    g,
157                    h_inv: E::G2Prepared::from(-h.into_group()),
158                    g_inv: E::G1Prepared::from(-g.into_group()),
159                    h,
160                },
161                private_key_share,
162                public_decryption_contexts: vec![],
163            });
164            public_contexts.push(PublicDecryptionContextSimple::<E> {
165                domain: *domain_point,
166                share_commitment: ShareCommitment::<E>(*share_commit),
167                blinded_key_share,
168                validator_public_key: ferveo_common::PublicKey {
169                    encryption_key: blinded_key_share.validator_public_key,
170                },
171            });
172        }
173        for private_ctxt in private_contexts.iter_mut() {
174            private_ctxt.public_decryption_contexts = public_contexts.clone();
175        }
176
177        (
178            DkgPublicKey(group_pubkey.into()),
179            PrivateKeyShare(group_privkey.into()), // TODO: Not the correct type since it's a DKG private key, which are never created in the protocol, but it's just for testing
180            private_contexts,
181        )
182    }
183
184    pub fn setup_precomputed<E: Pairing>(
185        shares_num: usize,
186        threshold: usize,
187        rng: &mut impl rand::Rng,
188    ) -> (
189        DkgPublicKey<E>,
190        PrivateKeyShare<E>,
191        Vec<PrivateDecryptionContextSimple<E>>,
192    ) {
193        setup_simple::<E>(shares_num, threshold, rng)
194    }
195
196    pub fn create_shared_secret_simple<E: Pairing>(
197        pub_contexts: &[PublicDecryptionContextSimple<E>],
198        decryption_shares: &[DecryptionShareSimple<E>],
199    ) -> SharedSecret<E> {
200        let domain = pub_contexts.iter().map(|c| c.domain).collect::<Vec<_>>();
201        let lagrange_coeffs = prepare_combine_simple::<E>(&domain);
202        share_combine_simple::<E>(decryption_shares, &lagrange_coeffs)
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use std::ops::Mul;
209
210    use ark_ec::{pairing::Pairing, CurveGroup};
211    use ark_std::{test_rng, UniformRand};
212    use ferveo_common::{FromBytes, ToBytes};
213    use rand::seq::IteratorRandom;
214
215    use crate::{
216        api::DecryptionSharePrecomputed,
217        test_common::{create_shared_secret_simple, setup_simple, *},
218    };
219
220    type E = ark_bls12_381::Bls12_381;
221    type TargetField = <E as Pairing>::TargetField;
222    type ScalarField = <E as Pairing>::ScalarField;
223
224    #[test]
225    fn ciphertext_serialization() {
226        let rng = &mut test_rng();
227        let shares_num = 16;
228        let threshold = shares_num * 2 / 3;
229        let msg = "my-msg".as_bytes().to_vec();
230        let aad: &[u8] = "my-aad".as_bytes();
231
232        let (pubkey, _, _) = setup_simple::<E>(threshold, shares_num, rng);
233
234        let ciphertext =
235            encrypt::<E>(SecretBox::new(msg), aad, &pubkey, rng).unwrap();
236
237        let serialized = ciphertext.to_bytes().unwrap();
238        let deserialized: Ciphertext<E> =
239            Ciphertext::from_bytes(&serialized).unwrap();
240
241        assert_eq!(serialized, deserialized.to_bytes().unwrap())
242    }
243
244    fn test_ciphertext_validation_fails<E: Pairing>(
245        msg: &[u8],
246        aad: &[u8],
247        ciphertext: &Ciphertext<E>,
248        shared_secret: &SharedSecret<E>,
249    ) {
250        // So far, the ciphertext is valid
251        let plaintext =
252            decrypt_with_shared_secret(ciphertext, aad, shared_secret).unwrap();
253        assert_eq!(plaintext, msg);
254
255        // Malformed the ciphertext
256        let mut ciphertext = ciphertext.clone();
257        ciphertext.ciphertext[0] += 1;
258        assert!(decrypt_with_shared_secret(&ciphertext, aad, shared_secret)
259            .is_err());
260
261        // Malformed the AAD
262        let aad = "bad aad".as_bytes();
263        assert!(decrypt_with_shared_secret(&ciphertext, aad, shared_secret)
264            .is_err());
265    }
266
267    #[test]
268    fn tdec_simple_variant_share_validation() {
269        let rng = &mut test_rng();
270        let shares_num = 16;
271        let threshold = shares_num * 2 / 3;
272        let msg = "my-msg".as_bytes().to_vec();
273        let aad: &[u8] = "my-aad".as_bytes();
274
275        let (pubkey, _, contexts) =
276            setup_simple::<E>(shares_num, threshold, rng);
277        let ciphertext =
278            encrypt::<E>(SecretBox::new(msg), aad, &pubkey, rng).unwrap();
279
280        let bad_aad = "bad aad".as_bytes();
281        assert!(contexts[0]
282            .create_share(&ciphertext.header().unwrap(), bad_aad)
283            .is_err());
284    }
285
286    #[test]
287    fn tdec_simple_variant_e2e() {
288        let mut rng = &mut test_rng();
289        let shares_num = 16;
290        let threshold = shares_num * 2 / 3;
291        let msg = "my-msg".as_bytes().to_vec();
292        let aad: &[u8] = "my-aad".as_bytes();
293
294        let (pubkey, _, contexts) =
295            setup_simple::<E>(shares_num, threshold, &mut rng);
296
297        let ciphertext =
298            encrypt::<E>(SecretBox::new(msg.clone()), aad, &pubkey, rng)
299                .unwrap();
300
301        // We need at least threshold shares to decrypt
302        let decryption_shares: Vec<_> = contexts
303            .iter()
304            .map(|c| {
305                c.create_share(&ciphertext.header().unwrap(), aad).unwrap()
306            })
307            .take(threshold)
308            .collect();
309        let selected_contexts =
310            contexts[0].public_decryption_contexts[..threshold].to_vec();
311        let shared_secret =
312            create_shared_secret_simple(&selected_contexts, &decryption_shares);
313
314        test_ciphertext_validation_fails(
315            &msg,
316            aad,
317            &ciphertext,
318            &shared_secret,
319        );
320
321        // If we use less than threshold shares, we should fail
322        let not_enough_dec_shares = decryption_shares[..threshold - 1].to_vec();
323        let not_enough_contexts = selected_contexts[..threshold - 1].to_vec();
324        let bash_shared_secret = create_shared_secret_simple(
325            &not_enough_contexts,
326            &not_enough_dec_shares,
327        );
328        let result =
329            decrypt_with_shared_secret(&ciphertext, aad, &bash_shared_secret);
330        assert!(result.is_err());
331    }
332
333    #[test]
334    fn tdec_precomputed_variant_e2e() {
335        let mut rng = &mut test_rng();
336        let shares_num = 16;
337        let threshold = shares_num * 2 / 3;
338        let msg = "my-msg".as_bytes().to_vec();
339        let aad: &[u8] = "my-aad".as_bytes();
340
341        let (pubkey, _, contexts) =
342            setup_precomputed::<E>(shares_num, threshold, &mut rng);
343        let ciphertext =
344            encrypt::<E>(SecretBox::new(msg.clone()), aad, &pubkey, rng)
345                .unwrap();
346
347        let selected_participants =
348            (0..threshold).choose_multiple(rng, threshold);
349        let selected_contexts = contexts
350            .iter()
351            .filter(|c| selected_participants.contains(&c.index))
352            .cloned()
353            .collect::<Vec<_>>();
354
355        let decryption_shares = selected_contexts
356            .iter()
357            .map(|context| {
358                context
359                    .create_share_precomputed(
360                        &ciphertext.header().unwrap(),
361                        aad,
362                        &selected_participants,
363                    )
364                    .unwrap()
365            })
366            .collect::<Vec<DecryptionSharePrecomputed>>();
367
368        let shared_secret = share_combine_precomputed::<E>(&decryption_shares);
369        test_ciphertext_validation_fails(
370            &msg,
371            aad,
372            &ciphertext,
373            &shared_secret,
374        );
375
376        // If we use less than threshold shares, we should fail
377        let not_enough_dec_shares = decryption_shares[..threshold - 1].to_vec();
378        let bash_shared_secret =
379            share_combine_precomputed(&not_enough_dec_shares);
380        let result =
381            decrypt_with_shared_secret(&ciphertext, aad, &bash_shared_secret);
382        assert!(result.is_err());
383    }
384
385    #[test]
386    fn tdec_simple_variant_share_verification() {
387        let mut rng = &mut test_rng();
388        let shares_num = 16;
389        let threshold = shares_num * 2 / 3;
390        let msg = "my-msg".as_bytes().to_vec();
391        let aad: &[u8] = "my-aad".as_bytes();
392
393        let (pubkey, _, contexts) =
394            setup_simple::<E>(shares_num, threshold, &mut rng);
395
396        let ciphertext =
397            encrypt::<E>(SecretBox::new(msg), aad, &pubkey, rng).unwrap();
398
399        let decryption_shares: Vec<_> = contexts
400            .iter()
401            .map(|c| {
402                c.create_share(&ciphertext.header().unwrap(), aad).unwrap()
403            })
404            .collect();
405
406        // In simple tDec variant, we verify decryption shares only after decryption fails.
407        // We could do that before, but we prefer to optimize for the happy path.
408
409        // Let's assume that combination failed here. We'll try to verify decryption shares
410        // against validator checksums.
411
412        let pub_contexts = &contexts[0].public_decryption_contexts;
413        assert!(verify_decryption_shares_simple(
414            pub_contexts,
415            &ciphertext,
416            &decryption_shares,
417        ));
418
419        // Now, let's test that verification fails if we one of the decryption shares is invalid.
420
421        let mut has_bad_checksum = decryption_shares[0].clone();
422        has_bad_checksum.validator_checksum.checksum = has_bad_checksum
423            .validator_checksum
424            .checksum
425            .mul(ScalarField::rand(rng))
426            .into_affine();
427
428        assert!(!has_bad_checksum.verify(
429            &pub_contexts[0].blinded_key_share.blinded_key_share,
430            &pub_contexts[0].validator_public_key.encryption_key,
431            &ciphertext,
432        ));
433
434        let mut has_bad_share = decryption_shares[0].clone();
435        has_bad_share.decryption_share =
436            has_bad_share.decryption_share.mul(TargetField::rand(rng));
437
438        assert!(!has_bad_share.verify(
439            &pub_contexts[0].blinded_key_share.blinded_key_share,
440            &pub_contexts[0].validator_public_key.encryption_key,
441            &ciphertext,
442        ));
443    }
444}