Skip to content

Commit 541d031

Browse files
committed
Add quadratic scheme
1 parent d4baec5 commit 541d031

6 files changed

Lines changed: 253 additions & 15 deletions

File tree

README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ pip install pymife
1919
### Single input inner product (Function Hiding)
2020
1. (Adaptive Secure) DDH based scheme from https://eprint.iacr.org/2016/440.pdf
2121

22+
### Single input inner product (Quadratic)
23+
1. (Adaptive Secure) DDH based scheme from https://eprint.iacr.org/2018/206.pdf
24+
2225
### Multi input inner product
2326
1. (Adaptive Secure) Damgard based scheme from https://eprint.iacr.org/2017/972.pdf
2427

@@ -105,6 +108,26 @@ c = FeDDH.encrypt(x, key)
105108
sk = FeDDH.keygen(y, key)
106109
m = FeDDH.decrypt(c, key.get_public_key(), sk, (0, 1000))
107110
```
111+
112+
113+
### Single input inner product (Quadratic)
114+
115+
#### DDH based scheme
116+
117+
```python
118+
from mife.single.quadratic.ddh import FeDDH
119+
120+
n = 2
121+
x = [i + 2 for i in range(n)]
122+
y = [i + 3 for i in range(n)]
123+
f = [[i + j + 1 for j in range(n)] for i in range(n)]
124+
key = FeDDH.generate(n)
125+
c = FeDDH.encrypt(x, y, key)
126+
sk = FeDDH.keygen(f, key)
127+
m = FeDDH.decrypt(c, key.get_public_key(), sk, (0, 1000))
128+
```
129+
130+
108131
### Multi input inner product
109132

110133
#### Damgard based scheme
@@ -219,13 +242,17 @@ To use custom group, simply pass the group class to the `generate` function.
219242

220243
This library has implemented prime order group and curve25519 group.
221244

222-
For MCFE-DDH scheme, you can also supply your own hash function by using the same signature as the default hash function found in `/src/mife/multiclient/ddh.py`.
245+
For Random Oracle Model MCFE-DDH scheme, you can also supply your own hash function by using the same signature as the default hash function found in `/src/mife/multiclient/ddh.py`.
246+
247+
For Function Hiding and Quadratic scheme, you can supply your own pairing group better efficiency.
223248

224249
## References
225250

226251
- https://eprint.iacr.org/2015/017.pdf
227252
- https://eprint.iacr.org/2015/608.pdf
253+
- https://eprint.iacr.org/2016/440.pdf
228254
- https://eprint.iacr.org/2017/972.pdf
229255
- https://eprint.iacr.org/2017/989.pdf
256+
- https://eprint.iacr.org/2018/206.pdf
230257
- https://eprint.iacr.org/2019/487.pdf
231258
- https://github.com/fentec-project/CiFEr/blob/master/src/innerprod/simple/lwe.cr2html

mife/data/zmod_r.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,23 +37,28 @@ class _ZmodRElem():
3737

3838
def __init__(self, group: ZmodR, val: mpz):
3939
self.group = group
40-
self.val = val
40+
self._val = val
41+
42+
@property
43+
def val(self):
44+
self._val = self._val % self.group.modulus
45+
return self._val
4146

4247
def __radd__(self, other):
4348
return self.__add__(other)
4449

4550
def __add__(self, other: Self) -> Self:
4651
if isinstance(other, int) or isinstance(other, mpz):
47-
return _ZmodRElem(self.group, (self.val + other) % self.group.modulus)
52+
return _ZmodRElem(self.group, (self._val + other) % self.group.modulus)
4853
if self.group != other.group:
4954
return Exception(f"Addition not define for element of {self.group} and {other.group}")
50-
return _ZmodRElem(self.group, (self.val + other.val) % self.group.modulus)
55+
return _ZmodRElem(self.group, (self._val + other._val) % self.group.modulus)
5156

5257
def __rsub__(self, other):
53-
return _ZmodRElem(self.group, (other - self.val) % self.group.modulus)
58+
return _ZmodRElem(self.group, (other - self._val) % self.group.modulus)
5459

5560
def __neg__(self) -> Self:
56-
return _ZmodRElem(self.group, -self.val)
61+
return _ZmodRElem(self.group, -self._val)
5762

5863
def __sub__(self, other):
5964
return self.__add__(-other)
@@ -63,16 +68,16 @@ def __rmul__(self, other):
6368

6469
def __mul__(self, other: _ZmodRElem):
6570
if isinstance(other, int) or isinstance(other, mpz):
66-
return _ZmodRElem(self.group, (self.val * other) % self.group.modulus)
67-
return _ZmodRElem(self.group, (self.val * other.val) % self.group.modulus)
71+
return _ZmodRElem(self.group, (self._val * other) % self.group.modulus)
72+
return _ZmodRElem(self.group, (self._val * other._val) % self.group.modulus)
6873

6974
def __truediv__(self, other):
7075
if isinstance(other, int):
7176
return self.__mul__(_ZmodRElem(self.group, mpz(inverse(other, self.group.modulus))))
72-
return self.__mul__(_ZmodRElem(self.group, mpz(inverse(other.val, self.group.modulus))))
77+
return self.__mul__(_ZmodRElem(self.group, mpz(inverse(other._val, self.group.modulus))))
7378

7479
def __rtruediv__(self, other):
75-
return _ZmodRElem(self.group, other * mpz(inverse(self.val, self.group.modulus)))
80+
return _ZmodRElem(self.group, other * mpz(inverse(self._val, self.group.modulus)))
7681

7782
def __eq__(self, other):
7883
if (isinstance(other, int) or isinstance(other, mpz)):

mife/single/fhiding/ddh.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def generate(n: int, F: PairingBase = None) -> _FeDDH_MK:
6363
G = ZmodR(F.order())
6464
B = invertible_matrix(G, n)
6565
B_determinant = B.determinant()
66-
B_star = B_determinant.val * B.inverse().T
66+
B_star = int(B_determinant) * B.inverse().T
6767

6868
msk = _FeDDH_MSK(g1, g2, B, B_star, B_determinant)
6969

@@ -87,7 +87,7 @@ def encrypt(x: List[int], key: _FeDDH_MK) -> _FeDDH_C:
8787

8888
x = [key.G(x[i]) for i in range(key.n)]
8989
exponents = Matrix.flatten((Matrix(x) * key.msk.B_star).M)
90-
c2 = [((exponents[i] * beta).val) * key.msk.g2 for i in range(key.n)]
90+
c2 = [int(exponents[i] * beta) * key.msk.g2 for i in range(key.n)]
9191

9292
return _FeDDH_C(c1, c2)
9393

@@ -127,10 +127,10 @@ def keygen(y: List[int], key: _FeDDH_MK) -> _FeDDH_SK:
127127

128128
alpha = randbelow(key.G.order())
129129

130-
k1 = ((alpha * key.msk.B_determinant).val) * key.msk.g1
130+
k1 = (int(alpha * key.msk.B_determinant)) * key.msk.g1
131131

132132
y = [key.G(y[i]) for i in range(key.n)]
133133
exponents = Matrix.flatten((Matrix(y) * key.msk.B).M)
134-
k2 = [((exponents[i] * alpha).val) * key.msk.g1 for i in range(key.n)]
134+
k2 = [int(exponents[i] * alpha) * key.msk.g1 for i in range(key.n)]
135135

136136
return _FeDDH_SK(k1, k2)

mife/single/quadratic/ddh.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
from secrets import randbelow
2+
from typing import List, Tuple
3+
4+
from mife.common import discrete_log_bound, invertible_matrix
5+
from mife.data.pairing import PairingBase
6+
from mife.data.pyecc_bn128_wrapper import Bn128Pairing
7+
from mife.data.matrix import Matrix
8+
from mife.data.group import GroupElem
9+
from mife.data.zmod_r import ZmodR, _ZmodRElem
10+
11+
12+
# References:
13+
# https://eprint.iacr.org/2018/206.pdf
14+
15+
class _FeDDH_MSK:
16+
def __init__(self, s: List[_ZmodRElem], t: List[_ZmodRElem]):
17+
self.s = s
18+
self.t = t
19+
20+
class _FeDDH_MK:
21+
def __init__(self, n: int, F: PairingBase, G: ZmodR, gs: List[GroupElem], gt: List[GroupElem], msk: _FeDDH_MSK = None):
22+
self.n = n
23+
self.F = F
24+
self.G = G
25+
self.gs = gs
26+
self.gt = gt
27+
self.msk = msk
28+
29+
def has_private_key(self) -> bool:
30+
return self.msk is not None
31+
32+
def get_public_key(self):
33+
return _FeDDH_MK(self.n, self.F, self.G, self.gs, self.gt)
34+
35+
36+
class _FeDDH_SK:
37+
def __init__(self, g2f: GroupElem, f: List[List[int]]):
38+
self.g2f = g2f
39+
self.f = f
40+
41+
class _FeDDH_C:
42+
def __init__(self, g1_gamma: GroupElem, c: List[List[GroupElem]]):
43+
self.g1_gamma = g1_gamma
44+
self.c = c
45+
46+
class FeDDH:
47+
48+
@staticmethod
49+
def generate(n: int, F: PairingBase = None) -> _FeDDH_MK:
50+
"""
51+
Generate a FeDDH master key
52+
53+
:param n: Dimension of the encrypt vector
54+
:param F: Group to use for the scheme. If set to None, bn128 will be used
55+
:return: FeDDH master key
56+
"""
57+
if F is None:
58+
F = Bn128Pairing()
59+
60+
G = ZmodR(F.order())
61+
62+
s = [G(randbelow(F.order())) for i in range(n)]
63+
t = [G(randbelow(F.order())) for i in range(n)]
64+
65+
g1 = F.generator1()
66+
g2 = F.generator2()
67+
68+
gs = [int(s[i]) * g1 for i in range(n)]
69+
gt = [int(t[i]) * g2 for i in range(n)]
70+
71+
msk = _FeDDH_MSK(s,t)
72+
73+
return _FeDDH_MK(n, F, G, gs, gt, msk=msk)
74+
75+
@staticmethod
76+
def encrypt(x: List[int], y: List[int], key: _FeDDH_MK) -> _FeDDH_C:
77+
"""
78+
Encrypt FeDDH message vector
79+
80+
:param x: First Message vector
81+
:param y: Second Message vector
82+
:param key: FeDDH master key
83+
:return: FeDDH cipher text
84+
"""
85+
if len(x) != key.n or len(y) != key.n:
86+
raise Exception(f"Encrypt vector must be of length {key.n}")
87+
88+
gamma = randbelow(key.G.order())
89+
W = invertible_matrix(key.G, 2)
90+
W_iT = W.inverse().T
91+
92+
c = [[] for i in range(key.n)]
93+
94+
g1 = key.F.generator1()
95+
g2 = key.F.generator2()
96+
97+
for i in range(key.n):
98+
a = Matrix.flatten((W_iT * Matrix([x[i], gamma * key.msk.s[i]]).T).M)
99+
b = Matrix.flatten((W * Matrix([y[i], -key.msk.t[i]]).T).M)
100+
c[i] = [int(a[0]) * g1, int(a[1]) * g1, int(b[0]) * g2, int(b[1]) * g2]
101+
102+
return _FeDDH_C(gamma * g1, c)
103+
104+
@staticmethod
105+
def decrypt(c: _FeDDH_C, pub: _FeDDH_MK, sk: _FeDDH_SK, bound: Tuple[int, int]) -> int:
106+
"""
107+
Decrypt FeDDH cipher text within a bound
108+
109+
:param c: FeDDH cipher text
110+
:param pub: FeDDH public key
111+
:param sk: FeDDH decryption key
112+
:param bound: Bound for discrete logarithm search, the decrypted text should be within the bound
113+
:return: Decrypted message
114+
"""
115+
out = pub.F.pairing(c.g1_gamma, sk.g2f)
116+
117+
for i in range(pub.n):
118+
for j in range(pub.n):
119+
t = pub.F.pairing(c.c[i][0], c.c[j][2]) + pub.F.pairing(c.c[i][1], c.c[j][3])
120+
out += sk.f[i][j] * t
121+
122+
g1 = pub.F.generator1()
123+
g2 = pub.F.generator2()
124+
125+
return discrete_log_bound(out, pub.F.pairing(g1, g2), bound)
126+
127+
128+
@staticmethod
129+
def keygen(f: List[List[int]], key: _FeDDH_MK) -> _FeDDH_SK:
130+
"""
131+
Generate FeDDH decryption key
132+
133+
:param f: Function vector f[i][j] is the coefficient for x_iy_j
134+
:param key: FeDDH master key
135+
:return: FeDDH decryption key
136+
"""
137+
if len(f) != key.n:
138+
raise Exception(f"Function vector must be of shape {key.n} x {key.n}")
139+
if not key.has_private_key():
140+
raise Exception("Private key not found in master key")
141+
142+
eval = 0
143+
144+
for i in range(key.n):
145+
if len(f[i]) != key.n:
146+
raise Exception(f"Function vector must be of shape {key.n} x {key.n}")
147+
for j in range(key.n):
148+
eval += f[i][j] * key.msk.s[i] * key.msk.t[j]
149+
150+
g2f = int(eval) * key.F.generator2()
151+
152+
return _FeDDH_SK(g2f, f)

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.10"
16+
version = "0.0.11"
1717
authors = [
1818
{ name="mechfrog88", email="kelzzin2@gmail.com" },
1919
]

tests/single/quadratic/test_ddh.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import time
2+
import logging
3+
from tests.test_base import TestBase
4+
from mife.single.quadratic.ddh import FeDDH
5+
6+
class TestFeDDH(TestBase):
7+
8+
def test_scheme_1(self):
9+
start = time.time()
10+
n = 2
11+
x = [i + 2 for i in range(n)]
12+
y = [i + 3 for i in range(n)]
13+
f = [[i + j + 1 for j in range(n)] for i in range(n)]
14+
15+
key = FeDDH.generate(n)
16+
c = FeDDH.encrypt(x, y, key)
17+
sk = FeDDH.keygen(f, key)
18+
m = FeDDH.decrypt(c, key.get_public_key(), sk, (0, 1000))
19+
end = time.time()
20+
21+
logging.info(f'Quadratic FeDDH test scheme 1 performance (n={n}): {end - start}s')
22+
23+
expected = 0
24+
for i in range(n):
25+
for j in range(n):
26+
expected += f[i][j] * x[i] * y[j]
27+
28+
self.assertEqual(expected, m)
29+
30+
def test_scheme_2(self):
31+
start = time.time()
32+
n = 2
33+
x = [i - 2 for i in range(n)]
34+
y = [i + 3 for i in range(n)]
35+
f = [[i - j + 3 for j in range(n)] for i in range(n)]
36+
37+
key = FeDDH.generate(n)
38+
c = FeDDH.encrypt(x, y, key)
39+
sk = FeDDH.keygen(f, key)
40+
m = FeDDH.decrypt(c, key.get_public_key(), sk, (-10000, 10000))
41+
end = time.time()
42+
43+
logging.info(f'Quadratic FeDDH test scheme 2 performance (n={n}): {end - start}s')
44+
45+
expected = 0
46+
for i in range(n):
47+
for j in range(n):
48+
expected += f[i][j] * x[i] * y[j]
49+
50+
print(expected)
51+
52+
self.assertEqual(expected, m)
53+
54+

0 commit comments

Comments
 (0)