forked from ethereum/research
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathipa_utils.py
237 lines (174 loc) · 6.9 KB
/
ipa_utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
from bandersnatch import Point, Scalar
from poly_utils import PrimeField
import hashlib
import time
#
# Utilities for dealing with polynomials in evaluation form
#
# A polynomial in evaluation for is defined by its values on DOMAIN,
# where DOMAIN is [0, 1, 2, ...]
#
# Any polynomial of degree < WIDTH can be represented uniquely in this form,
# and many operations (such as multiplication and exact division) are more
# efficient.
#
#
def hash(x):
if isinstance(x, bytes):
return hashlib.sha256(x).digest()
elif isinstance(x, Point):
return hash(x.serialize())
b = b""
for a in x:
if isinstance(a, bytes):
b += a
elif isinstance(a, int):
b += a.to_bytes(32, "little")
elif isinstance(a, Point):
b += a.serialize()
return hash(b)
class IPAUtils():
"""
Class that defines helper functions for IPA proofs in evaluation form (Lagrange basis)
"""
def __init__(self, BASIS_G, BASIS_Q, primefield):
self.MODULUS = primefield.MODULUS
self.BASIS_G = BASIS_G
self.BASIS_Q = BASIS_Q
self.WIDTH = primefield.WIDTH
self.DOMAIN = primefield.DOMAIN
self.primefield = primefield
def hash_to_field(self, x):
return int.from_bytes(hash(x), "little") % self.MODULUS
def pedersen_commit(self, a):
"""
Returns a Pedersen commitment to the vector a (defined by its coefficients)
"""
return Point().msm(self.BASIS_G, [Scalar().from_int(x) for x in a])
def pedersen_commit_sparse(self, values):
"""
Returns a Pedersen commitment to the vector a (defined by its coefficients)
"""
if len(values) < 5:
if len(values) == 0:
return Point().mul(0)
else:
it = iter(values.items())
k, v = next(it)
r = self.BASIS_G[k].dup().glv(v)
for k, v in it:
r = r.add(self.BASIS_G[k].dup().glv(v))
return r
return Point().msm([self.BASIS_G[i] for i in values.keys()], [Scalar().from_int(x) for x in values.values()])
def pedersen_commit_basis(self, a, basis):
"""
Returns a Pedersen commitment to the vector a (defined by its coefficients)
"""
return Point().msm(basis, [Scalar().from_int(x) for x in a])
def f_g_coefs(self, xinv_vec):
f_g_coefs = []
for i in range(len(self.DOMAIN)):
binary = [int(x) for x in bin(i)[2:].rjust(len(xinv_vec), '0')]
coef = 1
for xinv, b in zip(xinv_vec, binary):
if b == 1:
coef = coef * xinv % self.MODULUS
f_g_coefs.append(coef)
return f_g_coefs
def check_ipa_proof(self, C, z, y, proof):
"""
Check the IPA proof for a commitment to a Polynomial in evaluation form
"""
n = len(self.DOMAIN)
m = n // 2
b = self.primefield.barycentric_formula_constants(z)
w = self.hash_to_field([C, z, y])
q = self.BASIS_Q.dup().glv(w)
current_commitment = C.dup().add(q.dup().glv(y))
current_basis = self.BASIS_G
i = 0
xs = []
xinvs = []
while n > 1:
C_L, C_R = [Point().deserialize(C) for C in proof[i]]
x = self.hash_to_field([C_L, C_R])
xinv = self.primefield.inv(x)
xs.append(x)
xinvs.append(xinv)
current_commitment = current_commitment.dup().add(C_L.dup().glv(x)).add(C_R.dup().glv(xinv))
n = m
m = n // 2
i = i + 1
f_g_coefs = self.f_g_coefs(xinvs)
g_l = Point().msm(self.BASIS_G, f_g_coefs)
b_l = self.inner_product(b, f_g_coefs)
a_l = proof[-1][0]
a_l_times_b_l = a_l * b_l % self.MODULUS
computed_commitment = g_l.glv(a_l).add(q.glv(a_l_times_b_l))
return current_commitment == computed_commitment
def inner_product(self, a, b):
return sum(x * y % self.MODULUS for x, y in zip(a, b)) % self.MODULUS
def evaluate_and_compute_ipa_proof(self, C, f_eval, z):
"""
Evaluates a function f (given in evaluation form) at a point z (which can be in the DOMAIN or not)
and gives y = f(z) as well as an IPA proof that this is the correct result
"""
assert len(f_eval) == len(self.DOMAIN)
n = len(self.DOMAIN)
m = n // 2
a = f_eval[:]
b = self.primefield.barycentric_formula_constants(z)
y = self.inner_product(a, b)
proof = []
w = self.hash_to_field([C, z, y])
q = self.BASIS_Q.dup().glv(w)
current_basis = self.BASIS_G
while n > 1:
# Reduction step
a_L = a[:m]
a_R = a[m:]
b_L = b[:m]
b_R = b[m:]
z_L = self.inner_product(a_R, b_L)
z_R = self.inner_product(a_L, b_R)
C_L = self.pedersen_commit_basis(a_R, current_basis[:m]).add(q.dup().glv(z_L))
C_R = self.pedersen_commit_basis(a_L, current_basis[m:]).add(q.dup().glv(z_R))
proof.append([C_L.serialize(), C_R.serialize()])
x = self.hash_to_field([C_L, C_R])
xinv = self.primefield.inv(x)
# Compute updates for next round
a = [(v + x * w) % self.MODULUS for v, w in zip(a_L, a_R)]
b = [(v + xinv * w) % self.MODULUS for v, w in zip(b_L, b_R)]
current_basis = [v.dup().add(w.dup().glv(xinv)) for v, w in zip(current_basis[:m], current_basis[m:])]
n = m
m = n // 2
# Final step
proof.append([a[0]])
return y, proof
if __name__ == "__main__":
MODULUS = 13108968793781547619861935127046491459309155893440570251786403306729687672801
WIDTH = 256
time_a = time.time()
BASIS_G = [Point(generator=False) for i in range(WIDTH)]
BASIS_Q = Point(generator=False)
time_b = time.time()
print("Basis computed in {:.2f} ms".format((time_b - time_a)*1000))
time_a = time.time()
primefield = PrimeField(MODULUS, WIDTH)
time_b = time.time()
print("Lagrange precomputes in {:.2f} ms".format((time_b - time_a)*1000))
ipautils = IPAUtils(BASIS_G, BASIS_Q, primefield)
poly = [3, 4, 3, 2, 1, 3, 3, 3]*32
poly_eval = [primefield.eval_poly_at(poly, x) for x in primefield.DOMAIN]
time_a = time.time()
C = ipautils.pedersen_commit(poly_eval)
time_b = time.time()
print("Pedersen commitment computed in {:.2f} ms".format((time_b - time_a)*1000))
time_a = time.time()
y, proof = ipautils.evaluate_and_compute_ipa_proof(C, poly_eval, 17)
time_b = time.time()
print("Evaluation and proof computed in {:.2f} ms".format((time_b - time_a)*1000))
time_a = time.time()
assert ipautils.check_ipa_proof(C, 17, y, proof)
time_b = time.time()
print("Proof verified in {:.2f} ms".format((time_b - time_a)*1000))