__version__ = '2.0.1'
__author__  = "Avinash Kak (kak@purdue.edu)"
__date__    = '2018-March-24'
__url__     = 'https://engineering.purdue.edu/kak/distCeroCoin/CeroCoinClient-2.0.1.html'
__copyright__ = "(C) 2018 Avinash Kak. Python Software Foundation."



import ctypes
import sys,os,socket,signal,time
import threading
import string
import PrimeGenerator    # bundled with the CeroCoinClient module
import SHA256            # a BitVector based implementation bundled with the CeroCoinClient module
import random
import binascii
import functools

def interrupt_sig_handler( signum, frame ):                  
    print("=======> terminating all threads with kill")
    os.kill( os.getpid(), signal.SIGKILL )                           
    
signal.signal( signal.SIGINT,  interrupt_sig_handler )       

def message_hash(message):
    hasher = SHA256.SHA256(message = message)
    return hasher.sha256()  

def gcd(a,b):
    while b:
        a,b = b, a%b
    return a

def MI(num, mod):
    '''
    This function uses ordinary integer arithmetic implementation of the
    Extended Euclid's Algorithm to find the MI of the first-arg integer
    vis-a-vis the second-arg integer.
    '''
    NUM = num; MOD = mod
    x, x_old = 0, 1
    y, y_old = 1, 0
    while mod:
        q = num // mod
        num, mod = mod, num % mod
        x, x_old = x_old - q * x, x
        y, y_old = y_old - q * y, y
    if num == 1:
        MI = (x_old + MOD) % MOD
        return MI
    else:
        sys.exit("MI called with two args when first arg has no MI w.r.t the second arg")

def modular_exp(a,b,n):
    '''
    Python comes with a highly efficient implementation of modular exponentiation.  Nonetheless,
    let's have our own in order to make this demonstration consistent with the other demos in
    my class on computer and network security. The code snippet shown below is from
    Lecture 12 notes.
    '''
    result = 1
    while b > 0:
        if b & 1:
            result = (result * a) % n
        b = b >> 1
        a = (a * a) % n
    return result

lock = threading.Lock()

##########################################  class CeroCoinClient  ############################################

class CeroCoinClient( object ):                                     

    def __init__( self, **kwargs ):                                     
        run_miner_only=modulus_size=pub_exponent=None
        transaction=transactions=block=starting_pow_difficulty=debug=cerocoin_network=None
        max_iterations_debug_mode=num_transactions_in_block=None    
        local_ip_address=None
        if 'no_mining_just_trading' in kwargs    : no_mining_just_trading = kwargs.pop('no_mining_just_trading')
        if 'run_miner_only' in kwargs            : run_miner_only = kwargs.pop('run_miner_only')
        if 'cerocoin_network' in kwargs          : cerocoin_network = kwargs.pop('cerocoin_network')
        if 'modulus_size' in kwargs              : modulus_size = kwargs.pop('modulus_size')            
        if 'pub_exponent' in kwargs              : public_exponent = kwargs.pop('pub_exponent')            
        if 'starting_pow_difficulty' in kwargs   : starting_pow_difficulty = kwargs.pop('starting_pow_difficulty')
        if 'local_ip_address' in kwargs          : local_ip_address = kwargs.pop('local_ip_address')    
        if 'max_iterations_debug_mode' in kwargs : max_iterations_debug_mode = kwargs.pop('max_iterations_debug_mode')
        if 'num_transactions_in_block' in kwargs : num_transactions_in_block = kwargs.pop('num_transactions_in_block')
        if 'debug' in kwargs                     :     debug = kwargs.pop('debug')          
        self.local_ip_address                       =  local_ip_address
        self.run_miner_only                         =  run_miner_only 
        self.cerocoin_network                       =  cerocoin_network
        self.max_iterations_debug_mode              =  max_iterations_debug_mode if max_iterations_debug_mode else 200
        self.modulus_size                           =  modulus_size 
        self.prime_size                             =  modulus_size >> 1 if modulus_size else None
        self.p                                      =  None
        self.q                                      =  None
        self.modulus                                =  None
        self.pub_exponent                           =  pub_exponent if pub_exponent else 65537       
        self.priv_exponent                          =  None
        self.totient                                =  None
        self.pub_key                                =  None
        self.priv_key                               =  None
        self.pub_key_string                         =  None
        self.priv_key_string                        =  None
        self.ID                                     =  None
        self.pow_difficulty                         =  starting_pow_difficulty if starting_pow_difficulty else 251
        self.num_transactions_in_block              =  num_transactions_in_block
        self.transaction                            =  transaction
        self.prev_transaction                       =  None
        self.transactions_generated                 =  []
        self.transactions_received                  =  []
        self.num_transactions_sent                  =  0
        self.coins_acquired_from_others             =  []
        self.block                                  =  None
        self.prev_block                             =  None
        self.blockchain_length                      =  0
        self.coins_mined                            =  []
        self.coins_currently_owned_digitally_signed =  []
        self.nonce                                  =  None
        self.outgoing_client_sockets                =  []
        self.server_socket                          =  None
        self.server_port                            =  6404
        self.t_miner                                =  None
        self.miner_iteration_index                  =  0
        self.transactor_flag                        =  False
        self.blockmaker_flag                        =  False
        self.blockvalidator_flag                    =  False
        self.debug                                  =  debug

    #initialize
    def initialize_node_and_run(self):
        self.local_ip_address = self.find_local_ip_address()
        t_miner = None
        self.gen_rsa_primes(modulus_size = self.modulus_size)
        self.gen_private_exponent()
        self.gen_key_pair()
        self.write_key_pair_to_files()
        self.set_my_id()
        t_server =  ThreadedServer(self)
        t_server.daemon = True
        t_server.start()
        t_scanner = ThreadedScanner(self)
        t_scanner.daemon = True
        t_scanner.start()
        t_miner   = ThreadedMiner( self )
        t_transactor = ThreadedTransactor( self )
        t_blocks     = ThreadedBlockMaker( self )
        t_miner.daemon = True
        t_miner.start()
        t_transactor.daemon = True
        t_transactor.start()
        t_blocks.daemon = True
        t_blocks.start()
        self.t_miner = t_miner
        self.t_transactor = t_transactor
        while True: 
            time.sleep(1)
            
    def initialize_node_and_run_miner_only(self):
        '''
        Only used for testing
        '''
        print("\n\nInitializing the node and running only the miner")
        self.gen_rsa_primes(modulus_size = self.modulus_size)
        self.gen_private_exponent()
        self.gen_key_pair()
        self.write_key_pair_to_files()
        self.set_my_id()
        self.t_miner   = ThreadedMiner( self )
        self.t_miner.daemon = True
        self.t_miner.start()
        t_miner_supervisor = ThreadedMinerSupervisor( self )
        t_miner_supervisor.daemon = True
        t_miner_supervisor.start()
        while True: 
            time.sleep(1)

    def find_local_ip_address(self):
        '''
        Finds the local IP address for the host on which the CeroCoin client is running. 
        The IPv4 address '8.8.8.8' that you see in the second statement shown below is for 
        the Google Public DNS. Using a connection with this DNS in order to figure out your 
        own IPv4 address is an idea I picked up at stackoverflow.com.
        '''
        soc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        soc.connect(("8.8.8.8", 80))
        local_addr  = soc.getsockname()[0]
        print("\nlocal IPv4 address: %s" % local_addr)
        soc.close()
        while True:
            ans = None
            if sys.version_info[0] == 3:
                ans = input("\nIs %s for your local IPv4 address correct? Enter 'y' for 'yes' and 'n' for 'no': " % local_addr)
            else:
                ans = raw_input("\nIs %s for your local IPv4 address correct? Enter 'y' for 'yes' and 'n' for 'no': " % local_addr)
            ans = ans.strip()
            if ans == 'y' or ans == 'yes':
                return local_addr
            elif ans == 'n' or ans == 'no':
                while True:
                    ans = None
                    if sys.version_info[0] == 3:
                        ans = input("\nEnter your local IPv4 address:  ")
                    else:
                        ans = raw_input("\nEnter your local IPv4 address:  ")
                    try:
                        socket.inet_pton(socket.AF_INET, ans)
                        local_addr = ans                
                        break
                    except:
                        print("The IPv4 address you entered does not look right.  Try again.")
                return local_addr
            else:
                print("\nYour answer can only be 'y' or 'n'.  Try again!")


    def miner_supervisor(self):
        '''
        Only used in testing
        '''
        print("\n\n=======STARTING A MINER SUPERVISOR THREAD======\n\n")
        while self.t_miner is None: time.sleep(2)
        while True:
            if  self.miner_iteration_index % 20 > 10:
                print("\n\nMiner supervisor terminating the miner thread\n")
                self.terminate_thread( self.t_miner )
                time.sleep(2)
                print("\n\n=======STARTING A NEW MINER THREAD======\n\n")
                self.miner_iteration_index += 10
                self.t_miner = ThreadedMiner( self )
                self.t_miner.start()
                time.sleep(2)   

    def run_hasher(self, num_iterations):
        '''
        Only used for testing
        '''
        genesis_string = binascii.b2a_hex(os.urandom(64))
        if sys.version_info[0] == 3:
            # It is easier to use Python's encode() and decode() to go back and forth between the
            # bytes type and str type, but the statement that follows is more fun to look at:
            genesis_string = functools.reduce(lambda x,y: x+y, list(map(lambda x: chr(x), genesis_string)))
        for iter_index in range(num_iterations):
            nonce = binascii.b2a_hex(os.urandom(64))
            if sys.version_info[0] == 3:
                nonce = functools.reduce(lambda x,y: x+y, list(map(lambda x: chr(x), nonce)))
            string_to_be_hashed = genesis_string + nonce
            hasher = SHA256.SHA256(message_in_ascii = string_to_be_hashed)
            hashval = hasher.sha256()  
            print("for try %d, hashval: %s" % (iter_index, hashval))
            if iter_index == 0:
                return string_to_be_hashed,hashval

    def scan_the_network_for_cerocoin_nodes(self):
        ip_block_to_scan = self.cerocoin_network[:]
        try:
            del ip_block_to_scan[ip_block_to_scan.index(self.local_ip_address)]
        except:
            print("\nYour local IP address is not included in the list of network addresses supplied in your constructor call. !!!ABORTING!!!\n")
            os.kill( os.getpid(), signal.SIGKILL )                           
        print("\n Will scan the IP addresses: %s" % str(ip_block_to_scan))
        remote_server_port = self.server_port
        max_num_of_tries_for_establishing_network = 3
        while True:
            if max_num_of_tries_for_establishing_network == 0:
                print("\n\nUnable to establish a network for CeroCoin demonstrations.  !!!ABORTING!!!\n\n")
                os.kill( os.getpid(), signal.SIGKILL )                                           
            for host in ip_block_to_scan:
                try:
                    print("\nTrying to connect with host %s\n" % host)
                    sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )   
                    sock.settimeout(3)    
                    sock.connect( (host, remote_server_port) )                                 
                    sock.settimeout(None)      
                    print("\nDiscovering CeroCoinClient Nodes: -- Made connection with host: %s\n" % host)
                    self.outgoing_client_sockets.append(sock)
                except:
                    print("        no connection possible with %s" % host)
            if len(self.outgoing_client_sockets) == 1:
                print("\n\nWARNING: Only one other CeroCoin node found -- only the simplest of blockchain demos possible\n\n")
                break
            elif len(self.outgoing_client_sockets) == 0:
                max_num_of_tries_for_establishing_network -= 1
                print("\n\n\nNo CeroCoin clients found.  Will sleep for 5 sec and try again. (Max num of tries: 3)")
                time.sleep(5)
            else: 
                break
    #server
    def set_up_server(self):
        mdebug = True
        port = self.server_port
        try:
            if mdebug:
                print("\n\nWill set up a server on port %d\n\n" % port)
            server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            server_sock.bind( ('', port) )        
            server_sock.listen(5)                 
            server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.server_socket = server_sock
            print("\n\nSUCCESSFULLY CREATED A SERVER SOCKET ON PORT %d\n\n" % port)
        except (socket.error, (value, message)) as error:       
            if server_sock:                         
                server_sock.close()                 
                print("\nCould not establish server socket: " + message)  
                os.killpg(0, signal.SIGKILL)
        while True:       
            (client_sock, address) = server_sock.accept()                   
            print("\nConnection request from address: %s" % str(address))
            t_connection = ThreadedClientConnection(self, client_sock, address)
            t_connection.daemon = True
            t_connection.start()

    #clientcon
    def client_connection(self, client_socket, ip_address):
        mdebug = False
        while True:                                               
            print("\nserver in interactive mode with client\n")
            client_line = ''                                   
            while 1:                                           
                client_byte = client_socket.recv(1) 
                if sys.version_info[0] == 3:
                    client_byte = client_byte.decode()
                if client_byte == '\n' or client_byte == '\r': 
                    break                                      
                else:                                          
                    client_line += client_byte                 
            if mdebug:
                print("\n::::::::::::::line received from remote: %s\n" % client_line)
            if client_line == "Send pub key for a new transaction":
                if self.pub_key_string is None:
                    sys.exit("buyer ID not yet created")
                if sys.version_info[0] == 3:
                    tobesent = "BUYER_PUB_KEY=" + ",".join(self.pub_key_string.split()) + "\n" 
                    client_socket.send( tobesent.encode() )
                else:
                    client_socket.send( "BUYER_PUB_KEY=" + ",".join(self.pub_key_string.split()) + "\n" )
            elif client_line.startswith("----CEROCOIN_TRANSACTION_BEGIN"):    
                print("\nTransaction received: %s\n" % client_line)
                transaction_received = client_line
                print("\nValidating transaction\n")
                lock.acquire()
                if self.is_transaction_valid( transaction_received ):
                    self.prev_transaction = self.transaction
                    print("\n\nAdding new transaction to the list in 'self.transactions_received'\n")
                    self.transactions_received.append( transaction_received )
                    print("\n\nNumber of transactions in 'self.transactions_received': %d\n" % len(self.transactions_received))
                lock.release()
            elif client_line == "Sending new block":    
                if sys.version_info[0] == 3:
                    client_socket.send("OK to new block\n".encode())
                else:
                    client_socket.send("OK to new block\n")
            elif client_line.startswith("CEROCOIN_BLOCK_BEGIN"):
                self.blockvalidator_flag = True
                print("\n\nNew block received: %s\n" % client_line)
                block_received = client_line
                if self.is_block_valid( block_received ):
                    print("\nreceived block validated\n")
                    lock.acquire()
                    (received_blockchain_len, received_block_pow_difficulty) = self.get_block_prop(block_received,
                                                                ('BLOCKCHAIN_LENGTH', 'POW_DIFFICULTY'))
                    received_blockchain_len = int(received_blockchain_len)
                    received_block_pow_difficulty = int(received_block_pow_difficulty)
                    print("\n========> received blockchain length:   %d" % received_blockchain_len)
                    print("\n========> self.blockchain_length:       %d" % self.blockchain_length)
                    print("\n========> received_block_pow_difficulty:  %d" % received_block_pow_difficulty)
                    print("\n========> self.pow_difficulty:            %d" % self.pow_difficulty)
                    if self.block is None:
                        self.block = block_received
                        self.blockchain_length = received_blockchain_len
                        print("\n========> WILL ASK THE CURRENT MINER THREAD TO STOP\n")
                        self.terminate_thread( self.t_miner )
                        self.t_miner = None
                        time.sleep(2)
                        self.block = block_received
                        self.blockchain_length = received_blockchain_len                            
                        self.pow_difficulty = received_block_pow_difficulty
                        print("\n========> STARTING A NEW COIN MINER THREAD WITH POW DIFFICULTY OF %d AND BLOCKCHAIN LENGTH OF %d\n\n" % (self.pow_difficulty, self.blockchain_length))
                        self.t_miner = ThreadedMiner( self )
                        self.t_miner.daemon = True
                        self.t_miner.start()
                        time.sleep(5)
                        self.t_miner_changed = False
                    elif (received_blockchain_len > self.blockchain_length) and \
                                         (received_block_pow_difficulty <= self.pow_difficulty):
                        print("\n========> WILL ASK THE CURRENT MINER THREAD TO STOP\n")
                        self.terminate_thread( self.t_miner )
                        self.t_miner = None
                        time.sleep(2)
                        self.block = block_received
                        self.blockchain_length = received_blockchain_len                            
                        self.pow_difficuly = received_block_pow_difficulty
                        print("\n========> STARTING A NEW COIN MINER THREAD WITH POW DIFFICULTY OF %d AND BLOCKCHAIN LENGTH OF %d\n\n" % (self.pow_difficulty, self.blockchain_length))
                        self.t_miner = ThreadedMiner( self )
                        self.t_miner.daemon = True
                        self.t_miner.start()
                        time.sleep(5)
                        self.t_miner_changed = False
                    else:
                        print("\nNo reason to abandon the current miner thread\n") 
                    lock.release()
                else:
                    print("\n\nNOTICE: An illegal block received --- Ignoring the received block\n")
                self.blockvalidator_flag = False
            else:
                print("buyer:  We should not be here")

    def gen_rand_bits_with_set_bits(self, bitfield_size):               
        candidate = random.getrandbits( bitfield_size )      
        if candidate & 1 == 0: candidate += 1            
        candidate |= (1 << bitfield_size - 1)                  
        candidate |= (2 << bitfield_size - 3)                  
        return "%x" % candidate

    #prepare
    def prepare_new_transaction(self, coin, buyer_pub_key):
        '''
        Before the seller digitally signs the coin, it must include the buyer's public key.
        '''
        mdebug = False
        if mdebug:
            print("\n\nPrepareTranx::Buyer pub key:  %s\n" % buyer_pub_key)
        new_tranx = CeroCoinTransaction( transaction_id       = self.gen_rand_bits_with_set_bits(32),
                                         coin                 = str(coin),
                                         seller_id            = self.ID,
                                         buyer_pub_key        = buyer_pub_key,
                                         seller_pub_key       = ",".join(self.pub_key_string.split()),
                                         pow_difficulty       = self.pow_difficulty,
                                         timestamp            = str(time.time()),
                                       )
        digitally_signed_tranx = self.digitally_sign_transaction(new_tranx)
        return digitally_signed_tranx

    #miner
    def mine_a_coin(self):
        mdebug = True
        if not self.run_miner_only:
            while len(self.outgoing_client_sockets) == 0: time.sleep(2)
        while True:
            while self.transactor_flag or self.blockmaker_flag or self.blockvalidator_flag: time.sleep(2)
            if self.num_transactions_sent >= 2:
                print("\n\nadditional sleep of 10 secs for the miner for the next round\n\n")
                time.sleep(10)
            time.sleep(2)
            hashval_int = 1 << 260
            iteration_control = 0
            genesis_string = string_to_be_hashed = None
            if self.block is None:
                print("\n\nFresh mining with a RANDOM genesis message string\n\n") 
                genesis_string = binascii.b2a_hex(os.urandom(64))
                if sys.version_info[0] == 3:
                    # It is easier to use Python's encode() and decode() to go back and forth between the
                    # bytes type and str type, but the statement that follows is more fun to look at:
                    genesis_string = functools.reduce(lambda x,y: x+y, list(map(lambda x: chr(x), genesis_string)))
            else:
                print("\n\nUsing the new block for forming the genesis string\n\n")
                (transactions,prev_block_hash,timestamp) = self.get_block_prop(self.block, ('TRANSACTIONS',
                                                                                            'PREV_BLOCK_HASH',
                                                                                            'TIMESTAMP'))
                genesis_string = message_hash(transactions + prev_block_hash + timestamp)
            threadname = self.t_miner.getName()
            while  hashval_int > 1 << self.pow_difficulty:
                while self.transactor_flag or self.blockmaker_flag or self.blockvalidator_flag: time.sleep(2)
                time.sleep(1)
                iteration_control += 1
                self.miner_iteration_index += 1
                if iteration_control == self.max_iterations_debug_mode: 
                    sys.exit("max iterations for debugging reached - exiting")
                nonce = binascii.b2a_hex(os.urandom(64))
                if sys.version_info[0] == 3:
                    nonce = functools.reduce(lambda x,y: x+y, list(map(lambda x: chr(x), nonce)))
                string_to_be_hashed = genesis_string + nonce                
                hasher = SHA256.SHA256(message_in_ascii = string_to_be_hashed)
                hashval = hasher.sha256()  
                if len(hashval) < 64:
                    hashval = [0] * (64 - len(hashval)) + hashval
                print("%s -- Try %d at mining a new coin -- hashval: %s" % (threadname, iteration_control, hashval))
                hashval_int = int(hashval, 16)
            print("\n\n***SUCCESS***  -- A coin mined successfully with hashval %s\n" % hashval)
            coin_id = self.gen_rand_bits_with_set_bits(32)
            while self.transactor_flag or self.blockmaker_flag or self.blockvalidator_flag: time.sleep(2)
            newcoin =  CeroCoin( coin_id                = coin_id,
                                 coin_miner             = self.ID,
                                 miner_pub_key          = ",".join(self.pub_key_string.split()),
                                 genesis_string         = genesis_string,
                                 nonce                  = nonce,
                                 pow_difficulty         = self.pow_difficulty,
                                 timestamp              = str(time.time()),
                                 hashval                = hashval,
                               )
            print("\n\nInside miner: new coin mined: %s\n\n" % str(newcoin))
            signed_newcoin = self.digitally_sign_coin(newcoin)
            while self.transactor_flag or self.blockmaker_flag or self.blockvalidator_flag: time.sleep(2)
            self.add_to_coins_owned_digitally_signed(signed_newcoin)

    def terminate_thread(self, thread):
        """
        Thread termination logic as suggested by Johan Dahlin at stackoverflow.com
        """
        if not thread.isAlive():
            return
        exc = ctypes.py_object(SystemExit)
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread.ident), exc)
        if res == 0:
            raise ValueError("nonexistent thread id")
        elif res > 1:
            ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None)
            raise SystemError("PyThreadState_SetAsyncExc failed")
        else:
            print(">>>>>>>>> CURRENT MINER THREAD TERMINATED SUCCESSFULLY <<<<<<<<<<<<") 

    #findbuyer #buyer #transactor
    def find_buyer_for_new_coin_and_make_a_transaction(self):
        '''
        We now look for a remote client with whom the new coin can be traded in the form of a
        transaction. The remote client must first send over its public key in order to construct
        the transaction.
        '''
        mdebug = False
        while len(self.outgoing_client_sockets) == 0: time.sleep(2)
        while len(self.coins_currently_owned_digitally_signed) == 0: time.sleep(2)
        time.sleep(4)
        while True:
            while len(self.coins_currently_owned_digitally_signed) == 0: time.sleep(2)
            while self.blockmaker_flag or self.blockvalidator_flag: time.sleep(2)
            self.transactor_flag = True
            print("\n\nLOOKING FOR A CLIENT FOR MAKING A TRANSACTION\n\n")
            coin = self.coins_currently_owned_digitally_signed.pop()
            if coin is not None: 
                print("\nNew outgoing coin:  %s" % coin)
            buyer_sock = None
            new_transaction = None
            random.shuffle(self.outgoing_client_sockets)
            sock = self.outgoing_client_sockets[0]
            if sys.version_info[0] == 3:
                sock.send(b"Send pub key for a new transaction\n")
            else:
                sock.send("Send pub key for a new transaction\n")
            try:
                while True: 
                    while self.blockmaker_flag or self.blockvalidator_flag: time.sleep(2)
                    message_line_from_remote = ""
                    while True:
                        byte_from_remote = sock.recv(1)               
                        if sys.version_info[0] == 3:
                            byte_from_remote = byte_from_remote.decode()
                        if byte_from_remote == '\n' or byte_from_remote == '\r':   
                            break                                        
                        else:                                            
                            message_line_from_remote += byte_from_remote
                    if mdebug:
                        print("\n:::::::::::::message received from remote: %s\n" % message_line_from_remote)
                    if message_line_from_remote == "Do you have a coin to sell?":
                        if sys.version_info[0] == 3:
                            sock.send( b"I do. If you want one, send public key.\n" )
                        else:
                            sock.send( "I do. If you want one, send public key.\n" )
                    elif message_line_from_remote.startswith("BUYER_PUB_KEY="):
                        while self.blockmaker_flag or self.blockvalidator_flag: time.sleep(2)
                        buyer_pub_key = message_line_from_remote
                        print("\nbuyer pub key: %s" % buyer_pub_key)
                        new_transaction = self.prepare_new_transaction(coin, buyer_pub_key)
                        self.old_transaction = self.transaction
                        self.transaction = new_transaction
                        self.transactions_generated.append(new_transaction)
                        print("\n\nNumber of tranx in 'self.transactions_generated': %d\n" % len(self.transactions_generated))
                        print("\n\nsending to buyer: %s\n"  % new_transaction)
                        if sys.version_info[0] == 3:
                            tobesent = str(new_transaction) + "\n"
                            sock.send( tobesent.encode() )
                        else:
                            sock.send( str(new_transaction) + "\n" )
                        self.num_transactions_sent += 1
                        break
                    else:
                        print("seller side: we should not be here")
            except:
               print("\n\n>>>Seller to buyer: Could not maintain socket link with remote for %s\n" % str(socket))
            self.transactor_flag = False
            time.sleep(10)

    #blockmaker
    def construct_a_new_block_and_broadcast_the_block_to_cerocoin_network(self):
        '''
        We pack the newly generated transactions in a new block for broadcast to the network
        '''
        mdebug = False
        time.sleep(10)
        while len(self.transactions_generated) < self.num_transactions_in_block: time.sleep(2)
        while True:
            while len(self.transactions_generated) < self.num_transactions_in_block: time.sleep(2)
            self.blockmaker_flag = True
            print("\n\n\nPACKING THE ACCUMULATED TRANSACTIONS INTO A NEW BLOCK\n\n")
            current_block_hash = min_pow_difficulty = None
            if self.block is None:
                current_block_hash = self.gen_rand_bits_with_set_bits(256)
                min_pow_difficulty = self.pow_difficulty              
                self.blockchain_length = len(self.transactions_generated)
            else:
                current_block_hash = self.get_hash_of_block(self.block)       
                min_pow_difficulty = 0
                for tranx in self.transactions_generated:
                    tranx_pow = int(self.get_tranx_prop(self.block, 'POW_DIFFICULTY'))
                    if tranx_pow  > min_pow_difficulty:
                        min_pow_difficulty = tranx_pow
                self.blockchain_length += len(self.transactions_generated)
            new_block = CeroCoinBlock( block_id            =  self.gen_rand_bits_with_set_bits(32),
                                       block_creator       =  self.ID,
                                       transactions        =  str(self.transactions_generated),
                                       pow_difficulty      =  min_pow_difficulty,
                                       prev_block_hash     =  current_block_hash,
                                       blockchain_length   =  self.blockchain_length,
                                       timestamp           =  str(time.time()),
                                     )
            self.transactions_generated = []
            new_block_with_signature = self.digitally_sign_block( str(new_block) )
            print("\n\n\nWILL BROADCAST THIS SIGNED BLOCK: %s\n\n\n" % new_block_with_signature)
            self.block = new_block_with_signature
            for sock in self.outgoing_client_sockets:   
                if sys.version_info[0] == 3:
                    sock.send("Sending new block\n".encode())
                else:
                    sock.send("Sending new block\n")
                try:
                    while True: 
                        message_line_from_remote = ""
                        while True:
                            byte_from_remote = sock.recv(1)               
                            if sys.version_info[0] == 3:
                                byte_from_remote = byte_from_remote.decode()
                            if byte_from_remote == '\n' or byte_from_remote == '\r':   
                                break                                        
                            else:                                            
                                message_line_from_remote += byte_from_remote
                        if mdebug:
                            print("\n::::::::::BLK: message received from remote: %s\n" % message_line_from_remote)
                        if message_line_from_remote == "OK to new block":
                            if sys.version_info[0] == 3:
                                tobesent = self.block + "\n"
                                sock.send( tobesent.encode() )
                            else:
                                sock.send( self.block + "\n" )
                            break
                        else:
                            print("sender side for block upload: we should not be here")
                except:
                    print("Block upload: Could not maintain socket link with remote for %s\n" % str(sockx))
            self.blockmaker_flag = False
            time.sleep(10)

    def get_hash_of_block(self, block):
        (transactions,prev_block_hash,timestamp) = self.get_block_prop(self.block, ('TRANSACTIONS',
                                                                                    'PREV_BLOCK_HASH',
                                                                                    'TIMESTAMP'))
        return message_hash(transactions + prev_block_hash + timestamp)

    #verifycoin
    def verify_coin(self, coin):
        '''
        This method verifies the signature on a coin for the buyer of the coin
        '''
        mdebug = True
        coin_splits = coin.split()
        miner_pub_key=pub_exponent=None
        for item in coin_splits:
            if item.startswith('MINER_PUB_KEY'):
                miner_pub_key = item[ item.index('=')+1 : ]
                break
        pubkey_splits = miner_pub_key.split(",")
        mod=e=None
        for item in pubkey_splits:
            if "=" in item:
                if item.startswith("mod"): mod = int(item[ item.index('=')+1 : ], 16)
                if item.startswith("e"):   e   = int(item[ item.index('=')+1 : ], 16)
        if mdebug:       
            print("\nVerifier: modulus: %d    public exponent: %d" % (mod, e))
        coin_without_signature, coin_signature_string = " ".join(coin_splits[1:-2]), coin_splits[-2]
        hasher = SHA256.SHA256(message = str(coin_without_signature))
        hashval = hasher.sha256()  
        hashval_int = int(hashval, 16)
        print("\nVerifier: coin hashval as int: %d" % hashval_int)
        coin_signature = int( coin_signature_string[ coin_signature_string.index('=')+1 : ], 16 )
        coin_checkval_int = modular_exp( coin_signature, e, mod )        
        print("\nVerifier: coin checkval as int: %d" % coin_checkval_int)
        if hashval_int == coin_checkval_int: 
            print("\nVerifier: Since the coin hashval is equal to the coin checkval, the coin is authentic\n")
            return True
        else: 
            return False

    def add_to_coins_owned_digitally_signed(self, signed_coin):
        how_many_currently_owned = len(self.coins_currently_owned_digitally_signed)
        print("\n\nAdding the digitally signed new coin (#%d) to the 'currently owned and digitally signed' collection\n" % (how_many_currently_owned + 1))
        self.coins_currently_owned_digitally_signed.append(signed_coin)

    def add_to_coins_acquired_from_others(self, newcoin):
        how_many_previously_bought = len(self.coins_acquired_from_others)
        print("\nChecking authenticity of the coin")
        check = self.verify_coin(newcoin)
        if check is True:
            print("\nCoin is authentic")
        print("\nAdding the received coin (#%d) to the 'previously bought' collection\n" % (how_many_previously_bought + 1))
        self.coins_acquired_from_others.append(newcoin)

    def set_my_id(self):
        if self.pub_key_string is None or self.priv_key_string is None:
            sys.exit("set_my_id() can only be called after you have generated the public and private keys for your id") 
        hasher = SHA256.SHA256(message = self.pub_key_string)
        self.ID = hasher.sha256()  
        
    def gen_rsa_primes(self, modulus_size):
        assert modulus_size % 2 == 0, "The size of the modulus must be an even number" 
        generator = PrimeGenerator.PrimeGenerator( bits = self.prime_size )
        modulus=p=q=totient=None
        while True:
            p = generator.findPrime()
            q = generator.findPrime()
            if p != q and gcd(p - 1, self.pub_exponent) == 1 and gcd(q - 1, self.pub_exponent) == 1:
                modulus = p * q
                totient = (p-1) * (q-1)    
            if gcd(totient, self.pub_exponent) == 1:
                break
            else: next
        self.modulus,self.p,self.q,self.totient = modulus,p,q,totient
        return modulus, p, q, totient

    def gen_private_exponent(self):
        self.priv_exponent = MI(self.pub_exponent, self.totient)

    def gen_key_pair(self):
        p,q = self.p,self.q
        p_inv_mod_q = MI(p, q)
        q_inv_mod_p = MI(q, p)
        self.Xp  = q * q_inv_mod_p
        self.Xq  = p * p_inv_mod_q
        self.pub_key_string   = "CEROCOIN_PUBKEY mod=%x e=%x" % (self.modulus, self.pub_exponent)
        self.priv_key_string  = "CEROCOIN-PRIVKEY mod=%x e=%x d=%x p=%x q=%x totient=%x Xp=%x Xq=%x" %  \
                    (self.modulus,self.pub_exponent,self.priv_exponent,self.p,self.q,self.totient,self.Xp,self.Xq)

    def write_key_pair_to_files(self):
        if (self.pub_key_string is None) or (self.priv_key_string is None):
            sys.exit("you must first call gen_key_pair() before calling write_key_pair_to_files()")
        with open('CeroCoinClientKey_pub.txt', "w") as outfile:
            outfile.write(self.pub_key_string)
        with open('CeroCoinClientKey_priv.txt', "w") as outfile:
            outfile.write(self.priv_key_string)

    #digisigncoin
    def digitally_sign_coin(self, coin):
        mdebug = True
        modulus,e,d,p,q,Xp,Xq = self.modulus,self.pub_exponent,self.priv_exponent,self.p,self.q,self.Xp,self.Xq
        if mdebug:  
            print("\nDIGI_SIGN -- modulus: %d" % modulus)
            print("\nDIGI_SIGN -- public exp: %d" % e)
            print("\nDIGI_SIGN -- private exp: %d" % d)
            print("\nDIGI_SIGN -- p: %d" % p)
            print("\nDIGI_SIGN -- q: %d" % q)
        splits = str(coin).split()
        coin_without_ends = " ".join(str(coin).split()[1:-1])
        hasher = SHA256.SHA256(message = str(coin_without_ends))
        hashval = hasher.sha256()  
        if mdebug:
            print("\nDIGI_SIGN: hashval for coin as int: %d" % int(hashval, 16))
        hashval_int = int(hashval, 16)
        Vp = modular_exp(hashval_int, d, p)
        Vq = modular_exp(hashval_int, d, q)
        coin_signature = (Vp * Xp) % modulus + (Vq * Xq) % modulus
        if mdebug:
            print("\nDIGI_SIGN: coin signature as int: %d" % coin_signature)
        coin_signature_in_hex = "%x" % coin_signature
        coin_with_signature = self.insert_miner_signature_in_coin(str(coin), coin_signature_in_hex)
        checkval_int = modular_exp(coin_signature, e, modulus)
        if mdebug:
            print("\nDIGI_SIGN: coin hashval as int:  %d" % hashval_int)
            print("\nDIGI_SIGN: coin checkval as int: %d" % checkval_int)
        assert hashval_int == checkval_int, "coin hashval does not agree with coin checkval"
        if mdebug:
            print("\nThe coin is authentic since its hashval is equal to its checkval")
        return coin_with_signature

    def insert_miner_signature_in_coin(self, coin, coin_signature_in_hex):
        return  'CEROCOIN_BEGIN ' + " ".join(coin.split()[1:-1]) + " MINER_SIGNATURE=" + coin_signature_in_hex  + ' CEROCOIN_END'

    #digisigntranx
    def digitally_sign_transaction(self, tranx):
        mdebug = False
        modulus,e,d,p,q,Xp,Xq = self.modulus,self.pub_exponent,self.priv_exponent,self.p,self.q,self.Xp,self.Xq
        tranx_without_ends = " ".join(str(tranx).split()[1:-1])
        hasher = SHA256.SHA256(message = tranx_without_ends)
        hashval = hasher.sha256()  
        if mdebug:
            print("\nTR: transaction hashval as int: %d" % int(hashval, 16))
        hashval_int = int(hashval, 16)
        Vp = modular_exp(hashval_int, d, p)
        Vq = modular_exp(hashval_int, d, q)
        tranx_signature = (Vp * Xp) % modulus + (Vq * Xq) % modulus
        if mdebug:
            print("\nTR: transaction signature as int: %d" % tranx_signature)
        tranx_signature_in_hex = "%x" % tranx_signature
        tranx_with_signature = '----CEROCOIN_TRANSACTION_BEGIN ' + tranx_without_ends + " SELLER_TRANX_SIGNATURE=" + tranx_signature_in_hex + ' CEROCOIN_TRANSACTION_END----'
        checkval_int = modular_exp(tranx_signature, e, modulus)
        print("\nTR: Transaction hashval as int:   %d" % hashval_int)
        print("\nTR: Transaction checkval as int:  %d" % checkval_int)
        assert hashval_int == checkval_int, "tranx hashval does not agree with tranx checkval"
        print("\nTransaction is authenticated since its hashval is equal to its checkval")
        return tranx_with_signature

    #digisignblock
    def digitally_sign_block(self, block):
        '''
        Even though the method 'get_hash_of_block()' only hashes the transactions, prev_block hash,
        and the timestamp, we use the entire block, sans its two end delimiters, for the block 
        creator's digital signature.
        '''
        print("\n\nBlock creator putting digital signature on the block\n\n")
        mdebug = True
        modulus,e,d,p,q,Xp,Xq = self.modulus,self.pub_exponent,self.priv_exponent,self.p,self.q,self.Xp,self.Xq
        block_without_ends = " ".join(block.split()[1:-1])
        hasher = SHA256.SHA256(message = block_without_ends)
        hashval = hasher.sha256()  
        if mdebug:
            print("\nTR: hashval for block as int: %d" % int(hashval, 16))
        hashval_int = int(hashval, 16)
        Vp = modular_exp(hashval_int, d, p)
        Vq = modular_exp(hashval_int, d, q)
        block_signature = (Vp * Xp) % modulus + (Vq * Xq) % modulus
        if mdebug:
            print("\nTR: block signature as int: %d" % block_signature)
        block_signature_in_hex = "%x" % block_signature
        block_with_signature = 'CEROCOIN_BLOCK_BEGIN ' + block_without_ends + " BLOCK_CREATOR_SIGNATURE=" + block_signature_in_hex + ' CEROCOIN_BLOCK_END'
        checkval_int = modular_exp(block_signature, e, modulus)
        if mdebug:
            print("\nTR: block hashval as int:   %d" % hashval_int)
            print("\nTR: block checkval as int:  %d" % checkval_int)
        assert hashval_int == checkval_int, "block hashval does not agree with block checkval"
        return block_with_signature

    #isvalid
    # Yet to be fully implemented.  At this time, the code shown is just a place holder
    def is_transaction_valid(self, transaction):
        splits = transaction.split()
        if not splits[0].startswith('----CEROCOIN_TRANSACTION_BEGIN'):
            return False
        for item in splits[1:]:
            if item not in ['CEROCOIN_BEGIN','CEROCOIN_END','----CEROCOIN_TRANSACTION_BEGIN','CEROCOIN_TRANSACTION_END----','BLOCK_BEGIN','BLOCK_END'] and '=' not in item:
                return False
        return True    

    #isvalid
    # Yet to be fully implemented.  At this time, the code shown is just a place holder
    def is_block_valid(self, block):
        splits = block.split()
        if not splits[0].startswith('CEROCOIN_BLOCK_BEGIN'):
            return False
        for item in splits[1:]:
#            print("\nitem in validating block: %s" % item)
            if (item not in ['CEROCOIN_BEGIN','CEROCOIN_END','----CEROCOIN_TRANSACTION_BEGIN','CEROCOIN_TRANSACTION_END----','CEROCOIN_BLOCK_BEGIN','CEROCOIN_BLOCK_END']) and ('=' not in item):
                return False
        return True    

    #getter
    def get_coin_prop(self, coin, prop):
        splits = coin.split()
        for item in splits:
            if '=' in item:
                if item[:item.index('=')] == prop:
                    return item[item.index('=')+1:]
    #getter
    def get_tranx_prop(self, transaction, prop):
        splits = transaction.split()
        for item in splits:
            if '=' in item:
                if item[:item.index('=')] == prop:
                    return item[item.index('=')+1:]

    #getter
    def get_block_prop(self, block, prop):
        '''
        When 'prop' is a scalar for an attribute name in a block, it returns the value associated
        with that attribute.  On the other hand, when 'prop' is a tuple of attribute names, it
        returns a list of the corresponding attribute values.
        '''
        splits = block.split()
        if isinstance(prop, (tuple)):
            answer_dict = {prop[i] : None for i in range(len(prop))}
            for item in splits:
                if '=' in item:                
                    if item[:item.index('=')] in prop:
                        answer_dict[item[:item.index('=')]] = item[item.index('=')+1:]
            return [answer_dict[item] for item in prop] 
        else:
            for item in splits:
                if '=' in item:
                    if item[:item.index('=')] == prop:
                        return item[item.index('=')+1:]


#threaded
############################################  Multithreaded Classes  #############################################

class ThreadedServer( threading.Thread ):
    def __init__(self, network_node):
        self.network_node = network_node
        threading.Thread.__init__(self)
    def run(self):
        self.network_node.set_up_server()

class ThreadedScanner( threading.Thread ):
    def __init__(self, network_node):
        self.network_node = network_node
        threading.Thread.__init__(self)
    def run(self):
        self.network_node.scan_the_network_for_cerocoin_nodes()    

class ThreadedMiner( threading.Thread ):
    def __init__(self, network_node):
        self.network_node = network_node
        threading.Thread.__init__(self)
    def run(self):
        self.network_node.mine_a_coin()

class ThreadedTransactor( threading.Thread ):
    def __init__(self, network_node):
        self.network_node = network_node
        threading.Thread.__init__(self)
    def run(self):
        self.network_node.find_buyer_for_new_coin_and_make_a_transaction()

class ThreadedBlockMaker( threading.Thread ):
    def __init__(self, network_node):
        self.network_node = network_node
        threading.Thread.__init__(self)
    def run(self):
        self.network_node.construct_a_new_block_and_broadcast_the_block_to_cerocoin_network()

class ThreadedClientConnection( threading.Thread ):
    def __init__(self, network_node, client_socket, client_ip_address):
        self.network_node = network_node
        self.client_socket = client_socket
        self.client_ip_address = client_ip_address
        threading.Thread.__init__(self)
    def run(self):
        self.network_node.client_connection(self.client_socket, self.client_ip_address)

class ThreadedMinerSupervisor( threading.Thread ):
    def __init__(self, network_node):
        self.network_node = network_node
        threading.Thread.__init__(self)
    def run(self):
        self.network_node.miner_supervisor()

################################################  class CeroCoin  #################################################
class CeroCoin:                                     
    def __init__( self, **kwargs ):                                     
        coin_id=coin_miner=miner_pub_key=genesis_string=pow_difficulty=hashval=nonce=timestamp=debug=None    
        if 'coin_id' in kwargs              :        coin_id = kwargs.pop('coin_id')
        if 'coin_miner' in kwargs           :        coin_miner = kwargs.pop('coin_miner')
        if 'miner_pub_key' in kwargs        :        miner_pub_key = kwargs.pop('miner_pub_key')
        if 'genesis_string' in kwargs       :        genesis_string = kwargs.pop('genesis_string')
        if 'pow_difficulty' in kwargs       :        pow_difficulty = kwargs.pop('pow_difficulty')
        if 'nonce' in kwargs                :        nonce = kwargs.pop('nonce')        
        if 'timestamp' in kwargs            :        timestamp = kwargs.pop('timestamp')        
        if 'hashval' in kwargs              :        hashval = kwargs.pop('hashval')
        if 'debug' in kwargs                :        debug = kwargs.pop('debug')
        self.coin_id = coin_id if coin_id else sys.exit("A coin MUST have a 32-bit ID")
        self.coin_miner = coin_miner if coin_miner else sys.exit("A coin MUST have the miner associated with it")
        self.miner_pub_key = miner_pub_key
        self.nonce = nonce
        self.genesis_string = genesis_string if genesis_string else None
        self.hashval  =  hashval if hashval else None
        self.pow_difficulty = pow_difficulty
        self.timestamp  = timestamp 
        self.coin_signature = None

    def __str__(self):
        'To create a string representation of a coin'
        return " ".join(['CEROCOIN_BEGIN',
                         'COIN_ID='+self.coin_id, 
                         'COIN_MINER='+self.coin_miner, 
                         'MINER_PUB_KEY='+self.miner_pub_key,
                         'GENESIS_STRING='+self.genesis_string,
                         'NONCE='+self.nonce, 
                         'POW_DIFFICULTY='+str(self.pow_difficulty),
                         'TIMESTAMP='+self.timestamp, 
                         'HASHVAL='+self.hashval,
                         'CEROCOIN_END'
                       ])

##########################################  class CeroCoinTransaction  ############################################

class CeroCoinTransaction:                                     
    def __init__( self, **kwargs ):                                     
        transaction_id=coin=seller_id=buyer_id=transaction=prev_transaction=nonce=pow_difficulty=None
        timestamp=debug=None    
        if 'transaction_id' in kwargs          :        transaction_id = kwargs.pop('transaction_id')
        if 'coin' in kwargs                    :        coin = kwargs.pop('coin')
        if 'seller_id' in kwargs               :        seller_id = kwargs.pop('seller_id')
        if 'seller_pub_key' in kwargs          :        seller_pub_key = kwargs.pop('seller_pub_key')
        if 'buyer_pub_key' in kwargs           :        buyer_pub_key = kwargs.pop('buyer_pub_key')
        if 'pow_difficulty' in kwargs          :        pow_difficulty = kwargs.pop('pow_difficulty')
        if 'timestamp' in kwargs               :        timestamp = kwargs.pop('timestamp')
        if 'debug' in kwargs                   :        debug = kwargs.pop('debug')
        self.transaction_id = transaction_id
        self.coin = coin
        self.seller_id = seller_id
        self.seller_pub_key = seller_pub_key
        self.buyer_id = buyer_id
        self.buyer_pub_key = buyer_pub_key
        self.timestamp = timestamp

    def get_pow_difficulty_level( self ):
        return self.pow_difficulty

    def __str__(self):
        'To create a string representation of a transaction'
        return " ".join(['----CEROCOIN_TRANSACTION_BEGIN', 
                         'TRANSACTION_ID='+self.transaction_id, 
                         self.coin,
                         'SELLER='+self.seller_id, 
                         'SELLER_PUB_KEY='+self.seller_pub_key, 
                         'BUYER_PUB_KEY='+self.seller_pub_key, 
                         'TIMESTAMP='+self.timestamp, 
                         'CEROCOIN_TRANSACTION_END----' 
                        ])

#############################################  class CeroCoinBlock  ###############################################
#block
class CeroCoinBlock:                                     
    def __init__( self, **kwargs ):                                     
        block_id=block_creator=transactions=pow_difficulty=prev_block_hash=timestamp=None
        blockchain_length=debug=None    
        if 'block_id' in kwargs          :        block_id = kwargs.pop('block_id')
        if 'block_creator' in kwargs     :        block_creator = kwargs.pop('block_creator')
        if 'transactions' in kwargs      :        transactions = kwargs.pop('transactions')
        if 'pow_difficulty' in kwargs    :        pow_difficulty = kwargs.pop('pow_difficulty')       
        if 'prev_block_hash' in kwargs   :        prev_block_hash = kwargs.pop('prev_block_hash')
        if 'blockchain_length' in kwargs :        blockchain_length = kwargs.pop('blockchain_length') 
        if 'timestamp' in kwargs         :        timestamp = kwargs.pop('timestamp')        
        if 'debug' in kwargs             :        debug = kwargs.pop('debug')
        self.block_id = block_id
        self.block_creator = block_creator if block_creator else sys.exit("A block MUST have the creator associated with it")
        self.transactions = transactions
        self.pow_difficulty = pow_difficulty
        self.prev_block_hash = prev_block_hash
        self.blockchain_length = blockchain_length
        self.timestamp  = timestamp 

    def __str__(self):
        'To create a string representation of a block'
        transactions = str(self.transactions)
#        transactions = transactions.replace(' ', '')
        transactions = transactions.replace(' ', ':')
        return " ".join(['CEROCOIN_BLOCK_BEGIN', 
                         'BLOCK_ID='+self.block_id, 
                         'BLOCK_CREATOR='+self.block_creator, 
                         'TRANSACTIONS='+transactions, 
                         'POW_DIFFICULTY='+str(self.pow_difficulty),
                         'PREV_BLOCK_HASH='+self.prev_block_hash, 
                         'BLOCKCHAIN_LENGTH='+str(self.blockchain_length), 
                         'TIMESTAMP='+self.timestamp, 
                         'CEROCOIN_BLOCK_END'
                        ])