scicrypt_he/threshold_cryptosystems/
paillier.rs

1use rug::Integer;
2use scicrypt_bigint::UnsignedInteger;
3use scicrypt_numbertheory::gen_safe_prime;
4use scicrypt_traits::cryptosystems::{Associable, EncryptionKey};
5use scicrypt_traits::homomorphic::HomomorphicAddition;
6use scicrypt_traits::randomness::GeneralRng;
7use scicrypt_traits::randomness::SecureRng;
8use scicrypt_traits::security::BitsOfSecurity;
9use scicrypt_traits::threshold_cryptosystems::{
10    DecryptionShare, PartialDecryptionKey, TOfNCryptosystem,
11};
12use scicrypt_traits::DecryptionError;
13use std::ops::Rem;
14
15use crate::cryptosystems::paillier::PaillierCiphertext;
16
17/// Threshold Paillier cryptosystem: Extension of Paillier that requires t out of n parties to
18/// successfully decrypt.
19#[derive(Copy, Clone)]
20pub struct ThresholdPaillier {
21    modulus_size: u32,
22}
23
24/// The public key for encryption.
25#[derive(PartialEq, Eq, Debug)]
26pub struct ThresholdPaillierPK {
27    generator: UnsignedInteger,
28    modulus: UnsignedInteger,
29    theta: UnsignedInteger,
30    delta: UnsignedInteger,
31}
32
33/// One of the partial keys, of which t must be used to decrypt successfully.
34pub struct ThresholdPaillierSK {
35    id: i32,
36    key: UnsignedInteger,
37}
38
39/// A partially decrypted ciphertext, of which t must be combined to decrypt successfully.
40pub struct ThresholdPaillierShare {
41    id: i32,
42    share: UnsignedInteger,
43}
44
45impl TOfNCryptosystem for ThresholdPaillier {
46    type PublicKey = ThresholdPaillierPK;
47    type SecretKey = ThresholdPaillierSK;
48
49    fn setup(security_param: &BitsOfSecurity) -> Self {
50        ThresholdPaillier {
51            modulus_size: security_param.to_public_key_bit_length(),
52        }
53    }
54
55    fn generate_keys<R: SecureRng>(
56        &self,
57        threshold_t: usize,
58        key_count_n: usize,
59        rng: &mut GeneralRng<R>,
60    ) -> (ThresholdPaillierPK, Vec<ThresholdPaillierSK>) {
61        let prime_p = gen_safe_prime(self.modulus_size / 2, rng);
62        let prime_q = gen_safe_prime(self.modulus_size / 2, rng);
63
64        let subprime_p = &prime_p >> 1;
65        let subprime_q = &prime_q >> 1;
66
67        let modulus = &prime_p * &prime_q;
68        let sub_modulus = &subprime_p * &subprime_q;
69
70        let generator = modulus.clone() + 1;
71
72        let beta = UnsignedInteger::random_below(&modulus, rng);
73        let theta = (&sub_modulus * &beta) % &modulus;
74        let delta = UnsignedInteger::factorial_leaky(key_count_n as u64);
75
76        let m_times_n = &sub_modulus * &modulus;
77        let coefficients: Vec<UnsignedInteger> = (0..(threshold_t - 1))
78            .map(|_| UnsignedInteger::random_below(&m_times_n, rng))
79            .collect();
80
81        let partial_keys: Vec<ThresholdPaillierSK> = (1..=key_count_n)
82            .map(|i| {
83                let mut key = &beta * &sub_modulus;
84
85                for j in 0..(threshold_t - 1) {
86                    key += &((&coefficients[j as usize]
87                        * &UnsignedInteger::from(i.pow((j + 1) as u32) as u64))
88                        % &m_times_n);
89                }
90
91                ThresholdPaillierSK {
92                    id: i as i32,
93                    key: key % &m_times_n,
94                }
95            })
96            .collect();
97
98        (
99            ThresholdPaillierPK {
100                generator,
101                modulus,
102                theta,
103                delta,
104            },
105            partial_keys,
106        )
107    }
108}
109
110impl Associable<ThresholdPaillierPK> for PaillierCiphertext {}
111
112impl EncryptionKey for ThresholdPaillierPK {
113    type Input = UnsignedInteger;
114    type Plaintext = UnsignedInteger;
115    type Ciphertext = PaillierCiphertext;
116    type Randomness = UnsignedInteger;
117
118    fn encrypt_without_randomness(&self, plaintext: &Self::Plaintext) -> Self::Ciphertext {
119        let n_squared = self.modulus.square();
120        PaillierCiphertext {
121            c: self.generator.pow_mod(plaintext, &n_squared),
122        }
123    }
124
125    fn randomize<R: SecureRng>(
126        &self,
127        ciphertext: PaillierCiphertext,
128        rng: &mut GeneralRng<R>,
129    ) -> PaillierCiphertext
130    where
131        Self: Sized,
132    {
133        let n_squared = self.modulus.square();
134        let r = UnsignedInteger::random_below(&n_squared, rng);
135
136        self.randomize_with(ciphertext, &r)
137    }
138
139    fn randomize_with(
140        &self,
141        ciphertext: Self::Ciphertext,
142        randomness: &Self::Randomness,
143    ) -> Self::Ciphertext {
144        let n_squared = self.modulus.square();
145        let randomizer = randomness.pow_mod(&self.modulus, &n_squared);
146
147        PaillierCiphertext {
148            c: (&ciphertext.c * &randomizer) % &n_squared,
149        }
150    }
151}
152
153impl HomomorphicAddition for ThresholdPaillierPK {
154    fn add(
155        &self,
156        ciphertext_a: &Self::Ciphertext,
157        ciphertext_b: &Self::Ciphertext,
158    ) -> Self::Ciphertext {
159        PaillierCiphertext {
160            c: (&ciphertext_a.c * &ciphertext_b.c) % &self.modulus.square(),
161        }
162    }
163
164    fn mul_constant(&self, ciphertext: &Self::Ciphertext, input: &Self::Input) -> Self::Ciphertext {
165        let modulus = self.modulus.square();
166
167        PaillierCiphertext {
168            c: ciphertext.c.pow_mod(input, &modulus),
169        }
170    }
171
172    fn sub(
173        &self,
174        ciphertext_a: &Self::Ciphertext,
175        ciphertext_b: &Self::Ciphertext,
176    ) -> Self::Ciphertext {
177        let modulus = self.modulus.square();
178        PaillierCiphertext {
179            c: (&ciphertext_a.c * &ciphertext_b.c.clone().invert(&modulus).unwrap()) % &modulus,
180        }
181    }
182
183    fn add_constant(
184        &self,
185        ciphertext: &Self::Ciphertext,
186        constant: &Self::Plaintext,
187    ) -> Self::Ciphertext {
188        let modulus = self.modulus.square();
189        PaillierCiphertext {
190            c: (&ciphertext.c * &self.generator.pow_mod(constant, &modulus)) % &modulus,
191        }
192    }
193
194    fn sub_constant(
195        &self,
196        ciphertext: &Self::Ciphertext,
197        constant: &Self::Plaintext,
198    ) -> Self::Ciphertext {
199        let modulus = self.modulus.square();
200        PaillierCiphertext {
201            c: (&ciphertext.c
202                * &self
203                    .generator
204                    .pow_mod(constant, &modulus)
205                    .invert(&modulus)
206                    .unwrap())
207                % &modulus,
208        }
209    }
210}
211
212impl PartialDecryptionKey<ThresholdPaillierPK> for ThresholdPaillierSK {
213    type DecryptionShare = ThresholdPaillierShare;
214
215    fn partial_decrypt_raw(
216        &self,
217        public_key: &ThresholdPaillierPK,
218        ciphertext: &PaillierCiphertext,
219    ) -> ThresholdPaillierShare {
220        let n_squared = public_key.modulus.square();
221        ThresholdPaillierShare {
222            id: self.id,
223            share: ciphertext.c.pow_mod(
224                &(&(&UnsignedInteger::new(2, 2) * &public_key.delta) * &self.key),
225                &n_squared,
226            ),
227        }
228    }
229}
230
231impl DecryptionShare<ThresholdPaillierPK> for ThresholdPaillierShare {
232    fn combine(
233        decryption_shares: &[Self],
234        public_key: &ThresholdPaillierPK,
235    ) -> Result<UnsignedInteger, DecryptionError> {
236        let lambdas: Vec<Integer> = (0..decryption_shares.len())
237            .map(|i| {
238                let mut lambda = public_key.delta.clone().to_rug();
239
240                for i_prime in 0..decryption_shares.len() {
241                    if i == i_prime {
242                        continue;
243                    }
244
245                    if decryption_shares[i].id == decryption_shares[i_prime].id {
246                        continue;
247                    }
248
249                    lambda *= decryption_shares[i_prime].id;
250                    lambda /= decryption_shares[i_prime].id - decryption_shares[i].id;
251                }
252
253                lambda
254            })
255            .collect();
256
257        let n_squared = public_key.modulus.square();
258
259        let mut product = UnsignedInteger::from(1u64);
260
261        for (share, lambda) in decryption_shares.iter().zip(lambdas) {
262            let lambda_is_negative = lambda < 0;
263
264            let mut part = share
265                .share
266                .pow_mod(&(UnsignedInteger::from(lambda.abs() * 2u64)), &n_squared);
267
268            if lambda_is_negative {
269                part = part.invert(&n_squared).unwrap();
270            }
271
272            product = (&product * &part) % &n_squared;
273        }
274
275        let inverse = (&(&UnsignedInteger::from(4u64) * &public_key.delta.square())
276            * &public_key.theta)
277            .rem(&public_key.modulus)
278            .invert(&public_key.modulus)
279            .unwrap();
280
281        product -= 1;
282
283        Result::Ok((&(product / &public_key.modulus) * &inverse) % &public_key.modulus)
284    }
285}
286
287#[cfg(test)]
288mod tests {
289    use crate::threshold_cryptosystems::paillier::{ThresholdPaillier, ThresholdPaillierShare};
290    use rand_core::OsRng;
291    use scicrypt_bigint::UnsignedInteger;
292    use scicrypt_traits::cryptosystems::{Associable, EncryptionKey};
293    use scicrypt_traits::randomness::GeneralRng;
294    use scicrypt_traits::security::BitsOfSecurity;
295    use scicrypt_traits::threshold_cryptosystems::{
296        DecryptionShare, PartialDecryptionKey, TOfNCryptosystem,
297    };
298
299    #[test]
300    fn test_encrypt_decrypt_2_of_3() {
301        let mut rng = GeneralRng::new(OsRng);
302
303        let paillier = ThresholdPaillier::setup(&BitsOfSecurity::ToyParameters);
304        let (pk, sks) = paillier.generate_keys(2, 3, &mut rng);
305
306        let ciphertext = pk.encrypt(&UnsignedInteger::from(19u64), &mut rng);
307
308        let share_1 = sks[0].partial_decrypt(&ciphertext);
309        let share_3 = sks[2].partial_decrypt(&ciphertext);
310
311        assert_eq!(
312            UnsignedInteger::from(19u64),
313            ThresholdPaillierShare::combine(&[share_1, share_3], &pk).unwrap()
314        );
315    }
316    #[test]
317    fn test_randomize() {
318        let mut rng = GeneralRng::new(OsRng);
319
320        let paillier = ThresholdPaillier::setup(&BitsOfSecurity::ToyParameters);
321        let (pk, sks) = paillier.generate_keys(2, 3, &mut rng);
322
323        let ciphertext = pk.encrypt_raw(&UnsignedInteger::from(42), &mut rng);
324        let ciphertext_randomized = pk.randomize(ciphertext.clone(), &mut rng);
325
326        assert_ne!(ciphertext, ciphertext_randomized);
327
328        let ciphertext_associated = ciphertext_randomized.associate(&pk);
329        let share_1 = sks[0].partial_decrypt(&ciphertext_associated);
330        let share_3 = sks[2].partial_decrypt(&ciphertext_associated);
331
332        assert_eq!(
333            UnsignedInteger::from(42),
334            ThresholdPaillierShare::combine(&[share_1, share_3], &pk).unwrap()
335        );
336    }
337}