Skip to content

Commit 4458adb

Browse files
committed
Add Palia
1 parent 5fc4136 commit 4458adb

8 files changed

Lines changed: 345 additions & 11 deletions

File tree

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ pip install pymife
3030
2. (Adaptive Secure) Damgard based scheme from https://eprint.iacr.org/2019/487.pdf, using Damgard single input
3131
3. (Adaptive Secure with Random Oracle) Decentralized DDH based scheme from https://eprint.iacr.org/2019/020.pdf
3232

33+
### Private Non-interactive Aggregation (PALIA)
34+
1. (Adaptive Secure with Random Oracle) Damgard based scheme
35+
3336
## Note
3437
- The implementation of these schemes are not fully optimized and not peer-reviewed, recommended to only use for research / testing purpose.
3538
- More schemes will be added in the future
@@ -239,6 +242,36 @@ cs = [FeDDHMultiClientDec.encrypt(x[i], tag, keys[i]) for i in range(n)]
239242
sk = [FeDDHMultiClientDec.keygen(y, keys[i]) for i in range(n)]
240243
m = FeDDHMultiClientDec.decrypt(cs, tag, pub, sk, (0, 2000))
241244
```
245+
246+
### Private Non-interactive Aggregation (PALIA)
247+
248+
```python
249+
from mife.multiclient.decentralized.palia import Palia
250+
251+
n = 3
252+
m = 5
253+
x = [[i + j for j in range(m)] for i in range(n)]
254+
y = [[i - j + 10 for j in range(m)] for i in range(n)]
255+
256+
tag = b"testingtag123"
257+
pub = Palia.generate(n, m)
258+
keys = [pub.generate_party(i) for i in range(n)]
259+
for i in range(n):
260+
for j in range(n):
261+
if i == j: continue
262+
keys[i].exchange(j, keys[j].get_exc_public_key())
263+
264+
for i in range(n):
265+
keys[i].generate_share()
266+
267+
mk = Palia.generate_query_key(pub)
268+
pk = mk.getPublicKey()
269+
enc_y = Palia.encrypt_query(y, pk, pub)
270+
271+
cs = [Palia.encrypt(x[i], tag, keys[i]) for i in range(n)]
272+
sk = [Palia.keygen(enc_y, keys[i]) for i in range(n)]
273+
m = Palia.decrypt(cs, tag, pub, sk, y, mk, (0, 2000))
274+
```
242275
##### Export Keys
243276

244277
```python
@@ -260,6 +293,7 @@ print(f"secret_key = {json.dumps(sk.export())}")
260293
print(f"pub_key = {json.dumps(key.get_public_key().export())}")
261294
```
262295

296+
263297
## Customize
264298

265299
All of the DDH and Damgard schemes support custom group. You can implement your own group class by extending `/src/mife/data/group.py` as base class.

mife/data/paillier.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from __future__ import annotations
2+
from Crypto.Util.number import inverse
3+
from secrets import randbits
4+
from mife.common import getStrongPrime
5+
from math import gcd
6+
from gmpy2 import mpz
7+
8+
9+
class PaillierKey:
10+
def __init__(self, n, g, lcm=None):
11+
self.n = n
12+
self.g = g
13+
self.lcm = lcm
14+
self.n2 = self.n ** 2
15+
if lcm is not None:
16+
self.u = inverse(lcm, n)
17+
18+
def getPublicKey(self):
19+
return PaillierKey(self.n, self.g)
20+
21+
def hasPrivateKey(self):
22+
return self.u is not None
23+
24+
def encrypt(self, m: int):
25+
while True:
26+
r = randbits(self.n.bit_length())
27+
if r < self.n and gcd(r, self.n) == 1:
28+
break
29+
return PaillierElem(self.getPublicKey(), (pow(self.g, m, self.n2) * pow(r, self.n, self.n2)) % self.n2)
30+
31+
def decrypt(self, c: PaillierElem) -> int:
32+
if not self.hasPrivateKey():
33+
raise ValueError("No private key")
34+
return (((pow(c.c, self.lcm, self.n2) - 1) // self.n) * self.u) % self.n
35+
36+
37+
class PaillierElem:
38+
def __init__(self, pk: PaillierKey, c: int):
39+
self.pk = pk
40+
self.c = c
41+
42+
def __add__(self, other):
43+
if self.pk.n != other.pk.n:
44+
raise ValueError("Different public keys")
45+
return PaillierElem(self.pk, (self.c * other.c) % self.pk.n2)
46+
47+
def __radd__(self, other):
48+
if other == 0:
49+
return self
50+
raise ValueError("Invalid operation")
51+
52+
def __rmul__(self, other: int):
53+
return PaillierElem(self.pk, pow(self.c, other, self.pk.n2))
54+
55+
56+
class Paillier:
57+
@staticmethod
58+
def generate(bits=1024, p=None, q=None):
59+
if p is None:
60+
p = getStrongPrime(bits)
61+
if q is None:
62+
q = getStrongPrime(bits)
63+
n = p * q
64+
g = n + 1
65+
lcm = (p - 1) * (q - 1) // gcd(p - 1, q - 1)
66+
return PaillierKey(mpz(n), mpz(g), mpz(lcm))
67+
68+
69+
70+
71+

mife/multiclient/decentralized/ddh.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,16 @@ def exchange(self, index: int, pub_key: ECC.EccKey):
6363
self.exchange_key[index] = key_agreement(static_priv=self.exc_priv_key, static_pub=pub_key, kdf=kdf)
6464

6565
def generate_share(self):
66-
length = self.pub.F.order().bit_length()
66+
length = self.pub.F.order().bit_length() // 8
6767
self.share = [[] for _ in range(self.pub.n)]
6868
for i in range(self.pub.n):
6969
for j in range(self.pub.m):
7070
nonce = long_to_bytes(i * self.pub.m + j)
7171
self.share[i].append(
7272
(cprf.CPRF.eval(self.pub.n, self.index, self.exchange_key,
73-
b'a' + nonce, length),
73+
b'a' + nonce, length) % self.pub.F.order(),
7474
cprf.CPRF.eval(self.pub.n, self.index, self.exchange_key,
75-
b'b' + nonce, length))
75+
b'b' + nonce, length) % self.pub.F.order())
7676
)
7777

7878

@@ -87,13 +87,6 @@ def __init__(self, tag: bytes, c: List[GroupElem]):
8787
self.c = c
8888
self.tag = tag
8989

90-
def export(self):
91-
return {
92-
"tag": self.tag.hex(),
93-
"c": [x.export() for x in self.c]
94-
}
95-
96-
9790
class FeDDHMultiClientDec:
9891

9992
@staticmethod
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from __future__ import annotations
2+
3+
from typing import List, Tuple, Callable
4+
5+
from mife.data.group import GroupBase
6+
from mife.data.paillier import PaillierKey, PaillierElem, Paillier
7+
from mife.multiclient.decentralized.ddh import (FeDDHMultiClientDec, _FeDDHMultiClientDec_PK,
8+
_FeDDHMultiClientDec_C, _FeDDHMultiClientDec_MK,
9+
_FeDDHMultiClientDec_SK)
10+
class Palia:
11+
@staticmethod
12+
def generate(n: int, m: int, F: GroupBase = None,
13+
hash: Callable[[bytes, int], Tuple[int, int]] = None) -> _FeDDHMultiClientDec_PK:
14+
return FeDDHMultiClientDec.generate(n, m, F, hash)
15+
16+
@staticmethod
17+
def encrypt(x: List[int], tag: bytes, key: _FeDDHMultiClientDec_MK) -> _FeDDHMultiClientDec_C:
18+
return FeDDHMultiClientDec.encrypt(x, tag, key)
19+
20+
@staticmethod
21+
def decrypt(c: List[_FeDDHMultiClientDec_C], tag: bytes,
22+
key: _FeDDHMultiClientDec_PK, sk: List[_FeDDHMultiClientDec_SK], y: List[List[int]], mk: PaillierKey,
23+
bound: Tuple[int, int]) -> int:
24+
dec_sk = [_FeDDHMultiClientDec_SK(y, (mk.decrypt(sk[i].d[0]), mk.decrypt(sk[i].d[1]))) for i in range(len(sk))]
25+
return FeDDHMultiClientDec.decrypt(c, tag, key, dec_sk, bound)
26+
27+
@staticmethod
28+
def keygen(enc_y: List[List[PaillierElem]], key: _FeDDHMultiClientDec_MK) -> _FeDDHMultiClientDec_SK:
29+
return FeDDHMultiClientDec.keygen(enc_y, key)
30+
31+
@staticmethod
32+
def encrypt_query(y: List[List[int]], pk: PaillierKey, pub: _FeDDHMultiClientDec_PK) -> List[List[PaillierElem]]:
33+
return [[pk.encrypt(y[i][j] % pub.F.order()) for j in range(len(y[i]))] for i in range(len(y))]
34+
35+
@staticmethod
36+
def generate_query_key(pub: _FeDDHMultiClientDec_PK) -> PaillierKey:
37+
return Paillier.generate(pub.F.order().bit_length() + (pub.n * pub.m).bit_length() + 1)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ extragroup = [
1313

1414
[project]
1515
name = "pymife"
16-
version = "0.0.11"
16+
version = "0.0.12"
1717
authors = [
1818
{ name="mechfrog88", email="kelzzin2@gmail.com" },
1919
]

tests/data/test_paillier.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from tests.test_base import TestBase
2+
from mife.data.paillier import Paillier
3+
import time, logging
4+
5+
class TestPaillier(TestBase):
6+
7+
p = 134571773007924833854065458592286178876746744565913736881821465801779595071970858872308614994816317338556606169429905285264914998387968583302245087560224843241552942789674059901173947740579704064275315217146295711314307876927426902112041290244046572242022612528256950525807940730241959500337908782397375334067
8+
q = 136376041929080745332720207555608615396952127278171064896949721772485191795889286118100472072063058684610764963190170043115874503100144966915460078488572507709185072831129777110117660963433630166361483550907430003279041723670193111462964773640424026516757060999870723874192759460049941352318214316748147414927
9+
10+
def test_basic(self):
11+
start1 = time.time()
12+
sk = Paillier.generate(1024, TestPaillier.p, TestPaillier.q)
13+
pk = sk.getPublicKey()
14+
15+
c = pk.encrypt(3000)
16+
m = sk.decrypt(c)
17+
18+
self.assertEqual(3000, m)
19+
end1 = time.time()
20+
21+
logging.info(f'Paillier Basic : {end1 - start1}s')
22+
23+
def test_homomorphic_add(self):
24+
start1 = time.time()
25+
sk = Paillier.generate(1024, TestPaillier.p, TestPaillier.q)
26+
pk = sk.getPublicKey()
27+
28+
c1 = pk.encrypt(3000)
29+
30+
c2 = pk.encrypt(2000)
31+
c3 = c1 + c2
32+
33+
m = sk.decrypt(c3)
34+
self.assertEqual(m, 5000)
35+
end1 = time.time()
36+
37+
logging.info(f'Paillier Homomorphic Add : {end1 - start1}s')
38+
39+
def test_homomorphic_mul(self):
40+
start1 = time.time()
41+
sk = Paillier.generate(1024, TestPaillier.p, TestPaillier.q)
42+
pk = sk.getPublicKey()
43+
44+
c1 = pk.encrypt(3000)
45+
c2 = 3 * c1
46+
47+
m = sk.decrypt(c2)
48+
self.assertEqual(m, 9000)
49+
end1 = time.time()
50+
51+
logging.info(f'Paillier Homomorphic Mul : {end1 - start1}s')
52+
53+
def test_homomorphic_1(self):
54+
start1 = time.time()
55+
sk = Paillier.generate(1024, TestPaillier.p, TestPaillier.q)
56+
pk = sk.getPublicKey()
57+
58+
c1 = pk.encrypt(1000)
59+
c2 = pk.encrypt(4000)
60+
61+
c3 = 3 * c1 + 2 * c2
62+
63+
m = sk.decrypt(c3)
64+
self.assertEqual(m, 11000)
65+
66+
end1 = time.time()
67+
68+
logging.info(f'Paillier Homomorphic 1 : {end1 - start1}s')
69+
70+
def test_homomorphic_2(self):
71+
start1 = time.time()
72+
sk = Paillier.generate(1024, TestPaillier.p, TestPaillier.q)
73+
pk = sk.getPublicKey()
74+
75+
c1 = pk.encrypt(1000)
76+
c2 = pk.encrypt(4000)
77+
78+
c3 = 0
79+
c3 += 3 * c1
80+
c3 += 2 * c2
81+
82+
m = sk.decrypt(c3)
83+
self.assertEqual(m, 11000)
84+
85+
end1 = time.time()
86+
87+
logging.info(f'Paillier Homomorphic 2 : {end1 - start1}s')

tests/multiclient/decentralized/test_ddh.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def test_scheme_2(self):
6565
for i in range(n):
6666
expected += sum([a * b for a, b in zip(x[i], y[i])])
6767

68+
6869
self.assertEqual(expected, res)
6970

7071
def test_scheme_3(self):

0 commit comments

Comments
 (0)