Skip to content

Commit 8269381

Browse files
authored
Merge pull request #57 from qsantos/pure-python
Pure Python fallback
2 parents 72c3e4d + 9395ce5 commit 8269381

File tree

5 files changed

+373
-27
lines changed

5 files changed

+373
-27
lines changed

CHANGELOG.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
Version 1.4.0 (2018-04-19)
2+
=====
3+
4+
Complete pure Python fallback implementation.
5+
6+
Features
7+
----
8+
9+
- `invert` now available without `gmpy2`, implemented using the extended
10+
Euclidean algorithm (`extended_euclidean_algorithm`)
11+
- `getprimeover` now available without `gmpy2`, along with a probabilitic
12+
primality test `isprime` based on the Miller-Rabin test (`miller_rabin`)
13+
114
Version 1.3.0 (2017-02-08)
215
=====
316

phe/paillier.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def raw_encrypt(self, plaintext, r_value=None):
125125
neg_ciphertext = (self.n * neg_plaintext + 1) % self.nsquare
126126
nude_ciphertext = invert(neg_ciphertext, self.nsquare)
127127
else:
128-
# we chose g = n + 1, so that we can exploit the fact that
128+
# we chose g = n + 1, so that we can exploit the fact that
129129
# (n+1)^plaintext = n*plaintext + 1 mod n^2
130130
nude_ciphertext = (self.n * plaintext + 1) % self.nsquare
131131

@@ -217,14 +217,14 @@ def __init__(self, public_key, p, q):
217217
# check that p and q are different, otherwise we can't compute p^-1 mod q
218218
raise ValueError('p and q have to be different')
219219
self.public_key = public_key
220-
if q < p: #ensure that p < q.
220+
if q < p: #ensure that p < q.
221221
self.p = q
222222
self.q = p
223223
else:
224224
self.p = p
225225
self.q = q
226226
self.psquare = self.p * self.p
227-
227+
228228
self.qsquare = self.q * self.q
229229
self.p_inverse = invert(self.p, self.q)
230230
self.hp = self.h_function(self.p, self.psquare)
@@ -233,18 +233,18 @@ def __init__(self, public_key, p, q):
233233
@staticmethod
234234
def from_totient(public_key, totient):
235235
"""given the totient, one can factorize the modulus
236-
236+
237237
The totient is defined as totient = (p - 1) * (q - 1),
238238
and the modulus is defined as modulus = p * q
239-
239+
240240
Args:
241241
public_key (PaillierPublicKey): The corresponding public
242242
key
243243
totient (int): the totient of the modulus
244-
244+
245245
Returns:
246246
the :class:`PaillierPrivateKey` that corresponds to the inputs
247-
247+
248248
Raises:
249249
ValueError: if the given totient is not the totient of the modulus
250250
of the given public key
@@ -256,7 +256,7 @@ def from_totient(public_key, totient):
256256
if not p*q == public_key.n:
257257
raise ValueError('given public key and totient do not match.')
258258
return PaillierPrivateKey(public_key, p, q)
259-
259+
260260
def __repr__(self):
261261
pub_repr = repr(self.public_key)
262262
return "<PaillierPrivateKey for {}>".format(pub_repr)
@@ -342,20 +342,20 @@ def raw_decrypt(self, ciphertext):
342342
decrypt_to_p = self.l_function(powmod(ciphertext, self.p-1, self.psquare), self.p) * self.hp % self.p
343343
decrypt_to_q = self.l_function(powmod(ciphertext, self.q-1, self.qsquare), self.q) * self.hq % self.q
344344
return self.crt(decrypt_to_p, decrypt_to_q)
345-
345+
346346
def h_function(self, x, xsquare):
347-
"""Computes the h-function as defined in Paillier's paper page 12,
347+
"""Computes the h-function as defined in Paillier's paper page 12,
348348
'Decryption using Chinese-remaindering'.
349349
"""
350350
return invert(self.l_function(powmod(self.public_key.g, x - 1, xsquare),x), x)
351351

352352
def l_function(self, x, p):
353353
"""Computes the L function as defined in Paillier's paper. That is: L(x,p) = (x-1)/p"""
354354
return (x - 1) // p
355-
355+
356356
def crt(self, mp, mq):
357357
"""The Chinese Remainder Theorem as needed for decryption. Returns the solution modulo n=pq.
358-
358+
359359
Args:
360360
mp(int): the solution modulo p.
361361
mq(int): the solution modulo q.

phe/tests/paillier_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def testStaticPrivateKeyConstructor(self):
7878
c = public_key.encrypt(4242)
7979
self.assertEqual(private_key, private_key_from_static, "The private keys should be the same.")
8080
self.assertEqual(private_key_from_static.decrypt(c), 4242, "Result of the decryption should be 4242")
81-
81+
8282
def testPrivateKeyEquality(self):
8383
pk = PaillierPublicKey(2537)
8484
p1 = PaillierPrivateKey(pk, 43, 59)

phe/tests/util_test.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
import unittest
1919
import random
2020
import math
21+
try:
22+
from math import gcd # new in Python 3.5
23+
except ImportError:
24+
from fractions import gcd # deprecated since Python 3.5
2125

2226
from phe import util
2327

@@ -45,7 +49,7 @@ def testPrimeOverN(self):
4549
for n in range(2, 50):
4650
p = util.getprimeover(n)
4751
self.assertGreaterEqual(p, 1 << (n-1))
48-
52+
4953
def testIsqrt(self):
5054
for _ in range(100):
5155
n = random.randint(2, 10000000)
@@ -54,6 +58,58 @@ def testIsqrt(self):
5458
self.assertEqual(util.isqrt(nsq), util.improved_i_sqrt(nsq))
5559

5660

61+
# same tests as above, but with gmpy2 and Crypto libraries disabled
62+
class PaillierUtilFallbacksTest(PaillierUtilTest):
63+
64+
def setUp(self):
65+
# save presence of libraries
66+
self.HAVE_GMP = util.HAVE_GMP
67+
self.HAVE_CRYPTO = util.HAVE_CRYPTO
68+
# disable libraties
69+
util.HAVE_GMP = False
70+
util.HAVE_CRYPTO = False
71+
72+
def tearDown(self):
73+
# restore presence of libraries
74+
util.HAVE_GMP = self.HAVE_GMP
75+
util.HAVE_CRYPTO = self.HAVE_CRYPTO
76+
77+
def testExtendedEuclieanAlgorithm(self):
78+
# from <https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm>
79+
self.assertEqual(util.extended_euclidean_algorithm(240, 46), (2, -9, 47))
80+
81+
# tests with arbirary values
82+
for a, b in [(77, 99), (45, 127)]: # non-coprime pair, coprime pair
83+
r, s, t = util.extended_euclidean_algorithm(a, b)
84+
self.assertEqual(r, s*a + t*b)
85+
self.assertEqual(r, gcd(a, b))
86+
87+
def testMillerRabin(self):
88+
a = 2 # witness, enough by itself for checking n < 2047
89+
self.assertFalse(util.miller_rabin(4, a))
90+
self.assertTrue(util.miller_rabin(127, a))
91+
composite = util.first_primes[-1] * util.first_primes[-2]
92+
self.assertFalse(util.miller_rabin(composite, a))
93+
94+
def testIsPrime(self):
95+
self.assertTrue(util.is_prime(17881)) # first not in first_primes
96+
self.assertFalse(util.is_prime(-17881))
97+
98+
self.assertFalse(util.is_prime(-4))
99+
self.assertFalse(util.is_prime(-2))
100+
self.assertFalse(util.is_prime(-1))
101+
self.assertFalse(util.is_prime(0))
102+
self.assertFalse(util.is_prime(1))
103+
self.assertTrue(util.is_prime(2))
104+
self.assertTrue(util.is_prime(3))
105+
106+
# same tests as for miller_rabin()
107+
self.assertFalse(util.is_prime(4))
108+
self.assertTrue(util.is_prime(127))
109+
composite = util.first_primes[-1] * util.first_primes[-2]
110+
self.assertFalse(util.is_prime(composite))
111+
112+
57113
class Base64UtilTest(unittest.TestCase):
58114

59115
def testEncodeDecodePositiveNonZeroInt(self):
@@ -67,4 +123,4 @@ def testFailToEncodeZero(self):
67123

68124

69125
if __name__ == "__main__":
70-
unittest.main()
126+
unittest.main()

0 commit comments

Comments
 (0)