You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
680 lines
36 KiB
680 lines
36 KiB
#!/usr/bin/env python |
|
from __future__ import (absolute_import, division, |
|
print_function, unicode_literals) |
|
from builtins import * # noqa: F401 |
|
#Proof Of Discrete Logarithm Equivalence |
|
#For algorithm steps, see https://gist.github.com/AdamISZ/9cbba5e9408d23813ca8 |
|
import os |
|
import sys |
|
import hashlib |
|
import json |
|
import binascii |
|
import struct |
|
from jmbase import jmprint |
|
from jmbitcoin import multiply, add_pubkeys, getG, podle_PublicKey,\ |
|
podle_PrivateKey, encode, decode, N, podle_PublicKey_class |
|
|
|
|
|
PODLE_COMMIT_FILE = None |
|
|
|
|
|
def set_commitment_file(file_loc): |
|
global PODLE_COMMIT_FILE |
|
PODLE_COMMIT_FILE = file_loc |
|
|
|
|
|
def get_commitment_file(): |
|
return PODLE_COMMIT_FILE |
|
|
|
|
|
class PoDLEError(Exception): |
|
pass |
|
|
|
|
|
class PoDLE(object): |
|
"""See the comment to PoDLE.generate_podle for the |
|
mathematical structure. This class encapsulates the |
|
input data, the commitment and the opening (the "proof"). |
|
""" |
|
|
|
def __init__(self, |
|
u=None, |
|
priv=None, |
|
P=None, |
|
P2=None, |
|
s=None, |
|
e=None, |
|
used=False): |
|
#This class allows storing of utxo in format "txid:n" only for |
|
#convenience of storage/access; it doesn't check or use the data. |
|
#Arguments must be provided in hex. |
|
self.u = u |
|
if not priv: |
|
if P: |
|
#Construct a pubkey from raw hex |
|
self.P = podle_PublicKey(binascii.unhexlify(P)) |
|
else: |
|
self.P = None |
|
else: |
|
if P: |
|
raise PoDLEError("Pubkey should not be provided with privkey") |
|
#any other formatting abnormality will just throw in PrivateKey |
|
if len(priv) == 66 and priv[-2:] == '01': |
|
priv = priv[:-2] |
|
self.priv = podle_PrivateKey(binascii.unhexlify(priv)) |
|
self.P = self.priv.public_key |
|
if P2: |
|
self.P2 = podle_PublicKey(binascii.unhexlify(P2)) |
|
else: |
|
self.P2 = None |
|
#These sig values should be passed in hex. |
|
self.s = None |
|
self.e = None |
|
if s: |
|
self.s = binascii.unhexlify(s) |
|
if e: |
|
self.e = binascii.unhexlify(e) |
|
#Optionally maintain usage state (boolean) |
|
self.used = used |
|
#the H(P2) value |
|
self.commitment = None |
|
|
|
def get_commitment(self): |
|
"""Set the commitment to sha256(serialization of public key P2) |
|
Return in hex to calling function |
|
""" |
|
if not self.P2: |
|
raise PoDLEError("Cannot construct commitment, no P2 available") |
|
if not isinstance(self.P2, podle_PublicKey_class): |
|
raise PoDLEError("Cannot construct commitment, P2 is not a pubkey") |
|
self.commitment = hashlib.sha256(self.P2.format()).digest() |
|
return binascii.hexlify(self.commitment).decode('ascii') |
|
|
|
def generate_podle(self, index=0, k=None): |
|
"""Given a raw private key, in hex format, |
|
construct a commitment sha256(P2), which is |
|
the hash of the value x*J, where x is the private |
|
key as a raw scalar, and J is a NUMS alternative |
|
basepoint on the Elliptic Curve; we use J(i) where i |
|
is an index, so as to be able to create multiple |
|
commitments against the same privkey. The procedure |
|
for generating the J(i) value is shown in getNUMS(). |
|
Also construct a signature (s,e) of Schnorr type, |
|
which will serve as a zero knowledge proof that the |
|
private key of P2 is the same as the private key of P (=x*G). |
|
Signature is constructed as: |
|
s = k + x*e |
|
where k is a standard 32 byte nonce and: |
|
e = sha256(k*G || k*J || P || P2) |
|
|
|
Possibly Joinmarket specific comment: |
|
Users *should* generate with lower indices first, |
|
since verifiers will give preference to lower indices |
|
(each verifier may have their own policy about how high |
|
an index to allow, which really means how many reuses of utxos |
|
to allow in Joinmarket). |
|
|
|
Returns a commitment of form H(P2) which, note, will depend |
|
on the index choice. Repeated calls will reset the commitment |
|
and the associated signature data that can be used to open |
|
the commitment. |
|
""" |
|
#TODO nonce could be rfc6979? |
|
if not k: |
|
k = os.urandom(32) |
|
J = getNUMS(index) |
|
KG = podle_PrivateKey(k).public_key |
|
KJ = multiply(k, J.format(), False, return_serialized=False) |
|
self.P2 = getP2(self.priv, J) |
|
self.get_commitment() |
|
self.e = hashlib.sha256(b''.join([x.format( |
|
) for x in [KG, KJ, self.P, self.P2]])).digest() |
|
k_int = decode(k, 256) |
|
priv_int = decode(self.priv.secret, 256) |
|
e_int = decode(self.e, 256) |
|
sig_int = (k_int + priv_int * e_int) % N |
|
self.s = encode(sig_int, 256, minlen=32) |
|
return self.reveal() |
|
|
|
def reveal(self): |
|
"""Encapsulate all the data representing the proof |
|
in a dict for client functions. Data output in hex. |
|
""" |
|
if not all([self.u, self.P, self.P2, self.s, self.e]): |
|
raise PoDLEError("Cannot generate proof, data is missing") |
|
if not self.commitment: |
|
self.get_commitment() |
|
Phex, P2hex, shex, ehex, commit = [ |
|
binascii.hexlify(x).decode('ascii') |
|
for x in [self.P.format(), self.P2.format(), self.s, self.e, |
|
self.commitment] |
|
] |
|
return {'used': str(self.used), |
|
'utxo': self.u, |
|
'P': Phex, |
|
'P2': P2hex, |
|
'commit': commit, |
|
'sig': shex, |
|
'e': ehex} |
|
|
|
def serialize_revelation(self, separator='|'): |
|
state_dict = self.reveal() |
|
ser_list = [] |
|
for k in ['utxo', 'P', 'P2', 'sig', 'e']: |
|
ser_list += [state_dict[k]] |
|
ser_string = separator.join(ser_list) |
|
return ser_string |
|
|
|
@classmethod |
|
def deserialize_revelation(cls, ser_rev, separator='|'): |
|
ser_list = ser_rev.split(separator) |
|
if len(ser_list) != 5: |
|
raise PoDLEError("Failed to deserialize, wrong format") |
|
utxo, P, P2, s, e = ser_list |
|
return {'utxo': utxo, 'P': P, 'P2': P2, 'sig': s, 'e': e} |
|
|
|
def verify(self, commitment, index_range): |
|
"""For an object created without a private key, |
|
check that the opened commitment verifies for at least |
|
one NUMS point as defined by the range in index_range |
|
""" |
|
if not all([self.P, self.P2, self.s, self.e]): |
|
raise PoDLEError("Verify called without sufficient data") |
|
if not self.get_commitment() == commitment: |
|
return False |
|
for J in [getNUMS(i) for i in index_range]: |
|
sig_priv = podle_PrivateKey(self.s) |
|
sG = sig_priv.public_key |
|
sJ = multiply(self.s, J.format(), False) |
|
e_int = decode(self.e, 256) |
|
minus_e = encode(-e_int % N, 256, minlen=32) |
|
minus_e_P = multiply(minus_e, self.P.format(), False) |
|
minus_e_P2 = multiply(minus_e, self.P2.format(), False) |
|
KGser = add_pubkeys([sG.format(), minus_e_P], False) |
|
KJser = add_pubkeys([sJ, minus_e_P2], False) |
|
#check 2: e =?= H(K_G || K_J || P || P2) |
|
e_check = hashlib.sha256(KGser + KJser + self.P.format() + |
|
self.P2.format()).digest() |
|
if e_check == self.e: |
|
return True |
|
#commitment fails for any NUMS in the provided range |
|
return False |
|
|
|
|
|
def getNUMS(index=0): |
|
"""Taking secp256k1's G as a seed, |
|
either in compressed or uncompressed form, |
|
append "index" as a byte, and append a second byte "counter" |
|
try to create a new NUMS base point from the sha256 of that |
|
bytestring. Loop counter and alternate compressed/uncompressed |
|
until finding a valid curve point. The first such point is |
|
considered as "the" NUMS base point alternative for this index value. |
|
|
|
The search process is of course deterministic/repeatable, so |
|
it's fine to just store a list of all the correct values for |
|
each index, but for transparency left in code for initialization |
|
by any user. |
|
|
|
The NUMS generator generated is returned as a secp256k1.PublicKey. |
|
""" |
|
|
|
assert index in range(256) |
|
nums_point = None |
|
for G in [getG(True), getG(False)]: |
|
seed = G + struct.pack(b'B', index) |
|
for counter in range(256): |
|
seed_c = seed + struct.pack(b'B', counter) |
|
hashed_seed = hashlib.sha256(seed_c).digest() |
|
#Every x-coord on the curve has two y-values, encoded |
|
#in compressed form with 02/03 parity byte. We just |
|
#choose the former. |
|
claimed_point = b"\x02" + hashed_seed |
|
try: |
|
nums_point = podle_PublicKey(claimed_point) |
|
return nums_point |
|
except: |
|
continue |
|
assert False, "It seems inconceivable, doesn't it?" # pragma: no cover |
|
|
|
|
|
def verify_all_NUMS(write=False): |
|
"""Check that the algorithm produces the expected NUMS |
|
values; more a sanity check than anything since if the file |
|
is modified, all of it could be; this function is mostly |
|
for testing, but runs fast with pre-computed context so can |
|
be run in user code too. |
|
""" |
|
nums_points = {} |
|
for i in range(256): |
|
nums_points[i] = binascii.hexlify(getNUMS(i).format()).decode('ascii') |
|
if write: |
|
with open("nums_basepoints.txt", "wb") as f: |
|
from pprint import pformat |
|
f.write(pformat(nums_points).encode('utf-8')) |
|
assert nums_points == precomp_NUMS, "Precomputed NUMS points are not valid!" |
|
|
|
|
|
def getP2(priv, nums_pt): |
|
"""Given a secp256k1.PrivateKey priv and a |
|
secp256k1.PublicKey nums_pt, an alternate |
|
generator point (note: it's in no sense a |
|
pubkey, its privkey is unknowable - that's |
|
just the most easy way to manipulate it in the |
|
library), calculate priv*nums_pt |
|
""" |
|
priv_raw = priv.secret |
|
return multiply(priv_raw, |
|
nums_pt.format(), |
|
False, |
|
return_serialized=False) |
|
|
|
|
|
def get_podle_commitments(): |
|
"""Returns set of commitments used as a list: |
|
[H(P2),..] (hex) and a dict of all existing external commitments. |
|
It is presumed that each H(P2) can |
|
be used only once (this may not literally be true, but represents |
|
good joinmarket "citizenship"). |
|
This is stored as part of the data in PODLE_COMMIT_FILE |
|
Since takers request transactions serially there should be no |
|
locking requirement here. Multiple simultaneous taker bots |
|
would require extra attention. |
|
""" |
|
if not os.path.isfile(PODLE_COMMIT_FILE): |
|
return ([], {}) |
|
with open(PODLE_COMMIT_FILE, "rb") as f: |
|
c = json.loads(f.read().decode('utf-8')) |
|
if 'used' not in c.keys() or 'external' not in c.keys(): |
|
raise PoDLEError("Incorrectly formatted file: " + PODLE_COMMIT_FILE) |
|
return (c['used'], c['external']) |
|
|
|
|
|
def add_external_commitments(ecs): |
|
"""To allow external functions to add |
|
PoDLE commitments that were calculated elsewhere; |
|
the format of each entry in ecs must be: |
|
{txid:N:{'P':pubkey, 'reveal':{1:{'P2':P2,'s':s,'e':e}, 2:{..},..}}} |
|
""" |
|
update_commitments(external_to_add=ecs) |
|
|
|
|
|
def update_commitments(commitment=None, |
|
external_to_remove=None, |
|
external_to_add=None): |
|
"""Optionally add the commitment commitment to the list of 'used', |
|
and optionally remove the available external commitment |
|
whose key value is the utxo in external_to_remove, |
|
persist updated entries to disk. |
|
""" |
|
c = {} |
|
if os.path.isfile(PODLE_COMMIT_FILE): |
|
with open(PODLE_COMMIT_FILE, "rb") as f: |
|
try: |
|
c = json.loads(f.read().decode('utf-8')) |
|
except ValueError: #pragma: no cover |
|
#Exit conditions cannot be included in tests. |
|
jmprint("the file: " + PODLE_COMMIT_FILE + " is not valid json.", |
|
"error") |
|
sys.exit(0) |
|
|
|
if 'used' in c: |
|
commitments = c['used'] |
|
else: |
|
commitments = [] |
|
if 'external' in c: |
|
external = c['external'] |
|
else: |
|
external = {} |
|
if commitment: |
|
commitments.append(commitment) |
|
#remove repeats |
|
commitments = list(set(commitments)) |
|
if external_to_remove: |
|
external = { |
|
k: v |
|
for k, v in external.items() if k not in external_to_remove |
|
} |
|
if external_to_add: |
|
external.update(external_to_add) |
|
to_write = {} |
|
to_write['used'] = commitments |
|
to_write['external'] = external |
|
with open(PODLE_COMMIT_FILE, "wb") as f: |
|
f.write(json.dumps(to_write, indent=4).encode('utf-8')) |
|
|
|
def get_podle_tries(utxo, priv=None, max_tries=1, external=False): |
|
used_commitments, external_commitments = get_podle_commitments() |
|
|
|
if external: |
|
if utxo in external_commitments: |
|
ec = external_commitments[utxo] |
|
#use as many as were provided in the file, up to a max of max_tries |
|
m = min([len(ec['reveal'].keys()), max_tries]) |
|
for i in reversed(range(m)): |
|
key = str(i) |
|
p = PoDLE(u=utxo,P=ec['P'],P2=ec['reveal'][key]['P2'], |
|
s=ec['reveal'][key]['s'], e=ec['reveal'][key]['e']) |
|
if p.get_commitment() in used_commitments: |
|
return i+1 |
|
else: |
|
for i in reversed(range(max_tries)): |
|
p = PoDLE(u=utxo, priv=priv) |
|
c = p.generate_podle(i) |
|
if c['commit'] in used_commitments: |
|
return i+1 |
|
return 0 |
|
|
|
def generate_podle(priv_utxo_pairs, max_tries=1, allow_external=None, k=None): |
|
"""Given a list of privkeys, try to generate a |
|
PoDLE which is not yet used more than max_tries times. |
|
This effectively means satisfying two criteria: |
|
(1) the generated commitment is not in the list of used |
|
commitments |
|
(2) the index required to generate is not greater than 'max_tries'. |
|
Note that each retry means using a different generator |
|
(see notes in PoDLE.generate_podle) |
|
Once used, add the commitment to the list of used. |
|
If we fail to find an unused commitment with this algorithm, |
|
we fallback to sourcing an unused commitment from the "external" |
|
section of the commitments file; if we succeed in finding an unused |
|
one there, use it and add it to the list of used commitments. |
|
If still nothing available, return None. |
|
""" |
|
used_commitments, external_commitments = get_podle_commitments() |
|
for priv, utxo in priv_utxo_pairs: |
|
tries = get_podle_tries(utxo, priv, max_tries) |
|
if tries >= max_tries: |
|
continue |
|
#Note that we will return the *lowest* index |
|
#which is still available. |
|
index = tries |
|
p = PoDLE(u=utxo, priv=priv) |
|
c = p.generate_podle(index) |
|
#persist for future checks |
|
update_commitments(commitment=c['commit']) |
|
return c |
|
if allow_external: |
|
for u in allow_external: |
|
tries = get_podle_tries(utxo=u, max_tries=max_tries, external=True) |
|
if (tries >= max_tries): |
|
#If none of the entries in the 'reveal' list for this external |
|
#commitment were available, they've all been used up, so |
|
#remove this entry |
|
update_commitments(external_to_remove=u) |
|
continue |
|
index = str(tries) |
|
ec = external_commitments[u] |
|
p = PoDLE(u=u,P=ec['P'],P2=ec['reveal'][index]['P2'], |
|
s=ec['reveal'][index]['s'], e=ec['reveal'][index]['e']) |
|
update_commitments(commitment=p.get_commitment()) |
|
return p.reveal() |
|
#Failed to find any non-used valid commitment: |
|
return None |
|
|
|
|
|
def verify_podle(Pser, P2ser, sig, e, commitment, index_range=range(10)): |
|
verifying_podle = PoDLE(P=Pser, P2=P2ser, s=sig, e=e) |
|
#check 1: Hash(P2ser) =?= commitment |
|
if not verifying_podle.verify(commitment, index_range): |
|
return False |
|
return True |
|
|
|
|
|
precomp_NUMS = { |
|
0: '0296f47ec8e6d6a9c3379c2ce983a6752bcfa88d46f2a6ffe0dd12c9ae76d01a1f', |
|
1: '023f9976b86d3f1426638da600348d96dc1f1eb0bd5614cc50db9e9a067c0464a2', |
|
2: '023745b000f6db094a794d9ee08637d714393cd009f86087438ac3804e929bfe89', |
|
3: '023346660dcb1f8d56e44d23f93c3ad79761cdd5f4972a638e9e15517832f6a165', |
|
4: '02ec91c86964dcbb077c8193156f3cfa91476d5adfcfcf64913a4b082c75d5bca7', |
|
5: '02bbc5c4393395a38446e2bd4d638b7bfd864afb5ffaf4bed4caf797df0e657434', |
|
6: '02967efd39dc59e6f060bf3bd0080e8ecf4a22b9d1754924572b3e51ce2cde2096', |
|
7: '02cfce8a7f9b8a1735c4d827cd84e3f2a444de1d1f7ed419d23c88d72de341357f', |
|
8: '0206d6d6b1d88936bb6013ae835716f554d864954ea336e3e0141fefb2175b82f9', |
|
9: '021b739f21b981c2dcbaf9af4d89223a282939a92aee079e94a46c273759e5b42e', |
|
10: '025d72106845e03c3747f1416e539c5aa0712d858e7762807fdc4f3757fd980631', |
|
11: '02e7d4defb5d287734a0f96c2b390aa14f5f38e80c5a5e592e4ce10d55a5f5246b', |
|
12: '023c1bf301bcfa0f097f1a3931c68b4fd39b77a28cc7b61b2b1e0b7ca6d332493c', |
|
13: '0283ac2cdd6b362c90665802c264ee8e6342318070943717faee62ef9addeff3e9', |
|
14: '02cb9f6164cd2acdf071caef9deab870fc3d390a09b37ba7af8e91139b817ce807', |
|
15: '02f0a3a3e22c5b04b6fe97430d68f33861c3e9be412220dc2a24485ea5d55d94db', |
|
16: '02860ca3475757d90d999e6553e62c07fce5a6598d060cceeead08c8689b928095', |
|
17: '0246c8eabc38ce6a93868369d5900d84f36b2407eecb81286a25eb22684355b41d', |
|
18: '026aa6379d74e6cd6c721aef82a34341d1d15f0c96600566ad3fa8e9c43cbb5505', |
|
19: '02fdeacb3b4d15e0aae1a1d257b4861bcc9addb5dc3780a13eb982eb656f73d741', |
|
20: '021a83ecfaeb2c057f66a6b0d4a42bff3fe5fda11fe2eea9734f45f255444cddc0', |
|
21: '02d93580f3e0c2ec8ea461492415cc6a4be00c50969e2c32a2135e7d04f112309a', |
|
22: '0292c57be6c3e6ba8b44cf5e619529cf75e9c6b795ddecd383fb78f9059812cb3f', |
|
23: '02480f099771d0034d657f6b00cd17c7315b033b19bed9ca95897bc8189928dd47', |
|
24: '02ac0701cdc6f96c63752c01dc8400eab19431dfa15f85a7314b1e9a3df69a4a66', |
|
25: '026a304ceb69e37d655c1ef100d7ad23192867151983ab0d168af96afe7f1997f6', |
|
26: '023b9ff8e4a853b29ecae1e8312fae53863e86b8f8cb3155f31f7325ffb2baf02c', |
|
27: '021894ce66d61c33e439f38a36d92c0e45bf28dbc7e30bfb4d7135b87fc8e890e1', |
|
28: '02d9e7680e583cf904774d4c19f36cb3d238b6c770e1e7db03f444dc8b15b29687', |
|
29: '024350c7ff5b2bf2c58e3b17a792716d0e76cff7ad537375d1abc6e249466b25a3', |
|
30: '02c6577e1cdcbcfadb0ae037d01fbf6d74786eecdb9d1ee277d9ba69b969728cfe', |
|
31: '029f395b4c7b20bcb6120b57bee6d2f7353cd0aa9fe246176064068c1bd9b714d1', |
|
32: '02d180786087720b827bf04ae800547102470a1e43de314203e90228c586b481a1', |
|
33: '023548173a673965c18d994028bc6d5f5df1f60dccf9368b0eae34f8cff3106943', |
|
34: '02118124c53b86fdade932c4304ad347a19ce0af79a9ab885d7d3a6358a396e360', |
|
35: '02930bcdee5887fa5a258335d6948017e6d7f2665b32dcc76a84d5ca7cd604d89b', |
|
36: '0267e79a47058758a8ee240afd941e0ae8b4f175f29a3cf195ad6ff0e6d02955b1', |
|
37: '027e53d9fb04f1bb69324245306d26aa60172fd13d8fe27809b093222226914de6', |
|
38: '02ef09fbdcd22e1be4f0d4b2d13a141051b18009d7001f6828c6a40b145c9df23e', |
|
39: '028742fd08c60ba13e78913581db19af2f708c7ec53364589f6cbcf9d1c8b5105f', |
|
40: '020ce14308d2f516bf4f9e0944fb104907adef8f4c319bfcc3afab73e874a9ce4a', |
|
41: '027635f125f05a2548201f74c4bbdcbe89561204117bd8b82dfae29c85a576a58e', |
|
42: '02fe878f3ae59747ee8e9c34876b86851d5396124e1411f86fe5c58f08f413a549', |
|
43: '02f2a6af33bd08ab41a010d785694e9682fa1cc65733f30a53c40541d1c1bfb660', |
|
44: '02cbe9d18b6d5fc9993ef862892e5b2b1ea5d2710a4f208672c0f7c36a08bb5686', |
|
45: '023fb079b25c0a8241465fb55802f22ebb354e6da81f7dabfe214ddbd9d3dfcd5a', |
|
46: '021a5b234b9a10fc5f08ed9c1a136a250e92156adc12109a97dd7467276d6848a8', |
|
47: '0240fbe9363d50585da40aef95f311fc2795550e787f62421cd9b6e2f719bb9547', |
|
48: '02a245fbbc00f1d6feb72a9e1d3fd0033522839d33440aea64f52e8bccee616be8', |
|
49: '02fd1e94bb23a4306de64064841165e3db497ae5b246dabff738eb3e6ea51685a7', |
|
50: '0298362705914c839e45505369e54faefbb3aaebb4c486b4d6e59ca03304f3552c', |
|
51: '021b8109a23b858114d287273620dd920029d84b90f63af273c1c78492b1a70105', |
|
52: '028df6ce4fec30229cddb86c62606cff80e95cb8277028277f3dcc8ac9f98eef9d', |
|
53: '02ed02925d806df4ac764769d11743093708808157fb2933eb19af5399dcfd500c', |
|
54: '02ce88da0e81988bd8f5d63ad06898a355f7dc7f46bb08cf5f1e9bc5c3752ad13c', |
|
55: '02f4868cc8285cd8d74d4213d18d53d5f410d50223818f1be6fe8090904e03743d', |
|
56: '02770cecdf18aa2115b6e5c4295468f2e92a53068dc4295d0e5d0890b71d1a2fcc', |
|
57: '02b5d4dce8932de37c6ef13a7f063f164dfd07f7399e8e815e22b5af420608fd2a', |
|
58: '0284ad07924dbac50a72455aec3ddba50b1ed71e678ba935bb5d95c8a8232b1353', |
|
59: '02cb8c916a6f9bc39c8825f5b0378bb1b0a0679e191843aa4db2195b81f14c87e0', |
|
60: '0235aa30ec3df8dd193a132dbaf3b351af879c59504ed8b7b5ad5f1f1ea712854f', |
|
61: '02df91206e955cefe7bcda4555fc6ad761b0e98d464629f098d4483306851704e9', |
|
62: '02ed4f1fccd47e66a8d74e58b4f6e31b5172b628fc0dacdb408128c914eb80f506', |
|
63: '0263991bb62aaca78a128917f5c4e15183f98aefddf04070c5ca537186f1c1a97a', |
|
64: '02ffe2b017882d57db27864446ad7b21d3855ae64bddf74d46e3a611bf903580be', |
|
65: '02d647aba2c01eecd0fac7e82580dd8b92d66db7341d1b65a5e4b01234f1fbb2cd', |
|
66: '023134ff85401dba9aff426d3f3ba292ea59684b8c48ea0b495660797a839246a6', |
|
67: '02827880fe0410c9ea84f75a629f8f8e6eed1f26528af421cf23b8ecf93b6b4b7b', |
|
68: '02859b3f9f1f5ba6aa0787f8d3f3f2f21b4932c40bc36b6669383e3bbd19654a5f', |
|
69: '02a7d204dfc3eed44abd0419202e280f6772efd5acf9fd34331b8f47c81c6dab19', |
|
70: '02e15d11b443a9340ac31a8c5774ce34cd347834470c8d68c959828fae3a7eb0c6', |
|
71: '029931f65e46627d60519bfd08bd8a1bb3d8d2921f7f8c9ef31f4bfcdd8028ead2', |
|
72: '02e5415ba78743d736018f19757ee0e1ca5d4a4fb1d0464cd3eea8d89b34dd37b8', |
|
73: '027ea7860afc3de502d056d9a19ca330f16cd61cfefbeb768df68a882d1f8f15f5', |
|
74: '026c19becac43626582622e2b7e86ebd8056f40aa8ab031e70f4deae8cab34503f', |
|
75: '02098dab044c888ddebe6713fcb8481f178e3ba42d63310b08d8234e20fe1de13f', |
|
76: '02ed6af1a2bebcb381ce92f87638267b1afefe7a1cdce16253f5bf9f99a84ce4b2', |
|
77: '023d8493f9e72cd3212166de50940d980f603ae429309abb29e15cccc1983efe37', |
|
78: '025c07d7513b1bae52a0089a4faee127148e2ba5a651983083aedc1ae8403cf1eb', |
|
79: '0285a93a8c8e6134b3a53c5bd1b5b7d24e7911763ea887847c5d66af172ed17f10', |
|
80: '02fea28fb142aa95fcd44398c9482a3c185ec22fee8f24ad6b2297ac7478423f21', |
|
81: '02f9840a1635ae3fa131405526974d40d2edee17adf58278956373ce6c69757c2a', |
|
82: '023579e441a7dcdbd36a2932c64fa3318023b1f3d04daab148622b7646246a6d7c', |
|
83: '02bcbc2933f90a88996c1363c8d3a7004e0c6b75040041201fb022e45acb0af6a7', |
|
84: '02cd52e0d28f5564fc2bf842fa63dfefbcf2bb5fe0325703c132be5cd14cca7291', |
|
85: '021e648e261b93fedd3439352899c0fa1acedd1f68ab508050a13ed3cbbc93c2ff', |
|
86: '0295f9caea5f57d11b12ddee154a36a14921a8980fa76726e48e1d76443d4e306f', |
|
87: '02396edf4c18283dd3ef68a2c57b642bd87ae9f8b6be5e5fe4a41c5b86c5db8eb2', |
|
88: '0264f323ca3eee79385c9bfd35cd4cf576e51722f38dd98531d531a29913e5170d', |
|
89: '02facd3f63f543e0ab9b13323340113acbe8ed3bafdfabdc80626cdd15386c80f3', |
|
90: '02b6762640f96367fbf65eecfafcee5c6f7d6a42b706113053bb36a882659d3e65', |
|
91: '02ed63f2eca15d9b338fcdb9b3efa3b326e173a1390706258829680f7973fa851c', |
|
92: '026f6d47d0d48ff13d64ec6a1db2dc51173cee86ab8010a17809b3fe01483d9fc5', |
|
93: '02814e7cae580a1ef86d6ee9b2f9f26fe771e8ea47acf11153b04680ada9cd3042', |
|
94: '020e46225fb3ee8f04d08ffbe12d9092ff7f7227f9cb55709890c669e8a1c97963', |
|
95: '028194469e8d6ee660e95d6125ba0152ad5c24bf7e452adf80db7062d6926851c4', |
|
96: '02b3e1f5754562635ebeecfd32edb0d84a79b2f0c270bac153e60dd29334dc2663', |
|
97: '02afff20730724a2d422f330e962362e7831753545ac0a931dd94be011ccf93e9c', |
|
98: '02a9cfdf0471a34babfc2f6201dbc79530f3f319204daedb7ec05effc2bdfc5a74', |
|
99: '02838fe450f2dd0c460b5fae90ec2feb5b7f001f9cd14c01a475c492cf16ea594b', |
|
100: '02aacc3145d04972d0527c4458629d328219feda92bef6ef6025878e3a252e105a', |
|
101: '02720fe09616d4325d3c4c702a0aeafbbbff95ef962af531c5ae9461ec81fdf8c5', |
|
102: '02e6408f24461a6c484f6c4493c992d303211d5e4297d34afede719a2b70c96c14', |
|
103: '02b9ecf2d3fdf2611c6d4be441a0f9a3810dadae39feb3c0d855748cc2dd98a968', |
|
104: '027a32d12a536af038631890a9b90ee20b219c9c8231a95b1cde24c143d8173fec', |
|
105: '02d26c98fb50b57b7defdf1e8062a52b2a859ba42f3d1760ee8ff99c4e9eb3ec03', |
|
106: '02df85556e8d1e97a8093e4d9950905ebced0ea9a1e49728713df1974eeb455774', |
|
107: '021fe1dbada397155a80225b59b4fb9a32450a991b2d9d11d8500e98344927c856', |
|
108: '0211ccd0980a9ab6f4bb82fdc2e2d1ddace063a7bc1914a6ab4d02b0fa1ca746ec', |
|
109: '0264bd41f41aad19f8bfd290fd3af346ebbf80efd33f515854f82bd57e9740f7aa', |
|
110: '0226d5fb607cadb8720e900ce9afb9607386ad7b767e4ab3a4e0966223324b92eb', |
|
111: '02b3bbf2e2ceae25701bd3b78ba13bea3f0dfed7581b8a8a67c66de9fd96ee41e2', |
|
112: '024b8dd765e385d0e04772f3dbf1b1a82abc2de3e5740baac1f6306cd9fd45fe99', |
|
113: '022153f6a884ae893ebb0642a84d624c0b62894d7cb9e2a48a3a0c4696e593f9db', |
|
114: '0245e22b6388cb14c9c8dbcac94853bdf1e81816c07e926a82b96fc958aa874626', |
|
115: '02cba97826b089c695b1acffdcdbf1484beec5eb95853fea1535d6d7bdb4e678b0', |
|
116: '02ed006fbab2d18adbd96d2f1de6b83948e2a47acc8d2f92d7af9ba01ffae58276', |
|
117: '02513592f4434ee62802d3965f847684693000830107c72cd8de4b34e05b532dae', |
|
118: '028adc75647453a247bd44855abb56b60794aaed5ce21c9898e62adac7adcfbe8e', |
|
119: '02a712d5dc572086359f1688e8e7b9a5f7fc3079644aea27cdddb382208fee885b', |
|
120: '029abf8551218c9076f6d344baa099041fe73e5e844aac6e5c20240834105cdf60', |
|
121: '027d480071a2d128c51e84c380467e1ac8435f05b985bbfee0099d35b4121fb0ca', |
|
122: '02a7f2e4253fa0d833beca742e210c0d59a4ffc8559764766dcffb1aa3e4961826', |
|
123: '023521309a6bdfafdf7bdae574a5f6010eb992e4bae46d8f83c478eac137889270', |
|
124: '02b99fe8623aa19ca2bed6fe435ae95c5072a40193913bebe5466f675c92a31db7', |
|
125: '02dc035112a2b4881917ea1db159e7f35ee9d98d31533e1285ca150ce84e538e4f', |
|
126: '0291a07ecce8061561624de7348135b9081c5edd61541b24fa002fb6c074318fec', |
|
127: '020d8a5253d7e0166aa37680a5f64cab0cdad2cdc4c0e8ae61d310df4c4f7386eb', |
|
128: '026285db47fee60b5ad54cbd4c27a4e0cd723b86a920f03b12dc9b8c5f19f06448', |
|
129: '020f94a9df4302f701b4629f74d401484daf84c7aabaf533f8c21c1626009e923c', |
|
130: '027bb78af54b01ddad4e96b51a4e024105b373aab7e1a6ec16279967fcbbb096b4', |
|
131: '02e1b20c0da3b8c991f8909fd0d31874be00e9fcb130d7c28b8ad53326cdf13755', |
|
132: '02bbdd4dfc047f216e2cbff789bcf850423bedf2006d959963f75621810fecf0d9', |
|
133: '024e1fe4b23feda8651a467090e0ce7e8b8db2ccb1c27d52255c76754aa1940d1b', |
|
134: '0241aad8f575556c49c4fefae178c2c38541962bfff2ca84ebecea9f661ccf3536', |
|
135: '02bcf6203d725ca0640bd045389e854e00087c54ba01fd739c6ef685b22f89340c', |
|
136: '0202178e6b3a9b498399aa392b32dc9010f1eea322a6d439ad0c8cacf2008b3e34', |
|
137: '026db3289d470df0fdf04f5f608fae2d7ec4ddbd3de2603f6685789520bdee01fc', |
|
138: '0239bcfc796488129e3b2f01e6fbbda2f1b357b602e94b5091b44c916e9806dc34', |
|
139: '020513bc4a618d32d784083f13d46e6c6d547f01b24942351760f6dc42e2bb7167', |
|
140: '0204d2495e4fc20e0571ab2fcb4c1989fdda4542923aa97fe1a77a11c79ade1964', |
|
141: '021eaa6af99ea4f1143a45a1b5af7b2d3c3e8810f358be6261248c5ba2492a7b4e', |
|
142: '02799849e87e3862170add5b28a3b7e54b04cc60c2cec39de7eca9bfdfaaf930a8', |
|
143: '02639bced287084268136c5b6e9e22f743b6c8f813e6aabe39521715bfa4a46ab8', |
|
144: '0283c8b21fc038c1fbeedfae0b3abc4dbde672b0dcfda540f9fcfcf8c6e6d29fc3', |
|
145: '02b284f4510535ff98e683f25c08b7ae7dd19f7b861e70a202469ddfb2877bc729', |
|
146: '0256af1c82cde40ffd03564368b8256a5e48ef056df2655013f0b1aa15de1de8d2', |
|
147: '02964b55eab2f19518ee735cae2f7f780bfab480bcbd360f7a90a2904301203366', |
|
148: '02f046486f4a473f2226f6bd120aafc55a5c8651f3eb0855aa6a821f69f3016cc6', |
|
149: '02eb8dfb7c59fbf24671e258ca5e8eda3ea74c5f0455eed4987cfda79f4fcf823f', |
|
150: '020fac2c37cc273d982c07b2719a3694348629d5bdaebc22967fb9d0e1d7f01842', |
|
151: '025c0c8ff9a102f99f700081526d2b93b9d51caf81dcf4d02e93cf83b4a7ff5c92', |
|
152: '02a118f5fa9c5ef02707e021f9cb8056e69018ef145bec80ead4e09c06a60050c1', |
|
153: '029ea72333d1908bb082bffec9da8824883df76a89709ab090df86c45be4abf784', |
|
154: '02bacc52256e5221dbfc9a3f22e30fa8e86ddd38e3877e3dc41de91bdcf989b00b', |
|
155: '02bc8b37dc66e2296ae706c896f5b86bd335f724cfa9783e41b9dc5e901b42b1de', |
|
156: '02eca1099cea9bcab80820d6b64aec16dce1efa0e997b589f6dba3a8fd391fb100', |
|
157: '027f1c1bb99bd1a0e486f415f8960d45614a6fcac8cedc260e07197733844827d0', |
|
158: '021fc54df458bcfafc8a83d4759224c49c4b338cf23cd9825d6e9cdeffc276375b', |
|
159: '027d4fff88da831999ba9b21e19baf747dc26ea76651146e463d4f3e51c586ee91', |
|
160: '02e49c0fef0ebc52908cdcea4d913a42e5f24439fffdfaa21cc55a6add0ad9d122', |
|
161: '0208b5e8e5035fdb62517d4ebab0696775dbfbdba8ff80f2031c1156cda195a2ab', |
|
162: '0202e990bab267fff1575d6acc76fe2041f4196f4b17678872f9c160d930e5be35', |
|
163: '02c73fcedd9f6eabc8fe4e1e7211cdb0f28967391d200147d46e4077d2915c262d', |
|
164: '0261490abc5f14387ef585f42d99dbddb0837b166694d4af521086a1ffd46e5640', |
|
165: '02b46a143e4e0af20a12c39f3105aca57ca79f9332df67619ee859b5d9bffb6d6d', |
|
166: '0299f53c064d068f003f8871acae31b84ddda9d8dbe516d02dc170c70314ee2af7', |
|
167: '023305144dccba65c67001474ee1135aa96432f386b5eb27582393b2ed4bfc185d', |
|
168: '02e044b70ff7e9c784b3c40d09bdfadd4a037e692b0b3aa9ab6bb91203f86a0b37', |
|
169: '02ded067a2e44282b0d731a28ffbd03ca6046c5b1a262887ea7cab4810050fbb8c', |
|
170: '02e00e4c9198194d92a93059bce61f8249e1006eee287aa94fe51bb207462e5492', |
|
171: '0241b89d9164f4c07595ca99b7d73cad2b20ac39847cf703dff1d7d6add339ebeb', |
|
172: '02eba24cd4946e149025a9bf7759df5362245bf7c53c5a3205be0c92c59db8d5dc', |
|
173: '026bd40c611246a789521c46d758a80337ff40bb298a964612b2af74039211727a', |
|
174: '02b9095e071e4edfddf8afb0e176536957509d23f90fb7175ad086b4098e731c73', |
|
175: '0214ad0014dfddc5c7eb0801b97268c1b7e03d64215d6b9d5ed80b468089e4a01d', |
|
176: '02c455b8e38103ade8794fb51a1656e1439b42bdf79afd17a9df8542153914a7cf', |
|
177: '02cc89d6437fdcf711a76eb16f4014f2e21b71740afc8b3ec13ccb60a45b12d815', |
|
178: '0208eee5857dda0ae1c721e6ed4c74044add4e1ce66f105413e9ef1cccbdca87ad', |
|
179: '02edc663693827cad44d004ac24753bfc3167f81ff4074bb862453376593229c0f', |
|
180: '0202a4b7fb31e30b6d8f90a5442ef31f800902ea7a9511e24437b7a0ef516f79a9', |
|
181: '02ff05472c2019ac2c9ab8b7fcb0604a94b7379c350306be262144588ea252d0f4', |
|
182: '02b131bb594a1270d231e18459e484c49f3eca3b3b2291c9be81c01dc8a4037fa1', |
|
183: '02f50125277ea19f633e93868cf8e8a4cd76b21eedf8e3ef59de43f40d73a01d01', |
|
184: '027aab228a7d6f87003b01fb9c0b9bcfb2098adbc76f5f9b856aedd28077fc4471', |
|
185: '02925200e4f74bea719a99f4a0b05165b9af475f2187381bd0b79cad4d5f2593b6', |
|
186: '02c311f1750c6d5c364b71c3b0f369f6959d34a3718da695c5b227ecf1a4669bf6', |
|
187: '02cb030c71169d0a1ae30ffba92311bc06bb64b27570598dedabdea0b24631a0ca', |
|
188: '02e64669898eecff7aa887307be696a694f61559e7ca41119677b7e94f37cd2914', |
|
189: '028fe93e32c24df7f8aaf8d777335fd9ce9f9b5c121dec2ab1ff21575c047497e7', |
|
190: '026f08c1c3cb4cff5cdbd7985db4a8ebf0ebc0924530b0fa118d095c4667efeb52', |
|
191: '02afe08dbba6c999efb73aeae1da0ad8b143a1b51759caffd3ed2de4494adc47fb', |
|
192: '02e99aec0b5e869b3885a3b9f527fd3c546dde83d41a5a156703d0da5e10e04743', |
|
193: '02b7e5f4cb9233107bf7a47789dca4eb811af108822f2d4bd03dec13251ec45984', |
|
194: '023b971e135daa0b851797b17e3a1cc5ac8a9a6207a2e784a0fe36732a00407b49', |
|
195: '02b1742739bfbb528b2a2731cb5d5f1bd03f4fa9c94607837e586c7c6f6589be4a', |
|
196: '022cd1b023bb2afc68ee27b40f8deb1d1c6d7b7aa97c32c444f1ceebd449dbeb22', |
|
197: '02704e21f8bf38158d7e8100e297adfc930c14c8791beee9b907407f4ca654d95b', |
|
198: '02caabeb678374ca75bd815c370b2e37fb0470591557219d6289b1b1e655ed80c6', |
|
199: '026aa8d45112aa0da335054194c739e04787526250493f5a0eaaa8a346541d1a0f', |
|
200: '022fb12408355439bbee33066bbeefcffb0bdc9cfd1950510fd2a42bdc4eaa1d53', |
|
201: '02639fe47769f7694ca6dbfd934762472391d70b23868a58e11d2bd46373e1df29', |
|
202: '02f75360f52df674247c5f005b3451ee47becf3204862154d4e7ee97a0e40df3d2', |
|
203: '0230241e27d0d3ad727d26472541fcd48f2bb128db5611237fa9f33f86ede8d5c9', |
|
204: '0255d5a0aa37a226c001f6b7f19e2bddb10aeaa0652430b8defe35c3f03dfb3c0e', |
|
205: '024e6faa398b0acf8a8dfdd9d21e0a46a22d07cd0fcffd89749f74f94f9993f4d9', |
|
206: '020c1a256587306f58f274cc2238f651bbfadfd42436e6eb8f318ac08fae04e7ae', |
|
207: '025858b8188da173e8b01b8713b154ffae8b2d2eb8f9670362877102cf0c0c4f28', |
|
208: '02dc7509c77d7fa61c08c5525fb151bf4fe12deb1989a3be560a63105dae2ecd2e', |
|
209: '02a272df6dab1c22c209b45b601737c0077acb7869bb9fe264c991b4ef199e337d', |
|
210: '025168f2fdd730b4c33b57d3956e6a40dd27a4f32db70d9f9b5898fa2bed3de342', |
|
211: '028133baac70bc2c2ebe8a22af04b5faedd070e276c90e2f910bb9bf89441a80db', |
|
212: '029064628ebd6e97a945c1d52641a27bff3c4f59659e657b88d23c2ce1c4d04644', |
|
213: '023cf20c4e8675bce999a0128602fe21699db651540f3dcbe7a4ef2126243ba17a', |
|
214: '02cc685739a4b20e2d52ddf256e597c06b7eb69e65d009820c6744b739c7215340', |
|
215: '02d061544ce21398af3e0e6c329ce49976a9ecd804ebc543f4c16f6a32798f37c2', |
|
216: '029fe49ff440f23c69360a92d249db429bdc3601fc8a5a3fc1aa894de817c05490', |
|
217: '0222c8c4e90585f9816b5801bad43fb608857269fdaaefbe2b5b85903231685679', |
|
218: '0296b72ed4968860b733fb99846698df2e95c65af281b3ef8b5ab90e2d5de966cb', |
|
219: '02c27565a7fd5d1f4bcbe969bddbace99553fb65cb7750965350ff230b1f09f97d', |
|
220: '02e1254be9833236609bf44c62ef6da7188a44bbe2d53a72cf39a38ef9f99bb783', |
|
221: '0280663ce16afadc77e00ade780da53e7c11b02a66cbf36837ef7d9d2488f23417', |
|
222: '02ad8b11e62c6753917307bdde89a42896e0070d33f6f93c608d82f6d041b814a4', |
|
223: '02ce1d943dfc14654266507def2b7b9940bffceb4f54d709a149f99962083398fc', |
|
224: '023ea7eb26248c05beb4e4d8ba9f9785d5fd1a55d3137c90f40b807b60aa4262df', |
|
225: '0211c802fec9b31710d3849e2c1700cea5374ae422e54551946d96fc240c63fba0', |
|
226: '02204ad97ebe2ec30d6db1bfc1e1d4660331909668634c3cd928b5c369a6013367', |
|
227: '020251bf4271d359a082cdad23d9a5cd48916d78eed010fe1e7d9711cd420b3cdf', |
|
228: '0292b9757195350676e447e49425f887d3df7e27774bb3e0aab5b528da0a1a0340', |
|
229: '022be18362b2a167199a76f6065358063b1167d5bbcfe7652fc55f93a5ebd42e89', |
|
230: '02e6b1e618efe5f468bdb40f5ec167ed4fa7636849c4ff4ddab0199c903b37306c', |
|
231: '02a6676873de91890ecae000c575e46e4a9629865fb1662606da5e9c1fdcd55d5c', |
|
232: '02c088a3c96b13413caa5f32a8f4640e76ec0a37990577d679d2062e859547f058', |
|
233: '023e9703ed6209d5a25e0ecb34e04c22f274f37845aa2a4e2f2343e39928360e25', |
|
234: '02977d845787c4690152827bfd15e801044c84d33430a7ed928499e828cf131d14', |
|
235: '0224ea648555445d1305aaf6bd74fda3041b2a10bf7900a4c067462b01c6dc25f1', |
|
236: '02dfd472c98ece1dc2a18c1bebf98a09990fba673e725c029928937247022b9d24', |
|
237: '02a2a03933d06617adcf0f4ad692e95d463a5fa9938e8d451e5d6271f4a5af8bb4', |
|
238: '02ca24fa8d7aa53f7f5b4e1ca16eb6fd9b9cfb0162a332abb7a88ddf8e964c99bc', |
|
239: '02bbce92d1db3ef0c9c09793b760fd3b929c9168e4dff396c618fa0ed3cf6a5edb', |
|
240: '028af15d26d3b297f4d2aeaf308632b60251accf87aa8470b3d4d1ef2dabb99209', |
|
241: '021b81c0e878389231339fd9d622a736fc9d36de93a58ea6a4bc38fef86672278a', |
|
242: '021adc24309f605c7a5af106e8b930feaec0bec6545fb4c70b83ebe5cf341cab2d', |
|
243: '020462a3ff101ac379f87f43190459b7494f4128ea30035877ce22a35afb995e34', |
|
244: '02f1019851779a6d0db09e8abeba3b9a07b6931b43b0d973cfe261a96b4516cca4', |
|
245: '02d7023276f01ff22a9efeadd5b539d1d9ceb80ebf6813e6042a49c946a82f366f', |
|
246: '021594f45af3a21e0210a2ca4cbc3e95ea95db5aca3561fc1f759cb7f104dd0f62', |
|
247: '021398309b6c293c0dc28cdd7e55ad06306b59cb9c10d947df565e4a90f095a62a', |
|
248: '029f39d84383200e841187c5b0564e3b01a2ba019b86221c0c1dd3eae1b4dabb26', |
|
249: '0252ec719852f71c2d58886dd6ace6461a64677a368b7b8e220da005ac977abdc8', |
|
250: '0237f0d7de84b2cc6d2109b7241c3d49479066a09d1412c7a4734192715b021e06', |
|
251: '021e9e0e4784d15a29721c9a33fbcfb0af305d559c98a38dcf0ce647edd2c50caa', |
|
252: '02e705994a78f7942726209947d62d64edd062acfa8a708c21ac65de71e7ae71df', |
|
253: '0295f1cafd97e026341af3670ef750de4c44c82e6882f65908ec167d93d7056806', |
|
254: '023a0d381598e185bbff88494dc54e0a083d3b9ce9c8c4b86b5a4c9d5f949b1828', |
|
255: '02a0a8694820c794852110e5939a2c03f8482f81ed57396042c6b34557f6eb430a' |
|
}
|
|
|