scicrypt_he/cryptosystems/
integer_el_gamal.rs

1//! Here is an example of how to generates a key pair and encrypt a plaintext integer using the ElGamal public key.
2//! ```
3//! use scicrypt_traits::randomness::GeneralRng;
4//! use scicrypt_he::cryptosystems::integer_el_gamal::IntegerElGamal;
5//! use scicrypt_traits::security::BitsOfSecurity;
6//! use scicrypt_traits::cryptosystems::{AsymmetricCryptosystem, EncryptionKey};
7//! use rand_core::OsRng;
8//! use scicrypt_bigint::UnsignedInteger;
9//!
10//! let mut rng = GeneralRng::new(OsRng);
11//! let el_gamal = IntegerElGamal::setup(&Default::default());
12//! let (public_key, secret_key) = el_gamal.generate_keys(&mut rng);
13//! let ciphertext = public_key.encrypt(&UnsignedInteger::from(5), &mut rng);
14//! ```
15
16use crate::constants::{SAFE_PRIME_1024, SAFE_PRIME_2048, SAFE_PRIME_3072};
17use scicrypt_bigint::UnsignedInteger;
18use scicrypt_traits::cryptosystems::{
19    Associable, AsymmetricCryptosystem, DecryptionKey, EncryptionKey,
20};
21use scicrypt_traits::homomorphic::HomomorphicMultiplication;
22use scicrypt_traits::randomness::GeneralRng;
23use scicrypt_traits::randomness::SecureRng;
24use scicrypt_traits::security::BitsOfSecurity;
25use serde::{Deserialize, Serialize};
26
27/// Multiplicatively homomorphic ElGamal over a safe prime group where the generator is 4.
28///
29/// As an example we compute the product between 4 and 6 using ElGamal's homomorphic property.
30/// ```
31/// # use scicrypt_traits::randomness::GeneralRng;
32/// # use scicrypt_he::cryptosystems::integer_el_gamal::IntegerElGamal;
33/// # use scicrypt_traits::security::BitsOfSecurity;
34/// # use scicrypt_traits::cryptosystems::{AsymmetricCryptosystem, EncryptionKey, DecryptionKey};
35/// # use scicrypt_bigint::UnsignedInteger;
36/// # use rand_core::OsRng;
37/// let mut rng = GeneralRng::new(OsRng);
38/// let el_gamal = IntegerElGamal::setup(&Default::default());
39/// let (public_key, secret_key) = el_gamal.generate_keys(&mut rng);
40///
41/// let ciphertext_1 = public_key.encrypt(&UnsignedInteger::from(4), &mut rng);
42/// let ciphertext_2 = public_key.encrypt(&UnsignedInteger::from(6), &mut rng);
43///
44/// println!("[4] * [6] = [{}]", secret_key.decrypt(&(&ciphertext_1 * &ciphertext_2)));
45/// // Prints: "[4] * [6] = [24]".
46/// ```
47#[derive(Clone)]
48pub struct IntegerElGamal {
49    modulus: UnsignedInteger,
50}
51
52/// Public key containing the ElGamal encryption key and the modulus of the group.
53#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
54pub struct IntegerElGamalPK {
55    /// Generator for encrypting
56    pub h: UnsignedInteger,
57    /// Modulus of public key
58    pub modulus: UnsignedInteger,
59}
60
61/// ElGamal ciphertext of integers.
62#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
63pub struct IntegerElGamalCiphertext {
64    /// First part of ciphertext
65    pub c1: UnsignedInteger,
66    /// Second part of ciphertext
67    pub c2: UnsignedInteger,
68}
69
70impl Associable<IntegerElGamalPK> for IntegerElGamalCiphertext {}
71
72/// Decryption key for Integer-based ElGamal
73pub struct IntegerElGamalSK {
74    pub(crate) key: UnsignedInteger,
75}
76
77impl AsymmetricCryptosystem for IntegerElGamal {
78    type PublicKey = IntegerElGamalPK;
79    type SecretKey = IntegerElGamalSK;
80
81    /// Uses previously randomly generated safe primes as the modulus for pre-set modulus sizes.
82    fn setup(security_param: &BitsOfSecurity) -> Self {
83        let public_key_len = security_param.to_public_key_bit_length();
84        IntegerElGamal {
85            modulus: UnsignedInteger::from_string_leaky(
86                match public_key_len {
87                    1024 => SAFE_PRIME_1024.to_string(),
88                    2048 => SAFE_PRIME_2048.to_string(),
89                    3072 => SAFE_PRIME_3072.to_string(),
90                    _ => panic!("No parameters available for this security parameter"),
91                },
92                16,
93                public_key_len,
94            ),
95        }
96    }
97
98    /// Generates a fresh ElGamal keypair.
99    /// ```
100    /// # use scicrypt_traits::randomness::GeneralRng;
101    /// # use scicrypt_traits::security::BitsOfSecurity;
102    /// # use scicrypt_he::cryptosystems::integer_el_gamal::IntegerElGamal;
103    /// # use scicrypt_traits::cryptosystems::AsymmetricCryptosystem;
104    /// # use rand_core::OsRng;
105    /// # let mut rng = GeneralRng::new(OsRng);
106    /// let el_gamal = IntegerElGamal::setup(&Default::default());
107    /// let (public_key, secret_key) = el_gamal.generate_keys(&mut rng);
108    /// ```
109    fn generate_keys<R: SecureRng>(
110        &self,
111        rng: &mut GeneralRng<R>,
112    ) -> (IntegerElGamalPK, IntegerElGamalSK) {
113        let q = &self.modulus >> 1;
114        let secret_key = UnsignedInteger::random_below(&q, rng);
115        let public_key = UnsignedInteger::from(4u64).pow_mod(&secret_key, &self.modulus);
116
117        (
118            IntegerElGamalPK {
119                h: public_key,
120                modulus: self.modulus.clone(),
121            },
122            IntegerElGamalSK { key: secret_key },
123        )
124    }
125}
126
127impl EncryptionKey for IntegerElGamalPK {
128    type Input = UnsignedInteger;
129    type Plaintext = UnsignedInteger;
130    type Ciphertext = IntegerElGamalCiphertext;
131    type Randomness = UnsignedInteger;
132
133    fn encrypt_without_randomness(&self, plaintext: &Self::Plaintext) -> Self::Ciphertext {
134        IntegerElGamalCiphertext {
135            c1: UnsignedInteger::new(1, 1),
136            c2: plaintext.clone() % &self.modulus,
137        }
138    }
139
140    fn randomize<R: SecureRng>(
141        &self,
142        ciphertext: Self::Ciphertext,
143        rng: &mut GeneralRng<R>,
144    ) -> Self::Ciphertext {
145        let q = &self.modulus >> 1;
146        let y = UnsignedInteger::random_below(&q, rng);
147
148        self.randomize_with(ciphertext, &y)
149    }
150
151    fn randomize_with(
152        &self,
153        ciphertext: Self::Ciphertext,
154        randomness: &Self::Randomness,
155    ) -> Self::Ciphertext {
156        IntegerElGamalCiphertext {
157            c1: &ciphertext.c1 * &UnsignedInteger::from(4u64).pow_mod(randomness, &self.modulus),
158            c2: (&ciphertext.c2 * &self.h.pow_mod(randomness, &self.modulus)) % &self.modulus,
159        }
160    }
161}
162
163impl DecryptionKey<IntegerElGamalPK> for IntegerElGamalSK {
164    /// Decrypts an ElGamal ciphertext using the secret key.
165    /// ```
166    /// # use scicrypt_traits::randomness::GeneralRng;
167    /// # use scicrypt_he::cryptosystems::integer_el_gamal::IntegerElGamal;
168    /// # use scicrypt_traits::security::BitsOfSecurity;
169    /// # use scicrypt_traits::cryptosystems::{AsymmetricCryptosystem, EncryptionKey, DecryptionKey};
170    /// # use scicrypt_bigint::UnsignedInteger;
171    /// # use rand_core::OsRng;
172    /// # let mut rng = GeneralRng::new(OsRng);
173    /// # let el_gamal = IntegerElGamal::setup(&Default::default());
174    /// # let (public_key, secret_key) = el_gamal.generate_keys(&mut rng);
175    /// # let ciphertext = public_key.encrypt(&UnsignedInteger::from(5), &mut rng);
176    /// println!("The decrypted message is {}", secret_key.decrypt(&ciphertext));
177    /// // Prints: "The decrypted message is 5".
178    /// ```
179    fn decrypt_raw(
180        &self,
181        public_key: &IntegerElGamalPK,
182        ciphertext: &IntegerElGamalCiphertext,
183    ) -> UnsignedInteger {
184        (&ciphertext.c2
185            * &ciphertext
186                .c1
187                .pow_mod(&self.key, &public_key.modulus)
188                .invert(&public_key.modulus)
189                .unwrap())
190            % &public_key.modulus
191    }
192
193    fn decrypt_identity_raw(
194        &self,
195        public_key: &IntegerElGamalPK,
196        ciphertext: &<IntegerElGamalPK as EncryptionKey>::Ciphertext,
197    ) -> bool {
198        ciphertext.c2 == ciphertext.c1.pow_mod(&self.key, &public_key.modulus)
199    }
200}
201
202impl HomomorphicMultiplication for IntegerElGamalPK {
203    fn mul(
204        &self,
205        ciphertext_a: &Self::Ciphertext,
206        ciphertext_b: &Self::Ciphertext,
207    ) -> Self::Ciphertext {
208        IntegerElGamalCiphertext {
209            c1: (&ciphertext_a.c1 * &ciphertext_b.c1) % &self.modulus,
210            c2: (&ciphertext_a.c2 * &ciphertext_b.c2) % &self.modulus,
211        }
212    }
213
214    fn pow(&self, ciphertext: &Self::Ciphertext, input: &Self::Input) -> Self::Ciphertext {
215        IntegerElGamalCiphertext {
216            c1: ciphertext.c1.pow_mod(input, &self.modulus),
217            c2: ciphertext.c2.pow_mod(input, &self.modulus),
218        }
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use crate::cryptosystems::integer_el_gamal::IntegerElGamal;
225    use rand_core::OsRng;
226    use scicrypt_bigint::UnsignedInteger;
227    use scicrypt_traits::cryptosystems::{
228        Associable, AsymmetricCryptosystem, DecryptionKey, EncryptionKey,
229    };
230    use scicrypt_traits::randomness::GeneralRng;
231
232    #[test]
233    fn test_encrypt_decrypt_generator() {
234        let mut rng = GeneralRng::new(OsRng);
235
236        let el_gamal = IntegerElGamal::setup(&Default::default());
237        let (pk, sk) = el_gamal.generate_keys(&mut rng);
238
239        let ciphertext = pk.encrypt(&UnsignedInteger::from(19u64), &mut rng);
240
241        assert_eq!(UnsignedInteger::from(19u64), sk.decrypt(&ciphertext));
242    }
243
244    #[test]
245    fn test_encrypt_decrypt_identity() {
246        let mut rng = GeneralRng::new(OsRng);
247
248        let el_gamal = IntegerElGamal::setup(&Default::default());
249        let (pk, sk) = el_gamal.generate_keys(&mut rng);
250
251        let ciphertext = pk.encrypt(&UnsignedInteger::from(1), &mut rng);
252
253        assert!(sk.decrypt_identity(&ciphertext));
254    }
255
256    #[test]
257    fn test_homomorphic_mul() {
258        let mut rng = GeneralRng::new(OsRng);
259
260        let el_gamal = IntegerElGamal::setup(&Default::default());
261        let (pk, sk) = el_gamal.generate_keys(&mut rng);
262
263        let ciphertext_a = pk.encrypt(&UnsignedInteger::from(7u64), &mut rng);
264        let ciphertext_b = pk.encrypt(&UnsignedInteger::from(7u64), &mut rng);
265        let ciphertext_twice = &ciphertext_a * &ciphertext_b;
266
267        assert_eq!(UnsignedInteger::from(49u64), sk.decrypt(&ciphertext_twice));
268    }
269
270    #[test]
271    fn test_homomorphic_scalar_pow() {
272        let mut rng = GeneralRng::new(OsRng);
273
274        let el_gamal = IntegerElGamal::setup(&Default::default());
275        let (pk, sk) = el_gamal.generate_keys(&mut rng);
276
277        let ciphertext = pk.encrypt(&UnsignedInteger::from(9u64), &mut rng);
278        let ciphertext_twice = ciphertext.pow(&UnsignedInteger::from(4u64));
279
280        assert_eq!(
281            UnsignedInteger::from(6561u64),
282            sk.decrypt(&ciphertext_twice)
283        );
284    }
285
286    #[test]
287    fn randomize() {
288        let mut rng = GeneralRng::new(OsRng);
289
290        let el_gamal = IntegerElGamal::setup(&Default::default());
291        let (pk, sk) = el_gamal.generate_keys(&mut rng);
292
293        let ciphertext = pk.encrypt_raw(&UnsignedInteger::from(15u64), &mut rng);
294        let ciphertext_randomized = pk.randomize(ciphertext.clone(), &mut rng);
295
296        assert_ne!(ciphertext, ciphertext_randomized);
297
298        assert_eq!(
299            UnsignedInteger::from(15u64),
300            sk.decrypt(&ciphertext_randomized.associate(&pk))
301        );
302    }
303}