-
Notifications
You must be signed in to change notification settings - Fork 2
/
posw.py
282 lines (238 loc) · 8.91 KB
/
posw.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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
import random
import networkx as nx
import math
from util import sha256, sha256H
import secrets
import argparse
"""
Relevant Parameters described in the paper
N: The time parameter which we assume is of the form
2^n-1 for an integer n
H: (0, 1)^{<= w(n+1)} -> (0, 1)^w as a random oracle
t: A statistical security parameter
w: A statistical security parameter
M: Memory available to the prover, of the form
(t + n*t + 1 + 2^{m+1})w,
0 <= m <= n
"""
"""
Need to construct a custom class for binary string,
since for our DAG we have to differentiate 01 and 1.
Therefore we will need both the length of the binary string
and the value converted into an integer.
The {EMPTY} binary string will be length 0, intvalue 0
"""
class BinaryString:
def __init__(self, length, intvalue):
assert(2 ** length > intvalue)
self.length = length
self.intvalue = intvalue
def __eq__(self, other):
return other and self.length == other.length and self.intvalue == other.intvalue
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash((self.length, self.intvalue))
def __gt__(self, other):
if self.length > other.length:
return True
elif self.length < other.length:
return False
return self.intvalue > other.intvalue
def __str__(self):
return ''.join(list(map(str, self.get_bit_list())))
# Flips the n^th least significant bit from 0 to 1 or vice versa
# Returns a new BinaryString object, does not modify original
def flip_bit(self, n):
assert(self.length > n)
if self.get_bit(n) == 0:
self.intvalue = self.intvalue + (2 ** n)
else:
self.intvalue = self.intvalue - (2 ** n)
return BinaryString(self.length, self.intvalue)
def set_bit(self, n, bitvalue):
assert(self.length > n)
assert(bitvalue == 1 or bitvalue == 0)
if bitvalue == 0:
if self.get_bit(self, n) == 1:
self.flip_bit(self, n)
else:
if self.get_bit(self, n) == 0:
self.flip_bit(self, n)
# Gets the nth least significant bit.
def get_bit(self, n):
assert(self.length > n)
return (self.intvalue >> n) % 2
# Gets the list of the bit representation. Returns a list of
# 0s and 1s as integers. Starts with least significant bit.
def get_bit_list(self):
lst = []
curr_int = self.intvalue
for _ in range(self.length):
lst = [curr_int % 2] + lst
curr_int = (curr_int >> 1)
return lst
# Truncates the least significant bit of the binary string. Returns
# a new binary string object.
def truncate_last_bit(self):
new_bin = BinaryString(self.length, self.intvalue)
new_bin.intvalue = (self.intvalue >> 1)
new_bin.length = self.length-1
return new_bin
DEFAULT_w = 10
DEFAULT_t = 2**10 - 1
DEFAULT_n = 10
DEFAULT_m = 10
DEFAULT_N = 2**(DEFAULT_n + 1) - 1
"""
Converts bit list to integer
"""
def bits_to_int(bit_list):
val = 0
for i in range(len(bit_list)):
val *= 2
val += int(bit_list[i])
return val
"""
Get parents of a node in the DAG
"""
def get_parents(binary_str, n=DEFAULT_n):
parents = []
bit_list = binary_str.get_bit_list()
length = binary_str.length
if length == n:
for i in range(1, length + 1):
if bit_list[i - 1] == 1:
parents.append(BinaryString(i, bits_to_int(bit_list[:i - 1] + [0])))
else:
parents.append(BinaryString(length + 1, bits_to_int(bit_list + [0])))
parents.append(BinaryString(length + 1, bits_to_int(bit_list + [1])))
return sorted(parents)
"""
Creates the DAG for a given size of graph
"""
def construct_dag(n=DEFAULT_n):
G = nx.DiGraph()
binstrs = []
for level in range(n+1):
binstrs = [BinaryString(level, i) for i in range(2 ** level)]
G.add_nodes_from(binstrs)
if level > 0:
for node in binstrs:
bit_list = node.get_bit_list()
G.add_edge(node, BinaryString(level - 1, bits_to_int(bit_list[:level - 1])))
for leaf in binstrs:
bit_list = leaf.get_bit_list()
for i in range(1, len(bit_list) + 1):
if bit_list[i - 1] == 1:
G.add_edge(BinaryString(i, bits_to_int(bit_list[:i - 1] + [0])), leaf)
return G
"""
Selects chi from (0, 1)^w as the nonce
"""
def statement(w=DEFAULT_w, secure=True):
if secure:
return secrets.randbelow(2**w - 1)
else:
return random.randint(0, 2**w - 1)
"""
Computes the function PoSW^Hx(N). It stores the the labels
phi_P of the m highest layers, and sends the root label
phi = l_epsilon to the Verifier
"""
def compute_posw(chi, n=DEFAULT_n, H=sha256H):
G = construct_dag(n)
for elem in nx.topological_sort(G):
hash_str = str(elem)
for parent in get_parents(elem, n):
hash_str += str(G.node[parent]['label'])
G.node[elem]['label'] = H(chi, hash_str)
return G
"""
Samples a random challenge gamma <- (0, 1)^{w * t}, essentially a list
of random gamma_1, ..., gamma_t sampled from (0, 1)^w
"""
def opening_challenge(n=DEFAULT_n, t=DEFAULT_t, secure=True, s=None):
if not secure and s:
random.seed(s)
return [BinaryString(n, secrets.randbelow(2**n) if secure else random.randint(0, 2**n - 1)) for _ in range(t)]
"""
Takes in an instance of class BinaryString and returns a list of the
siblings of the nodes of the path to to root of a binary tree. Also
returns the node itself, so there are N+1 items in the list for a
tree with length N.
"""
def path_siblings(bitstring):
path_lst = [bitstring]
new_bitstring = BinaryString(bitstring.length, bitstring.intvalue)
for i in range(bitstring.length):
path_lst += [new_bitstring.flip_bit(0)]
new_bitstring = new_bitstring.truncate_last_bit()
return path_lst
"""
Prover computes tau := open^H(chi, N, phi_P, gamma) and sends it to
the Verifier. phi_P will be passed in using a NetworkX graph G
Returns a list of tuples described by
(l_{gamma_i}, dict{alternate_siblings: l_{the alternate siblings})
"""
def compute_open(chi, G, gamma):
# On a challenge gamma = [gamma_1, ..., gamma_n]
# tau the label of node gamma_i, l_{gamma_i}, and all the
# labels of the siblings of the nodes of path from gamma_i to root.
# Example for gamma_i = 0101
# tau contains labels of: 0101, 0100, 011, 00 and 1
tuple_lst = []
# First get the list
for gamma_i in gamma:
label_gamma_i = G.node[gamma_i]['label']
label_gamma_i_siblings = {}
for sib in path_siblings(gamma_i):
label_gamma_i_siblings[sib] = G.node[sib]['label']
tuple_lst += [(label_gamma_i, label_gamma_i_siblings)]
return tuple_lst
"""
Verifier computes and outputs verify^H(chi, N, phi, gamma, tau)
given either {accept, reject}
We will let accept be True and reject be False
"""
def compute_verify(chi, phi, gamma, tau, n=DEFAULT_n, H=sha256H):
for i in range(len(gamma)):
# Check validity of l_{gamma_i}
tag, s_tags = tau[i]
s_tags[gamma[i]] = tag
hash_str = str(gamma[i])
for parent in get_parents(gamma[i], n):
if parent not in s_tags:
return False
hash_str += s_tags[parent]
if tag != H(chi, hash_str):
return False
# Check "Merkle-like commitment"
for j in reversed(range(n)):
hash_str = str(gamma[i])[:j]
gamma_l_0 = bits_to_int(gamma[i].get_bit_list()[:j] + [0])
gamma_l_1 = bits_to_int(gamma[i].get_bit_list()[:j] + [1])
gamma_l = bits_to_int(gamma[i].get_bit_list()[:j])
hash_str += s_tags[BinaryString(j + 1, gamma_l_0)]
hash_str += s_tags[BinaryString(j + 1, gamma_l_1)]
s_tags[BinaryString(j, gamma_l)] = H(chi, hash_str)
if phi != s_tags[BinaryString(0, 0)]:
return False
return True
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Runs a test prover-verifier proof of sequential work'
+ ' assuming both parties are honest.')
parser.add_argument('-n', type=int, help='depth of the DAG', default=DEFAULT_n)
parser.add_argument('-t', type=int,help='number of challenges in gamma', default=DEFAULT_t)
args = parser.parse_args()
print("\nStarting test run with honest Prover and Verifier...")
chi = statement()
print("\tGenerated statement: {}".format(chi))
G = compute_posw(chi, n=args.n)
print("\tComputed PoSW.".format(chi))
gamma = opening_challenge(n=args.n, t=args.t)
print("\tCreated challenge gamma with {} challenges.".format(len(gamma)))
tau = compute_open(chi, G, gamma)
print("\tComputed proof tau.")
print("\tVerification: {}".format(compute_verify(chi, G.node[BinaryString(0, 0)]['label'], gamma, tau, n=args.n)))
print("")