#Proof Of Discrete Logarithm Equivalence
#For algorithm steps, see https://gist.github.com/AdamISZ/9cbba5e9408d23813ca8
import os
import sys
import hashlib
import json
import struct
from pprint import pformat
from jmbase import jmprint
from jmbitcoin import multiply, add_pubkeys, getG, podle_PublicKey,\
    podle_PrivateKey, N, podle_PublicKey_class
from jmbase import (EXIT_FAILURE, utxostr_to_utxo,
                    utxo_to_utxostr, hextobin, bintohex)

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 binary not hex.
        self.u = u
        if not priv:
            if P:
                self.P = podle_PublicKey(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) == 33 and priv[-1:] == b"\x01":
                priv = priv[:-1]
            self.priv = podle_PrivateKey(priv)
            self.P = self.priv.pub
        if P2:
            self.P2 = podle_PublicKey(P2)
        else:
            self.P2 = None
        self.s = None
        self.e = None
        if s:
            self.s = s
        if e:
            self.e = 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)
        """
        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).digest()
        return self.commitment

    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.
        """
        self.i = index
        #TODO nonce could be rfc6979?
        if not k:
            k = os.urandom(32)
        J = getNUMS(self.i)
        KG = podle_PrivateKey(k).pub
        KJ = multiply(k, J, return_serialized=False)
        self.P2 = getP2(self.priv, J)
        self.get_commitment()
        self.e = hashlib.sha256(b''.join([KG, KJ, self.P, self.P2])).digest()
        k_int, priv_int, e_int = (int.from_bytes(x,
            byteorder="big") for x in [k, self.priv.secret_bytes, self.e])
        sig_int = (k_int + priv_int * e_int) % N
        self.s = (sig_int).to_bytes(32, byteorder="big")
        return self.reveal()

    def reveal(self):
        """Encapsulate all the data representing the proof
        in a dict for client functions.
        """
        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()
        return {'used': self.used,
                'utxo': self.u,
                'P': self.P,
                'P2': self.P2,
                'commit': self.commitment,
                'sig': self.s,
                'e': self.e}

    def serialize_revelation(self, separator='|'):
        """ Outputs the over-the-wire format as used in
        Joinmarket communication protocol.
        """
        state_dict = self.reveal()
        success, utxo = utxo_to_utxostr(state_dict["utxo"])
        assert success, "invalid utxo in PoDLE"
        ser_list = [utxo]
        ser_list += [bintohex(state_dict[x]) for x in ["P", "P2", "sig", "e"]]
        ser_string = separator.join(ser_list)
        return ser_string

    @classmethod
    def deserialize_revelation(cls, ser_rev, separator='|'):
        """ Reads the over-the-wire format as used in
        Joinmarket communication protocol.
        """
        ser_list = ser_rev.split(separator)
        if len(ser_list) != 5:
            raise PoDLEError("Failed to deserialize, wrong format")
        utxostr, P, P2, s, e = ser_list
        success, utxo = utxostr_to_utxo(utxostr)
        assert success, "invalid utxo format in PoDLE."
        return {'utxo': utxo, 'P': hextobin(P),
                'P2': hextobin(P2), 'sig': hextobin(s), 'e': hextobin(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.pub
            sJ = multiply(self.s, J)
            e_int = int.from_bytes(self.e, byteorder="big")
            minus_e = (-e_int % N).to_bytes(32, byteorder="big")
            minus_e_P = multiply(minus_e, self.P)
            minus_e_P2 = multiply(minus_e, self.P2)
            KGser = add_pubkeys([sG, minus_e_P])
            KJser = add_pubkeys([sJ, minus_e_P2])
            #check 2: e =?= H(K_G || K_J || P || P2)
            e_check = hashlib.sha256(KGser + KJser + self.P +
                                     self.P2).digest()
            if e_check == self.e:
                return True
        #commitment fails for any NUMS in the provided range
        return False

    def __repr__(self):
        """ Specified here to allow logging.
        """
        # note: will throw if not fully initalised
        r = self.reveal()
        success, utxo = utxo_to_utxostr(r["utxo"])
        assert success, "invalid utxo in PoDLE."
        return pformat({'used': r["used"],
                'utxo': utxo,
                'P': bintohex(r["P"]),
                'P2': bintohex(r["P2"]),
                'commit': bintohex(r["commit"]),
                'sig': bintohex(r["sig"]),
                'e': bintohex(r["e"])})

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)
                # CPubKey does not throw ValueError or otherwise
                # on invalid initialization data; it must be inspected:
                assert nums_point.is_fullyvalid()
                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] = bintohex(getNUMS(i))
    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_bytes
    return multiply(priv_raw,
                    nums_pt,
                    return_serialized=False)

# functions which interact with the external persistence of podle data:

def switch_external_dict_format(ed, utxo_converter, hexbinconverter):
    """External dict has structure:
    {txid:N:{'P':pubkey, 'reveal':{1:{'P2':P2,'s':s,'e':e}, 2:{..},..}}}
    This function switches between readable/writable in file (strings, hex)
    and that used internally.
    """
    retval = {}
    for u in ed:
        success, u2 = utxo_converter(u)
        assert success, "invalid utxo format in external dict parsing."
        retval[u2] = {"P": hexbinconverter(ed[u]["P"])}
        retval[u2]["reveal"] = {}
        for i in ed[u]["reveal"]:
            # hack: python json does not allow int dict keys, they must
            # be str, so when reading from file, we convert back to int:
            if not isinstance(i, int):
                j = int(i)
            else:
                j = i
            retval[u2]["reveal"][j] = {
                "P2": hexbinconverter(ed[u]["reveal"][i]["P2"]),
                "s": hexbinconverter(ed[u]["reveal"][i]["s"]),
                "e": hexbinconverter(ed[u]["reveal"][i]["e"])}
    return retval

def external_dict_to_file(ed):
    """ Converts internal format of dict to one writable/readable
    in file.
    """
    return switch_external_dict_format(ed, utxo_to_utxostr, bintohex)

def external_dict_from_file(ed):
    """ Takes the external dict extracted through json deserialization
    from a file and converts it to internal format:
    {txid:N:{'P':pubkey, 'reveal':{1:{'P2':P2,'s':s,'e':e}, 2:{..},..}}}
    """
    return switch_external_dict_format(ed, utxostr_to_utxo, hextobin)

def write_to_podle_file(used, external):
    """ Update persisted commitment data in PODLE_COMMIT_FILE.
    """
    to_write = {}
    to_write['used'] = [bintohex(x) for x in used]
    externalfmt = external_dict_to_file(external)
    to_write['external'] = externalfmt
    with open(PODLE_COMMIT_FILE, "wb") as f:
        f.write(json.dumps(to_write, indent=4).encode('utf-8'))

def read_from_podle_file():
    """ Returns used commitment list and external commitments dict
    struct currently stored in PODLE_COMMIT_FILE.
    """
    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(EXIT_FAILURE)
            if 'used' not in c.keys() or 'external' not in c.keys():
                raise PoDLEError("Incorrectly formatted file: " + PODLE_COMMIT_FILE)

        used = [hextobin(x) for x in c["used"]]
        external = external_dict_from_file(c["external"])
        return (used, external)
    return ([], {})

def get_podle_commitments():
    """Returns set of commitments used as a list:
    [H(P2),..] 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 ([], {})
    return read_from_podle_file()

def add_external_commitments(ecs):
    """To allow external functions to add
    PoDLE commitments that were calculated elsewhere.
    """
    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.
    """
    commitments, external = read_from_podle_file()
    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)
    write_to_podle_file(commitments, external)

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)):
                p = PoDLE(u=utxo, P=ec["P"], P2=ec["reveal"][i]["P2"],
                          s=ec["reveal"][i]["s"], e=ec["reveal"][i]["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)
        p.generate_podle(index)
        #persist for future checks
        update_commitments(commitment=p.commitment)
        return p
    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
            ec = external_commitments[u]
            ecri = ec["reveal"][tries]
            p = PoDLE(u=u, P=ec["P"], P2=ecri["P2"], s=ecri["s"], e=ecri["e"])
            update_commitments(commitment=p.get_commitment())
            return p
    #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)
    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'
}
