Skip to content

Commit 275231e

Browse files
tanjuntaohardbyte
andauthored
Using gmpy2's mulmod to accelerate the mulmod operation (#99)
* Using gmpy2's mulmod to accelerate the mulmod operation According to #98, the mulmod operation(mulmod(a, b, c) = a * b % c) could be accelerated by using gmpy2.mpz when a, b, and c are very large numbers. The main changes are: * add a mulmod funciton in util.py * use util.mulmod function to wrap the mulmod operation in paillier.py Co-authored-by: Brian Thorne <brian@hardbyte.nz>
1 parent 046a77c commit 275231e

File tree

2 files changed

+29
-8
lines changed

2 files changed

+29
-8
lines changed

phe/paillier.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
Mapping = dict
2727

2828
from phe import EncodedNumber
29-
from phe.util import invert, powmod, getprimeover, isqrt
29+
from phe.util import invert, powmod, mulmod, getprimeover, isqrt
3030

3131
# Paillier cryptosystem is based on integer factorisation.
3232
# The default is chosen to give a minimum of 128 bits of security.
@@ -125,6 +125,7 @@ def raw_encrypt(self, plaintext, r_value=None):
125125
if self.n - self.max_int <= plaintext < self.n:
126126
# Very large plaintext, take a sneaky shortcut using inverses
127127
neg_plaintext = self.n - plaintext # = abs(plaintext - nsquare)
128+
# avoid using gmpy2's mulmod when a * b < c
128129
neg_ciphertext = (self.n * neg_plaintext + 1) % self.nsquare
129130
nude_ciphertext = invert(neg_ciphertext, self.nsquare)
130131
else:
@@ -135,7 +136,7 @@ def raw_encrypt(self, plaintext, r_value=None):
135136
r = r_value or self.get_random_lt_n()
136137
obfuscator = powmod(r, self.n, self.nsquare)
137138

138-
return (nude_ciphertext * obfuscator) % self.nsquare
139+
return mulmod(nude_ciphertext, obfuscator, self.nsquare)
139140

140141
def get_random_lt_n(self):
141142
"""Return a cryptographically random number less than :attr:`n`"""
@@ -342,8 +343,14 @@ def raw_decrypt(self, ciphertext):
342343
raise TypeError('Expected ciphertext to be an int, not: %s' %
343344
type(ciphertext))
344345

345-
decrypt_to_p = self.l_function(powmod(ciphertext, self.p-1, self.psquare), self.p) * self.hp % self.p
346-
decrypt_to_q = self.l_function(powmod(ciphertext, self.q-1, self.qsquare), self.q) * self.hq % self.q
346+
decrypt_to_p = mulmod(
347+
self.l_function(powmod(ciphertext, self.p-1, self.psquare), self.p),
348+
self.hp,
349+
self.p)
350+
decrypt_to_q = mulmod(
351+
self.l_function(powmod(ciphertext, self.q-1, self.qsquare), self.q),
352+
self.hq,
353+
self.q)
347354
return self.crt(decrypt_to_p, decrypt_to_q)
348355

349356
def h_function(self, x, xsquare):
@@ -363,7 +370,7 @@ def crt(self, mp, mq):
363370
mp(int): the solution modulo p.
364371
mq(int): the solution modulo q.
365372
"""
366-
u = (mq - mp) * self.p_inverse % self.q
373+
u = mulmod(mq - mp, self.p_inverse, self.q)
367374
return mp + (u * self.p)
368375

369376
def __eq__(self, other):
@@ -613,7 +620,7 @@ def obfuscate(self):
613620
"""
614621
r = self.public_key.get_random_lt_n()
615622
r_pow_n = powmod(r, self.public_key.n, self.public_key.nsquare)
616-
self.__ciphertext = self.__ciphertext * r_pow_n % self.public_key.nsquare
623+
self.__ciphertext = mulmod(self.__ciphertext, r_pow_n, self.public_key.nsquare)
617624
self.__is_obfuscated = True
618625

619626
def _add_scalar(self, scalar):
@@ -709,7 +716,7 @@ def _raw_add(self, e_a, e_b):
709716
int: E(a + b), calculated by taking the product of E(a) and
710717
E(b) modulo :attr:`~PaillierPublicKey.n` ** 2.
711718
"""
712-
return e_a * e_b % self.public_key.nsquare
719+
return mulmod(e_a, e_b, self.public_key.nsquare)
713720

714721
def _raw_mul(self, plaintext):
715722
"""Returns the integer E(a * plaintext), where E(a) = ciphertext

phe/util.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
# GMP's powmod has greater overhead than Python's pow, but is faster.
3434
# From a quick experiment on our machine, this seems to be the break even:
3535
_USE_MOD_FROM_GMP_SIZE = (1 << (8*2))
36-
36+
_USE_MULMOD_FROM_GMP_SIZE = (1 << 1000) # pow(2, 1000)
3737

3838
def powmod(a, b, c):
3939
"""
@@ -50,6 +50,20 @@ def powmod(a, b, c):
5050
return int(gmpy2.powmod(a, b, c))
5151

5252

53+
def mulmod(a, b, c):
54+
"""
55+
Uses GMP, if available, to do a * b mod c, where a, b, c
56+
are integers.
57+
58+
:return int: (a * b) % c
59+
"""
60+
if not HAVE_GMP or max(a, b, c) < _USE_MULMOD_FROM_GMP_SIZE:
61+
return a * b % c
62+
else:
63+
a, b, c = gmpy2.mpz(a), gmpy2.mpz(b), gmpy2.mpz(c)
64+
return int(gmpy2.mod(gmpy2.mul(a, b), c))
65+
66+
5367
def extended_euclidean_algorithm(a, b):
5468
"""Extended Euclidean algorithm
5569

0 commit comments

Comments
 (0)