├── .gitignore ├── LICENSE ├── README.md ├── modules ├── __init__.py ├── pkenc_elgamal_exp.py ├── pkenc_paillier.py ├── psi_2_round_elgamal.py ├── psi_2_round_paillier.py ├── psi_3_round_paillier.py └── utils_poly.py ├── sample_simulation.txt ├── simulate_psi.py └── unit_tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | .idea/ 56 | 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Milinda Perera 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Private Set Intersection Library 2 | ================================ 3 | 4 | Increasing dependence on anytime-anywhere availability of data and the increasing fear of losing privacy 5 | motivate the need for privacy-preserving techniques. One interesting and common problem occurs when two parties, 6 | a server and a client, need to privately compute an intersection of their respective sets of data. 7 | In doing so, one or both parties must obtain the intersection, while neither should learn any information 8 | about the other set. 9 | 10 | This library implements three private set intersection (PSI) protocols based on oblivious polynomial evaluations and 11 | interpolations. At the completion of each of these protocols, only the client learns the intersection. 12 | The protocols are, 13 | 14 | 1. A 3-Round PSI Protocol Based on Paillier Encryption Scheme 15 | 2. A 2-Round PSI Protocol Based on Paillier Encryption Scheme 16 | 3. A 2-Round PSI Protocol Based on ElGamal Encryption Scheme 17 | 18 | The first protocol is initiated by the server, whereas the second and third protocols are initiated by 19 | the client. The Paillier encryption scheme is based on integer groups. Therefore, it requires a security parameter of 20 | at least 1024 to be secure with respect to currently available computing power. On the other hand, ElGamal 21 | encryption scheme is based on elliptic curve groups, which can provide comparable security only with a security 22 | parameter of 160. This makes the third protocol far more efficient than the other two. 23 | Please refer to [this paper](http://link.springer.com/chapter/10.1007/978-3-642-14577-3_13) for more information 24 | regarding the theory behind these protocols. 25 | 26 | Some information about the included files are as follows. 27 | 28 | **1. simulate_psi.py:** 29 | 30 | This script simulates the three PSI protocols. The script first asks the user for the length of the server and 31 | client sets as well as the length of their intersection. Next, it generates those sets by randomly picking 32 | integers. Finally, it runs the three PSI protocols on the two sets and reports the output. It also measures 33 | the time required to execute each protocol and reports that result as well. One can run this script by executing 34 | the following command. 35 | 36 | `python simulate_psi.py` 37 | 38 | **2. sample_simulation.txt** 39 | 40 | This file contains the output of a single run of the simulate_psi.py script. 41 | 42 | **3. unit_tests.py** 43 | 44 | This script consists of the unit tests created for testing purposes. One can run this script by executing the following command. 45 | 46 | `python unit_tests.py` 47 | 48 | **4. modules/psi_3_round_paillier.py, 5. modules/psi_2_round_paillier.py, 6. modules/psi_2_round_elgamal.py** 49 | 50 | These modules contain the classes that implement the three PSI protocols. 51 | 52 | **7. modules/utils_poly.py** 53 | 54 | This module consists of polynomial interpolation and evaluation functions. These functions are defined general 55 | enough to work on any type of ring elements such as integer rings, polynomial rings, etc. In other words, the input 56 | type is only required to have addition and multiplication operations implemented, and the designer of the input 57 | type is given complete freedom to build those operations. 58 | 59 | **8. modules/pkenc_paillier.py** 60 | 61 | This module contains the class that implements the [Paillier cryptosystem](http://en.wikipedia.org/wiki/Paillier_cryptosystem). 62 | Also defined in this module is a class for Paillier ciphertexts that abstract away the homomorphic properties of 63 | the Paillier cryptosystem. 64 | 65 | **9. modules/pkenc_elgamal.py** 66 | 67 | This module provides the class that implements a variant of the [ElGamal cryptosystem](http://en.wikipedia.org/wiki/ElGamal) 68 | that provides the homorphic properties required for oblivious polynomial evaluations. More specifically, the encryption of 69 | a message `m` maps it to the group element `g^m`. This allows one to perform homomorphic addition and multiplication (by a constant) 70 | in the exponent. Unfortunately, this mapping makes it hard to decrypt ciphertexts efficiently. Nevertheless, it suffices for 71 | our purposes. 72 | -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcoder/private-set-intersection/558374a9d1b173b7bb6f360373ca66638d2b245d/modules/__init__.py -------------------------------------------------------------------------------- /modules/pkenc_elgamal_exp.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Milinda Perera' 2 | 3 | from charm.toolbox.ecgroup import G, ZR, ECGroup 4 | from charm.toolbox.eccurve import prime192v2 5 | 6 | 7 | class ElGamalExp(object): 8 | """ 9 | Implements the ElGamal cryptosystem where messages are encoded in the exponent. 10 | """ 11 | 12 | def keygen(self): 13 | group = ECGroup(prime192v2) 14 | g = group.random(G) 15 | x = group.random(ZR) 16 | h = g ** x 17 | pk = {'g': g, 'h': h, 'group': group, 'order': 6277101735386680763835789423078825936192100537584385056049} 18 | sk = {'x': x} 19 | return pk, sk 20 | 21 | def encrypt(self, pk, m): 22 | g, h, group, order = pk['g'], pk['h'], pk['group'], pk['order'] 23 | y = group.random(ZR) 24 | c1 = g ** y 25 | c2 = (h ** y) * (g ** (m % order)) 26 | return Cipher(c1, c2, pk) 27 | 28 | def does_encrypt_zero(self, pk, sk, c): 29 | x = sk['x'] 30 | return c.c2 == c.c1 ** x 31 | 32 | def does_encrypt_one(self, pk, sk, c): 33 | g = pk['g'] 34 | x = sk['x'] 35 | return c.c2 == (c.c1 ** x) * g 36 | 37 | def does_encrypt(self, pk, sk, c, m): 38 | g, order = pk['g'], pk['order'] 39 | x = sk['x'] 40 | return c.c2 == (c.c1 ** x) * (g ** (m % order)) 41 | 42 | 43 | class Cipher(object): 44 | """ 45 | This class abstracts the homomorphic operations of the Paillier ciphertexts. 46 | """ 47 | 48 | def __init__(self, c1, c2, pk): 49 | self.c1 = c1 50 | self.c2 = c2 51 | self.pk = pk 52 | 53 | def __add__(self, other): 54 | g, order = self.pk['g'], self.pk['order'] 55 | if type(other) == Cipher: 56 | return Cipher(self.c1 * other.c1, self.c2 * other.c2, self.pk) 57 | else: 58 | return Cipher(self.c1, self.c2 * (g ** (other % order)), self.pk) 59 | 60 | def __mul__(self, other): 61 | g, order = self.pk['g'], self.pk['order'] 62 | return Cipher(self.c1 ** (other % order), self.c2 ** (other % order), self.pk) 63 | -------------------------------------------------------------------------------- /modules/pkenc_paillier.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Milinda Perera' 2 | 3 | from charm.toolbox.integergroup import random, randomPrime, isPrime, gcd, lcm, integer 4 | 5 | 6 | class Paillier(object): 7 | """ 8 | Implements the Paillier cryptosystem. 9 | """ 10 | 11 | def L(self, u, n): 12 | return integer(int(u) - 1) / n 13 | 14 | def keygen(self, secparam=1024): 15 | while True: 16 | p, q = randomPrime(secparam), randomPrime(secparam) 17 | if isPrime(p) and isPrime(q) and gcd(p * q, (p - 1) * (q - 1)) == 1: 18 | break 19 | n = p * q 20 | g = n + 1 21 | n2 = n ** 2 22 | lam = lcm(p - 1, q - 1) 23 | u = (self.L(((g % n2) ** lam), n) % n) ** -1 24 | pk = {'n': n, 'g': g, 'n2': n2} 25 | sk = {'lam': lam, 'u': u} 26 | return pk, sk 27 | 28 | def encrypt(self, pk, m): 29 | g, n, n2 = pk['g'], pk['n'], pk['n2'] 30 | r = random(pk['n']) 31 | c = (((g % n2) ** m) * ((r % n2) ** n)) % n2 32 | return Cipher(c, pk) 33 | 34 | def decrypt(self, pk, sk, ct): 35 | n, n2 = pk['n'], pk['n2'] 36 | lam, u = sk['lam'], sk['u'] 37 | m = ((self.L((ct.c ** lam) % n2, n) % n) * u) % n 38 | return m 39 | 40 | 41 | class Cipher(object): 42 | """ 43 | This class abstracts the homomorphic operations of the Paillier ciphertexts. 44 | """ 45 | 46 | def __init__(self, c, pk): 47 | self.c = c 48 | self.pk = pk 49 | 50 | def __add__(self, other): 51 | g, n2 = self.pk['g'], self.pk['n2'] 52 | if type(other) == Cipher: 53 | return Cipher((self.c * other.c) % n2, self.pk) 54 | else: 55 | return Cipher((self.c * ((g ** other) % n2)) % n2, self.pk) 56 | 57 | def __mul__(self, other): 58 | n2 = self.pk['n2'] 59 | return Cipher((self.c ** other) % n2, self.pk) 60 | 61 | 62 | def test_paillier(): 63 | enc_scheme = Paillier() 64 | pk, sk = enc_scheme.keygen(1024) 65 | 66 | m = 2 67 | c = enc_scheme.encrypt(pk, m) 68 | c1 = c * (2 ** 2) + 0 69 | c1 = c1 * (2 ** 2) + c1 70 | c1.c *= ((c1.pk['n2'] - 1) % c1.pk['n2']) 71 | 72 | d = enc_scheme.decrypt(pk, sk, c) 73 | 74 | print(str(m), str(d)) 75 | 76 | 77 | if __name__ == '__main__': 78 | test_paillier() -------------------------------------------------------------------------------- /modules/psi_2_round_elgamal.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Milinda Perera' 2 | 3 | from charm.toolbox.integergroup import random, integer 4 | 5 | from pkenc_elgamal_exp import ElGamalExp 6 | from utils_poly import poly_eval_horner, poly_from_roots 7 | 8 | 9 | class PSI2RoundElGamal(object): 10 | """ 11 | Implements the 2-round PSI protocol based on ElGamal cryptosystem 12 | """ 13 | 14 | def client_to_server(self, client_set): 15 | # Initialize the cryptosystem. 16 | enc_scheme = ElGamalExp() 17 | pk, sk = enc_scheme.keygen() 18 | 19 | # Map the client set to the ring of ElGamal exponents, interpolate the unique 20 | # polynomial representing the set, and encrypt its coefficients. 21 | set_a_mapped = [integer(a, pk['order']) for a in client_set] 22 | coefs = poly_from_roots(set_a_mapped, integer(-1, pk['order']), integer(1, pk['order'])) 23 | coef_cts = [enc_scheme.encrypt(pk, int(c)) for c in coefs] 24 | 25 | out = {'pk': pk, 'coef_cts': coef_cts} 26 | client_state = {'pk': pk, 'sk': sk, 'client_set': client_set} 27 | 28 | return out, client_state 29 | 30 | def server_to_client(self, server_set, pk, coef_cts): 31 | # Evaluate the polynomial on each element of the server set. 32 | eval_cts = [poly_eval_horner(coef_cts, e) * int(random(pk['order'])) + e for e in server_set] 33 | 34 | return eval_cts 35 | 36 | def client_output(self, eval_cts, pk, sk, client_set): 37 | g, order = pk['g'], pk['order'] 38 | x = sk['x'] 39 | 40 | eval_exps = [ct.c2 * ((ct.c1 ** x) ** -1) for ct in eval_cts] 41 | intersection = [e for e in client_set if g ** (e % order) in eval_exps] 42 | 43 | return intersection 44 | -------------------------------------------------------------------------------- /modules/psi_2_round_paillier.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Milinda Perera' 2 | 3 | from charm.toolbox.integergroup import random, integer 4 | 5 | from pkenc_paillier import Paillier 6 | from utils_poly import poly_eval_horner, poly_from_roots 7 | 8 | 9 | class PSI2RoundPaillier(object): 10 | """ 11 | Implements the 2-round PSI protocol based on Paillier cryptosystem 12 | """ 13 | 14 | def __init__(self, sec_param): 15 | self.sec_param = sec_param 16 | 17 | def client_to_server(self, client_set): 18 | # Initialize the cryptosystem. 19 | enc_scheme = Paillier() 20 | pk, sk = enc_scheme.keygen(self.sec_param) 21 | 22 | # Map the client set to the ring of Paillier ciphertexts, interpolate the unique 23 | # polynomial representing the set, and encrypt its coefficients. 24 | slient_set_mapped = [integer(a, pk['n']) for a in client_set] 25 | coefs = poly_from_roots(slient_set_mapped, integer(-1, pk['n']), integer(1, pk['n'])) 26 | coef_cts = [enc_scheme.encrypt(pk, c) for c in coefs] 27 | 28 | out = {'pk': pk, 'coef_cts': coef_cts} 29 | client_state = {'pk': pk, 'sk': sk, 'client_set': client_set} 30 | 31 | return out, client_state 32 | 33 | def server_to_client(self, server_set, pk, coef_cts): 34 | # Evaluate the polynomial on each element of the server set. 35 | eval_cts = [poly_eval_horner(coef_cts, e) * random(pk['n']) + e for e in server_set] 36 | 37 | return eval_cts 38 | 39 | def client_output(self, eval_cts, pk, sk, client_set): 40 | enc_scheme = Paillier() 41 | evals = [int(enc_scheme.decrypt(pk, sk, ct)) for ct in eval_cts] 42 | intersection = sorted(set(evals) & set(client_set)) 43 | 44 | return intersection 45 | -------------------------------------------------------------------------------- /modules/psi_3_round_paillier.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Milinda Perera' 2 | 3 | from charm.toolbox.integergroup import random, integer 4 | 5 | from pkenc_paillier import Paillier 6 | from utils_poly import poly_eval_horner, poly_from_roots 7 | 8 | 9 | class PSI3RoundPaillier(object): 10 | """ 11 | Implements the 3-round PSI protocol based on Paillier cryptosystem 12 | """ 13 | 14 | def __init__(self, sec_param): 15 | self.sec_param = sec_param 16 | 17 | def server_to_client_1(self, server_set): 18 | # Initialize the cryptosystem. 19 | enc_scheme = Paillier() 20 | pk, sk = enc_scheme.keygen(self.sec_param) 21 | 22 | # Map the server set to the ring of Paillier ciphertexts, interpolate the unique 23 | # polynomial representing the set, and encrypt its coefficients. 24 | server_set_mapped = [integer(a, pk['n']) for a in server_set] 25 | coefs = poly_from_roots(server_set_mapped, integer(-1, pk['n']), integer(1, pk['n'])) 26 | coef_cts = [enc_scheme.encrypt(pk, c) for c in coefs] 27 | 28 | out = {'pk': pk, 'coef_cts': coef_cts} 29 | server_state = {'pk': pk, 'sk': sk, 'server_set': server_set} 30 | 31 | return out, server_state 32 | 33 | def client_to_server(self, client_set, pk, coef_cts): 34 | # Evaluate the polynomial on each element of the client set. 35 | eval_cts = [poly_eval_horner(coef_cts, e) * random(pk['n']) + e for e in client_set] 36 | 37 | return eval_cts 38 | 39 | def server_to_client_2(self, eval_cts, pk, sk, server_set): 40 | enc_scheme = Paillier() 41 | evals = [int(enc_scheme.decrypt(pk, sk, ct)) for ct in eval_cts] 42 | intersection = sorted(set(evals) & set(server_set)) 43 | 44 | return intersection 45 | 46 | def client_output(self, intersect): 47 | return intersect 48 | -------------------------------------------------------------------------------- /modules/utils_poly.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Milinda Perera' 2 | 3 | 4 | def poly_from_roots(roots, neg_one, one): 5 | """ 6 | Interpolates the unique polynomial that encodes the given roots. 7 | The function also requires the one and the negative one of the underlying ring. 8 | """ 9 | zero = one + neg_one 10 | coefs = [neg_one * roots[0], one] 11 | for r in roots[1:]: 12 | coefs = poly_mul(coefs, [neg_one * r, one], zero) 13 | return coefs 14 | 15 | 16 | def poly_eval(coefs, x): 17 | """ 18 | Evaluates the polynomial whose coefficients are given in coefs on the value x. 19 | Polynomial coefficient are stored in coefs from the lowest power to the highest. 20 | x is required to be a ring element. 21 | """ 22 | out = 0 23 | for i in range(len(coefs)): 24 | out = coefs[i] * (x ** i) + out 25 | return out 26 | 27 | 28 | def poly_eval_horner(coefs, x): 29 | """ 30 | Evaluates the polynomial whose coefficients are given in coefs on the value x 31 | using the Horner's method. This function is much faster than regular polynomial evaluation. 32 | Polynomial coefficient are stored in coefs from the lowest power to the highest. 33 | x is required to be a ring element. 34 | """ 35 | out = 0 36 | for c in reversed(coefs): 37 | out = c + out * x 38 | return out 39 | 40 | 41 | def poly_mul(coefs1, coefs2, zero): 42 | """ 43 | Multiplies two polynomials whose coefficients are given in coefs1 and coefs2. 44 | Zero value of the underlying ring is required on the input zero. 45 | """ 46 | coefs3 = [zero] * (len(coefs1) + len(coefs2) - 1) 47 | for i in range(len(coefs1)): 48 | for j in range(len(coefs2)): 49 | coefs3[i + j] += coefs1[i] * coefs2[j] 50 | return coefs3 51 | 52 | 53 | def poly_print(coefs): 54 | """Prints the polynomial whose coefficients are given in coefs in human readable form.""" 55 | out = [poly_coef_to_str(int(coefs[i]), i) for i in range(len(coefs)) if coefs[i] != 0 or i == 0] 56 | out.reverse() 57 | if out[0][0] == '+': 58 | del out[0][0] 59 | return ' '.join([' '.join(l) for l in out]) 60 | 61 | 62 | def poly_coef_to_str(coef, degree): 63 | """A helper function for poly_print.""" 64 | out = [] 65 | if coef < 0: 66 | out.append('-') 67 | else: 68 | out.append('+') 69 | if abs(coef) != 1 or degree == 0: 70 | out.append(str(abs(coef))) 71 | if degree == 1: 72 | out.append('X') 73 | elif degree > 1: 74 | out.append('X^' + str(degree)) 75 | return out 76 | -------------------------------------------------------------------------------- /sample_simulation.txt: -------------------------------------------------------------------------------- 1 | $ python tester.py 2 | Input set length: 200 3 | Input intersection length: 25 4 | 5 | server set: [2, 9, 11, 12, 17, 18, 21, 22, 26, 30, 31, 33, 35, 37, 39, 40, 43, 44, 46, 48, 49, 50, 54, 59, 66, 67, 68, 76, 77, 79, 80, 82, 86, 88, 89, 91, 93, 95, 98, 100, 102, 103, 105, 106, 109, 111, 114, 118, 119, 121, 123, 127, 129, 131, 132, 137, 138, 141, 143, 145, 146, 147, 152, 153, 155, 157, 158, 159, 162, 163, 164, 165, 166, 168, 169, 180, 181, 185, 186, 187, 188, 190, 193, 194, 198, 200, 204, 205, 206, 208, 211, 212, 213, 222, 232, 234, 244, 247, 249, 250, 252, 253, 255, 256, 257, 261, 262, 264, 268, 269, 275, 276, 277, 279, 280, 283, 284, 291, 292, 293, 295, 296, 300, 303, 305, 306, 307, 312, 315, 324, 325, 327, 330, 331, 332, 339, 341, 344, 345, 350, 357, 365, 367, 368, 369, 370, 376, 378, 382, 385, 386, 388, 389, 391, 392, 399, 400, 401, 402, 415, 419, 421, 426, 427, 434, 442, 443, 444, 445, 448, 451, 454, 455, 456, 458, 462, 463, 472, 474, 477, 479, 482, 483, 488, 491, 495, 496, 497, 498, 502, 503, 506, 507, 508, 509, 511, 520, 523, 525, 532] 6 | client set: [2, 9, 11, 12, 17, 18, 21, 22, 26, 30, 31, 33, 35, 37, 39, 40, 43, 44, 46, 48, 49, 50, 54, 59, 66, 2049, 2055, 2058, 2059, 2061, 2062, 2067, 2068, 2073, 2074, 2079, 2080, 2084, 2086, 2088, 2093, 2096, 2097, 2101, 2102, 2106, 2107, 2108, 2110, 2112, 2113, 2114, 2118, 2120, 2123, 2126, 2127, 2128, 2131, 2132, 2133, 2134, 2137, 2139, 2143, 2145, 2146, 2147, 2150, 2151, 2153, 2159, 2160, 2161, 2166, 2167, 2173, 2179, 2180, 2181, 2184, 2189, 2194, 2195, 2200, 2201, 2202, 2204, 2210, 2211, 2212, 2216, 2217, 2218, 2222, 2223, 2227, 2229, 2230, 2232, 2236, 2243, 2244, 2245, 2247, 2249, 2251, 2254, 2257, 2264, 2268, 2270, 2274, 2275, 2277, 2278, 2284, 2285, 2294, 2295, 2296, 2297, 2298, 2299, 2301, 2303, 2304, 2306, 2307, 2311, 2312, 2315, 2318, 2321, 2323, 2324, 2326, 2327, 2328, 2330, 2336, 2337, 2338, 2339, 2342, 2344, 2347, 2348, 2351, 2352, 2354, 2355, 2356, 2358, 2360, 2362, 2364, 2365, 2366, 2367, 2371, 2377, 2378, 2380, 2387, 2390, 2393, 2394, 2395, 2399, 2404, 2406, 2408, 2409, 2410, 2412, 2415, 2417, 2418, 2420, 2422, 2423, 2426, 2429, 2435, 2436, 2437, 2442, 2444, 2446, 2447, 2449, 2454, 2457, 2463, 2465, 2468, 2469, 2473, 2480] 7 | intersection: [2, 9, 11, 12, 17, 18, 21, 22, 26, 30, 31, 33, 35, 37, 39, 40, 43, 44, 46, 48, 49, 50, 54, 59, 66] 8 | 9 | PSI3RoundPaillier output: [2, 9, 11, 12, 17, 18, 21, 22, 26, 30, 31, 33, 35, 37, 39, 40, 43, 44, 46, 48, 49, 50, 54, 59, 66] 10 | PSI3RoundPaillier time: 21.8620269299 11 | 12 | PSI2RoundPaillier output: [2, 9, 11, 12, 17, 18, 21, 22, 26, 30, 31, 33, 35, 37, 39, 40, 43, 44, 46, 48, 49, 50, 54, 59, 66] 13 | PSI2RoundPaillier time: 18.0804710388 14 | 15 | PSI2RoundElGamal output: [2, 9, 11, 12, 17, 18, 21, 22, 26, 30, 31, 33, 35, 37, 39, 40, 43, 44, 46, 48, 49, 50, 54, 59, 66] 16 | PSI2RoundElGamal time: 3.55466294289 17 | -------------------------------------------------------------------------------- /simulate_psi.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Milinda Perera' 2 | 3 | import random as pyrandom 4 | from time import time 5 | 6 | from modules.psi_3_round_paillier import PSI3RoundPaillier 7 | from modules.psi_2_round_paillier import PSI2RoundPaillier 8 | from modules.psi_2_round_elgamal import PSI2RoundElGamal 9 | 10 | 11 | def timer(func, *pargs, **kargs): 12 | """ 13 | Measures the time required to run func with the given parameters. 14 | Returns the time as well as the result of the computation. 15 | """ 16 | start = time() 17 | ret = func(*pargs, **kargs) 18 | elapsed = time() - start 19 | return elapsed, ret 20 | 21 | 22 | def run_psi_3_round_paillier(server_set, client_set): 23 | """ 24 | Simulates the 3-round PSI protocol based on Paillier encryption scheme 25 | on the given server and client sets. Returns the final output of the client. 26 | """ 27 | psi = PSI3RoundPaillier(1024) 28 | server_out_1, server_state = psi.server_to_client_1(server_set) 29 | client_out_1 = psi.client_to_server(client_set, **server_out_1) 30 | server_out_2 = psi.server_to_client_2(client_out_1, **server_state) 31 | client_out_2 = psi.client_output(server_out_2) 32 | return client_out_2 33 | 34 | 35 | def run_psi_2_round_paillier(server_set, client_set): 36 | """ 37 | Simulates the 2-round PSI protocol based on Paillier encryption scheme 38 | on the given server and client sets. Returns the final output of the client. 39 | """ 40 | psi = PSI2RoundPaillier(1024) 41 | client_out_1, client_state = psi.client_to_server(client_set) 42 | server_out = psi.server_to_client(server_set, **client_out_1) 43 | client_out_2 = psi.client_output(server_out, **client_state) 44 | return client_out_2 45 | 46 | 47 | def run_psi_2_round_elgamal(server_set, client_set): 48 | """ 49 | Simulates the 2-round PSI protocol based on ElGamal encryption scheme 50 | on the given server and client sets. Returns the final output of the client. 51 | """ 52 | psi = PSI2RoundElGamal() 53 | client_out_1, client_state = psi.client_to_server(client_set) 54 | server_out = psi.server_to_client(server_set, **client_out_1) 55 | client_out_2 = psi.client_output(server_out, **client_state) 56 | return client_out_2 57 | 58 | 59 | if __name__ == '__main__': 60 | # Obtain the server and client set lengths as well as the intersection length from the user. 61 | set_len = input('Input set length: ') 62 | intersection_len = input('Input intersection length: ') 63 | 64 | # Generate server and client sets with above parameters. 65 | server_set = [] 66 | client_set = [] 67 | while not (len(client_set) == len(server_set) == set_len): 68 | server_set = list(set([pyrandom.randint(1, set_len * 10) for i in range(set_len * 5)]))[:set_len] 69 | client_set = list(set([pyrandom.randint(set_len * 10, set_len * 20) for i in range(set_len * 5)]))[:set_len - intersection_len] + server_set[:intersection_len] 70 | 71 | # Print generated sets as well as their intersection for comparison purposes. 72 | print 73 | print('server set: {0}'.format(sorted(server_set))) 74 | print('client set: {0}'.format(sorted(client_set))) 75 | print('intersection: {0}'.format(sorted(set(server_set) & set(client_set)))) 76 | print 77 | 78 | sims = [['PSI3RoundPaillier', run_psi_3_round_paillier], 79 | ['PSI2RoundPaillier', run_psi_2_round_paillier], 80 | ['PSI2RoundElGamal', run_psi_2_round_elgamal]] 81 | 82 | # Simulate the protocols and report results. 83 | for sim in sims: 84 | time_taken, result = timer(sim[1], server_set, client_set) 85 | print('{0} output: {1}'.format(sim[0], sorted(result))) 86 | print('{0} time: {1}'.format(sim[0], time_taken)) 87 | print -------------------------------------------------------------------------------- /unit_tests.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Milinda Perera' 2 | 3 | import unittest 4 | 5 | from charm.toolbox.integergroup import integer 6 | 7 | from modules.utils_poly import poly_from_roots, poly_eval, poly_eval_horner, poly_mul, poly_print 8 | from modules.pkenc_elgamal_exp import ElGamalExp 9 | from modules.pkenc_paillier import Paillier 10 | 11 | 12 | class Test_utils_poly(unittest.TestCase): 13 | def test_1_poly_from_roots(self): 14 | """Tests the polynomial interpolation function.""" 15 | 16 | output = "poly_from_roots failed on {0}.\nNeeded {1}, got {2}." 17 | 18 | # Each test case is (roots, coefs) 19 | cases = [([2, 3, 4, 5], [120, -154, 71, -14, 1]), 20 | ([0], [0, 1]), 21 | ([-1], [1, 1])] 22 | 23 | for (roots, coefs) in cases: 24 | result = poly_from_roots(roots, -1, 1) 25 | self.assertListEqual(result, coefs, output.format(roots, coefs, result)) 26 | 27 | def test_2_poly_eval(self): 28 | """Tests the regular polynomial evaluation function""" 29 | 30 | output = "poly_eval failed on {0} and {1}.\nNeeded {2}, got {3}." 31 | 32 | # Each test case is (roots, x, eval) 33 | cases = [([2, 3, 4, 5], 3, 0), 34 | ([-1, 2, -10, -111], -10, 0), 35 | ([1, 2, -7, -22, 0], 4, 6864)] 36 | 37 | for (roots, x, eval) in cases: 38 | coefs = poly_from_roots(roots, -1, 1) 39 | result = poly_eval(coefs, x) 40 | self.assertEqual(result, eval, output.format(roots, x, eval, result)) 41 | 42 | def test_3_poly_eval_horner(self): 43 | """Tests the Horner's polynomial evaluation function""" 44 | 45 | output = "poly_eval_horner failed on {0} and {1}.\nNeeded {2}, got {3}." 46 | 47 | # Each test case is (roots, x, eval) 48 | cases = [([2, 3, 4, 5], 3, 0), 49 | ([-1, 2, -10, -111], -10, 0), 50 | ([1, 2, -7, -22, 0], 4, 6864)] 51 | 52 | for (roots, x, eval) in cases: 53 | coefs = poly_from_roots(roots, -1, 1) 54 | result = poly_eval_horner(coefs, x) 55 | self.assertEqual(result, eval, output.format(roots, x, eval, result)) 56 | 57 | def test_4_poly_mul(self): 58 | """Tests the polynomial multiplication""" 59 | 60 | output = "poly_mul failed on {0} and {1}.\nNeeded {2}, got {3}." 61 | 62 | # Each test case is (coefs_1, coefs_2, coefs_mul) 63 | cases = [([0], [0], [0]), 64 | ([1], [1], [1]), 65 | ([2], [-2], [-4]), 66 | ([2, 3], [4, 5], [8, 22, 15]), 67 | ([0, -2, 5, -6], [0, 2, 0, -4], [0, 0, -4, 10, -4, -20, 24])] 68 | 69 | for (coefs_1, coefs_2, coefs_mul) in cases: 70 | result = poly_mul(coefs_1, coefs_2, 0) 71 | self.assertEqual(result, coefs_mul, output.format(coefs_1, coefs_2, coefs_mul, result)) 72 | 73 | def test_5_poly_print(self): 74 | """Tests the polynomial print function""" 75 | 76 | output = "poly_print failed on {0}.\nNeeded {1}, got {2}." 77 | 78 | # Each test case is (coefs, string) 79 | cases = [([0], '0'), 80 | ([1], '1'), 81 | ([1, 0], '1'), 82 | ([1, 0, 0, 0, -6], '- 6 X^4 + 1'), 83 | ([-1], '- 1'), 84 | ([-1, 0], '- 1'), 85 | ([-1, 9], '9 X - 1'), 86 | ([-1, 0, 9], '9 X^2 - 1')] 87 | 88 | for (coefs, string) in cases: 89 | result = poly_print(coefs) 90 | self.assertEqual(result, string, output.format(coefs, string, result)) 91 | 92 | 93 | class Test_pkenc_elgamal_exp(unittest.TestCase): 94 | def setUp(self): 95 | self.enc_scheme = ElGamalExp() 96 | self.pk, self.sk = self.enc_scheme.keygen() 97 | 98 | def test_1_enc(self): 99 | """Tests ElGamal encryption function.""" 100 | 101 | output = "ElGamal encryption failed on {0}." 102 | 103 | messages = [0, 1, 55, 535, 29847123948, 1928347123949123764876, 23984761239847612346781234987001234] 104 | 105 | for m in messages: 106 | c = self.enc_scheme.encrypt(self.pk, m) 107 | encoded_m = c.c1 ** self.sk['x'] * self.pk['g'] ** (m % self.pk['order']) 108 | self.assertEqual(c.c2, encoded_m, output.format(m)) 109 | 110 | def test_2_homomorphic_addition(self): 111 | """Tests homomorphic addition of ElGamal ciphertexts.""" 112 | 113 | output = "ElGamal homomorphic addition failed on {0} and {1}." 114 | 115 | # Each test case is (num1, num2, sum) 116 | cases = [(0, 0, 0), 117 | (0, 1, 1), 118 | (-1, 2, 1), 119 | (3, 30, 33), 120 | (-22, 22, 0)] 121 | 122 | for (a, b, s) in cases: 123 | c1 = self.enc_scheme.encrypt(self.pk, a) 124 | c2 = self.enc_scheme.encrypt(self.pk, b) 125 | c3 = c1 + c2 126 | self.assertTrue(self.enc_scheme.does_encrypt(self.pk, self.sk, c3, s), output.format(a, b)) 127 | 128 | def test_3_homomorphic_multiplication(self): 129 | """Tests homomorphic multiplication of ElGamal ciphertexts.""" 130 | 131 | output = "ElGamal homomorphic multiplication failed on {0} and {1}." 132 | 133 | # Each test case is (num1, num2, mul) 134 | cases = [(0, 0, 0), 135 | (0, 1, 0), 136 | (-2, 4, -8), 137 | (-5, 5, -25), 138 | (6, 4, 24)] 139 | 140 | for (a, b, m) in cases: 141 | c1 = self.enc_scheme.encrypt(self.pk, a) 142 | c3 = c1 * b 143 | self.assertTrue(self.enc_scheme.does_encrypt(self.pk, self.sk, c3, m), output.format(a, b)) 144 | 145 | 146 | class Test_pkenc_paillier(unittest.TestCase): 147 | def setUp(self): 148 | self.enc_scheme = Paillier() 149 | self.pk, self.sk = self.enc_scheme.keygen(1024) 150 | 151 | def test_1_enc(self): 152 | """Tests Paillier encryption function.""" 153 | 154 | output = "Paillier encryption failed on {0}." 155 | 156 | messages = [1, 2, 55, 535, 29847123948, 1928347123949123764876, 23984761239847612346781234987001234] 157 | 158 | for m in messages: 159 | c = self.enc_scheme.encrypt(self.pk, m) 160 | d = self.enc_scheme.decrypt(self.pk, self.sk, c) 161 | self.assertEqual(d, m, output.format(m)) 162 | 163 | def test_2_homomorphic_addition(self): 164 | """Tests homomorphic addition of Paillier ciphertexts.""" 165 | 166 | output = "Paillier homomorphic addition failed on {0} and {1}." 167 | 168 | cases = [(1, 1, 2), 169 | (-1, 2, 1), 170 | (-3, 30, 27), 171 | (22, 22, 44)] 172 | 173 | # Each test case is (num1, num2, sum) 174 | for (a, b, s) in cases: 175 | c1 = self.enc_scheme.encrypt(self.pk, integer(a, self.pk['n'])) 176 | c2 = self.enc_scheme.encrypt(self.pk, integer(b, self.pk['n'])) 177 | c3 = c1 + c2 178 | d = self.enc_scheme.decrypt(self.pk, self.sk, c3) 179 | self.assertEqual(d, integer(s, self.pk['n']), output.format(a, b)) 180 | 181 | def test_3_homomorphic_multiplication(self): 182 | """Tests homomorphic multiplication of Paillier ciphertexts.""" 183 | 184 | output = "Paillier homomorphic multiplication failed on {0} and {1}." 185 | 186 | # Each test case is (num1, num2, mul) 187 | cases = [(1, 1, 1), 188 | (2, 4, 8), 189 | (-5, 5, -25), 190 | (6, -4, -24)] 191 | 192 | for (a, b, m) in cases: 193 | c1 = self.enc_scheme.encrypt(self.pk, integer(a, self.pk['n'])) 194 | c2 = c1 * integer(b, self.pk['n']) 195 | d = self.enc_scheme.decrypt(self.pk, self.sk, c2) 196 | self.assertEqual(d, integer(m, self.pk['n']), output.format(a, b)) 197 | 198 | 199 | if __name__ == '__main__': 200 | tests = [Test_utils_poly, Test_pkenc_elgamal_exp, Test_pkenc_paillier] 201 | suites = [unittest.makeSuite(t, 'test') for t in tests] 202 | all_suites = unittest.TestSuite(suites) 203 | runner = unittest.TextTestRunner() 204 | runner.run(all_suites) --------------------------------------------------------------------------------