def __init__(self): """ Create class instance :returns instance of Graph class :rtype: object """ self.wallet = Wallet() self.keystore = self.wallet.prompt_unlock() if not self.keystore: sys.exit(1) self.logger = logging.getLogger('Graph') self.logger.debug("Node address %s", str(self.keystore['address'])) self.stake = 1 self.tot_stake = 4 # TODO: It should change dynamically self.min_s = int(2 * self.tot_stake / 3 + 1) # min stake amount self.to_sign_count = 10 self.last_signed_state = 0 self.unsent_count = 0 self._cgc = CryptographCommon(graph=self) self.common = Common() self.crypto = Crypto() self.head = None self.round = {} self.tbd = set() self._event = Event(graph=self) self._fame = Fame(graph=self) self._order = Order(graph=self) self._round = Rounds(graph=self)
def test_sign_creating(self, signed_state_instance): """ Test of creation of one ordinary signature from 0 to 4 """ transaction = Transaction(signed_state_instance.graph.database) crypto = Crypto() # Insert consensus signed_state_instance.graph.database.insert_consensus(set(range(5))) # Check if it was successfully inserted assert signed_state_instance.graph.database.get_consensus_count() == 5 # From 0 to 4 created_sign = signed_state_instance.create_state_sign() # Check if generated tx is valid tx_dict = transaction.parse_transaction_hex(created_sign) assert tx_dict != False # Check if signature is valid data = crypto.validate_state_sign(tx_dict) assert data != False # Check main data fields assert data['hash'] == from_0_to_4_hash assert data['start'] == 0 # Check last_created flag in db assert signed_state_instance.graph.database.get_consensus_last_created_sign( ) == 4
def get_node_id(): """ Generate a node id based on public key. :return: string """ crypto = Crypto() pub_key_sha = crypto.sha256(str(Prisma().graph.keystore['publicKey'])) node_id = pub_key_sha[:10] return hexlify(node_id).decode('utf-8')
def __init__(self, graph): """ Create class instance :param graph: instance of cryptograph class :type graph: object :returns instance of Event class :rtype: object """ self.graph = graph self.crypto = Crypto() self.logger = logging.getLogger('Event')
def __init__(self, graph): """ Create class instance :param graph: instance of Graph class :type graph: object :returns instance of SignedStateManager class :rtype: object """ self.graph = graph self.crypto = Crypto() self.transaction = Transaction() self.logger = logging.getLogger('SignedStateManager')
def start(self, is_prompt): """ Start the app. :param is_prompt: :return: """ self.logger.info('Starting Prisma v{0}'.format(self.version)) try: self.common = Common() self.db = PrismaDB(self.config.get('general', 'database')) self.wallet = Wallet() self.crypto = Crypto() self.graph = Graph() self.graph.init_graph() self.state_manager = SignedStateManager(self.graph) self.api = ApiService() self.network = NetworkService() self.network.start() self.api.start() if is_prompt: # set prompt with a thread prompt = Prompt() reactor.callInThread( prompt.run) # callInThread will make input non-blocking except error.CannotListenError as e: self.logger.critical('Port %i is already in use.', e.port) except Exception as e: self.logger.exception('Error: ' + str(e)) exit(1)
def test_sign_creating_many(self, signed_state_instance): """ Test of creation of many (2) signatures """ transaction = Transaction(signed_state_instance.graph.database) crypto = Crypto() signed_state_instance.graph.unsent_count = 10 # Insert consensus signed_state_instance.graph.database.insert_consensus(set(range(10))) # Check the consensus was successfully inserted assert signed_state_instance.graph.database.get_consensus_count() == 10 sign_list = signed_state_instance.try_create_state_signatures() # Check if precisely two signatures were created assert len(sign_list) == 2 # Check last_created flag in db assert signed_state_instance.graph.database.get_consensus_last_created_sign( ) == 9 """Check main data fields""" # From 0 to 5 data = crypto.validate_state_sign( transaction.parse_transaction_hex(sign_list[0])) assert data['hash'] == from_0_to_4_hash assert data['start'] == 0 # From 4 to 9 data = crypto.validate_state_sign( transaction.parse_transaction_hex(sign_list[1])) assert data['hash'] == from_5_to_10_hash assert data['start'] == 5
def main(self): if len(sys.argv) == 1: print('Run: prisma_dev.py drop|genesis') exit() elif sys.argv[1] == 'drop': database_list = self.db_connection.database_names() for database in database_list: if database != 'local': self._destroy_db(database) elif sys.argv[1] == 'genesis': # balance balances = self.read_JSON_from_file(self.genesis_balances_file) balance = collections.OrderedDict(sorted(balances.items())) state = SignedStateManager(None).get_ordered_state( -1, "0" * 64, balance) state_hash = Crypto().blake_hash( bytes(dumps(state).encode('utf-8'))) genesis = {'state': state, 'hash': state_hash, 'signed': True} print(genesis) self.write_JSON_to_file(self.genesis_output_file, genesis)
def __init__(self): self.logger = logging.getLogger('Wallet') self.crypto = Crypto() self.transaction = Transaction() self.transaction_buffer = []
class Wallet(object): """ Wallet """ def __init__(self): self.logger = logging.getLogger('Wallet') self.crypto = Crypto() self.transaction = Transaction() self.transaction_buffer = [] def create_prisma_home_directory(self): """ Creates or make sure that prisma root directory exists. :return: True: Prisma root directory exists. :return: False: Could not create prisma root directory. :rtype: bool """ if platform == 'linux' or platform == 'linux2': storage_dir = path.expanduser('~/.prisma') if not path.isdir(storage_dir): try: makedirs(storage_dir) self.logger.debug("Created new prisma storage directory.") except OSError as e: self.logger.error( "Could not create prisma storage directory." + "Reason: %s", str(e)) return False else: self.logger.error("Could not create prisma storage directory. " + "Operating system not supported.") return False return True def keystore_path(self): """ Creates or make sure that prisma wallet key storage file exists. :return: True - Prisma wallet key storage file exists. :return: False - could not create prisma wallet key storage file. """ if platform == 'linux' or platform == 'linux2': key_storage_file = path.expanduser('~/.prisma/keystore.json') if not path.exists(key_storage_file): try: with open(key_storage_file, 'a') as key_storage: dump([], key_storage) self.logger.debug( "Created new empty prisma key storage file.") except Exception as e: self.logger.error( "Could not create prisma key storage file." + "Reason: %s", str(e)) return False return key_storage_file else: self.logger.error( "Could not open keystore. Operating system not supported.") return False def encrypt_keystore(self, account, password): """ Encrypts the wallet private key. :param account: dictionary of unencrypted public and private keys. :param password: password string for encryption. :return: Success: dictionary of the encrypted private key, unencrypted public key and address :return: False: could not encrypt private key. :rtype: dict or bool """ if 'privateKeySeed' in account and 'publicKey' in account and 'address' in account: try: key_storage = nacl.secret.SecretBox( self.crypto.sha256(password)) encrypted_private_key_seed = key_storage.encrypt( account['privateKeySeed']) except Exception as e: self.logger.error( "Could not create encrypted keystorage. Reason: %s", str(e)) return False self.logger.debug( "Created new encrypted key storage for public key: %s", str(account['publicKey'])) return { 'encPrivateKeySeed': (hexlify(encrypted_private_key_seed)).decode(), 'publicKey': account['publicKey'].decode(), 'address': account['address'] } return False def decrypt_keystore(self, address, password): """ Decrypts the wallet private key. :param password: password string for decryption of the private key. :param address: :return: Success: dictionary of the unencrypted private key, unencrypted public key and address. :return: False: could not decrypt private key. :rtype: dict or bool """ enc_pk = {} wallets = self.read_keystore() for wallet in wallets: if 'address' in wallet and wallet['address'] == address: enc_pk = wallet break if enc_pk: _enc_pk = unhexlify( bytes(enc_pk['encPrivateKeySeed'].encode('utf-8'))) try: key_storage = nacl.secret.SecretBox( self.crypto.sha256(password)) private_key_seed = key_storage.decrypt(ciphertext=_enc_pk) except Exception as e: self.logger.error( "Could not decrypt key storage. Check password. Error: %s", str(e)) return False return { 'publicKey': bytes(enc_pk['publicKey'].encode('utf-8')), 'address': enc_pk['address'], 'privateKeySeed': private_key_seed } self.logger.error( 'Wallet with address {0} does not exist.'.format(address)) return False def read_keystore(self): """ Reads the json key storage file. :param none :return: Success: dictionary of the encrypted private key, unencrypted public key and address. :return: False: could not decrypt private key. :rtype: dict or bool """ if self.create_prisma_home_directory() and self.keystore_path(): try: with open(self.keystore_path()) as key_storage: wallets = load(key_storage) if not isinstance(wallets, list): # todo: validate wallet keys self.logger.error("Invalid wallet format.") return None return wallets except Exception as e: self.logger.error("Could not read key storage. Reason: %s", str(e)) self.logger.error("No wallets exists.") return False def write_keystore(self, encrypted_json_array): """ Writes the json key storage file. :param encrypted_json_array: dictionary with the encrypted private key, unencrypted public key and address :return: True: successfully wrote dictionary to key storage. :return: False: could not write dictionary to key storage. :rtype: bool """ if self.create_prisma_home_directory() and self.keystore_path(): wallets = self.read_keystore() if wallets is not None: wallets.append(encrypted_json_array) try: with open(self.keystore_path(), "w") as key_storage: dump(wallets, key_storage) self.logger.debug( "Successfully wrote keys to key storage.") except Exception as e: self.logger.error( "Could not write keys to key storage. Reason: %s", str(e)) return False return True return False def addr_from_public_key(self, public_key): """ Converts the public key to a more readable wallet address. :param public_key: byte string with the public key :type public_key: byte string :return: Success: an int representation of the byte string plus suffix :return: False: could not convert the public key to an int representation :rtype: string or bool """ try: address_as_int = int.from_bytes(public_key[:8], byteorder='big') address = str(address_as_int) + "PR" except Exception as e: self.logger.error( "Could not generate address from public key. Reason:", str(e)) return False return address def new_wallet(self, password, write=True): """ Creates a new random public and private key. Encrypts the private key and writes it to wallet key storage. :param password: password string for encrypt and decrypt the private key. :param write: :return: True: successfully created a new wallet. :return: False: could not create a new wallet. :rtype: bool """ kp = self.crypto.generate_keypair() keys = { "privateKeySeed": hexlify(bytes(kp)), "publicKey": hexlify(bytes(kp.verify_key)) } if keys: keys['address'] = self.addr_from_public_key( bytes(keys['publicKey'])) if not write: return keys if self.create_prisma_home_directory() and self.keystore_path(): encrypted_keys = self.encrypt_keystore(keys, password) if encrypted_keys: if self.write_keystore(encrypted_keys): return keys return False def list_wallets(self): """ Lists existing wallet addresses. One address will be sent as an argument to decrypt_keystore(). :return: List """ address_list = [] wallets = self.read_keystore() if wallets: for wallet in wallets: if 'address' in wallet: address_list.append(wallet['address']) return address_list def create_wallet(self): """ Creates a new wallet asking password in the prompt. :return: dict """ password = getpass(prompt='New password: '******'Verify password: '******'Passwords do not match.') keys = self.new_wallet(password) if not keys: raise Exception( 'Could not create wallet. Check ~/.prisma directory permissions.' ) return keys def prompt_unlock(self): """ Prompt user for unlocking a certain wallet by its address. :return: success: keys dictionary, no success: False """ try: # verify if there is not an existing wallet then create a new wallet and continue if len(self.list_wallets()) == 0: print( 'You don\'t have any wallets. Let\'s create your first wallet.' ) keys = self.create_wallet() print('Wallet created with address: ' + keys['address']) return keys # if wallet address is specified in the config file then just ask for a password, otherwise ask for an # address in the prompt if CONFIG.has_option('general', 'wallet_address'): wallet_address = CONFIG.get('general', 'wallet_address') if CONFIG.get('general', 'wallet_address') not in self.list_wallets(): raise Exception( 'Wallet provided in the config file does not exist.') self.logger.info( 'Unlocking wallet with address {0}'.format(wallet_address)) # in case of dev mode we can have the password in the config file, otherwise ask for a password if CONFIG.getboolean('developer', 'developer_mode') and \ CONFIG.has_option('developer', 'wallet_password'): keys = self.decrypt_keystore( wallet_address, CONFIG.get('developer', 'wallet_password')) if keys: return keys else: while True: password = getpass(prompt="Password: "******"Password can not be empty.") else: while True: wallet_address = input("Wallet address: ") if wallet_address and wallet_address in self.list_wallets( ): password = getpass(prompt="Password: "******"Password can not be empty.") else: self.logger.info("Wallet address does not exist.") break except Exception as e: self.logger.error( 'Error when unlocking wallet. Reason: {0}'.format(e)) return False def transaction_buffer_add(self, transaction): """ Saves a transaction in a buffer. :param transaction: """ self.transaction_buffer.append(transaction) def clear_transaction_buffer(self): """ Empties the transaction buffer. """ del self.transaction_buffer[:] def insert_transaction_buffer_into_pool(self): """ Gets all stored transactions and inserts them into the pool. """ self.transaction.insert_transactions_into_pool(self.transaction_buffer) self.clear_transaction_buffer()
def __init__(self): self.common_functions = Common() self.crypto = Crypto() self.validator = TxValidator() self.logger = logging.getLogger('Transaction')
class Transaction(object): """ Transaction """ def __init__(self): self.common_functions = Common() self.crypto = Crypto() self.validator = TxValidator() self.logger = logging.getLogger('Transaction') def hexlify_transaction(self, tx): """ Hexifies a transaction. :param tx: :return: Success: :return: False: Failed to hexlify tx :rtype: string or bool """ try: return hexlify(bytes( json.dumps(tx).encode('utf-8'))).decode('utf-8') except Exception as e: self.logger.error( "Failed to hexlify transaction. Reason: {0}".format(e)) return False @staticmethod def unhexlify_transaction(tx_hex): """ From a tx_hex returns the original dictionary. :param tx_hex: :return: dict """ try: return json.loads(unhexlify(tx_hex).decode('utf-8')) except Exception as e: self.logger.error( "Failed to unhexlify transaction. Reason: {0}".format(e)) return False def form_funds_tx(self, keys, recipient_id, amount): """ Wraps a transaction in an object. :param keys: :param recipient_id: :param amount: :return: self.finalize_transaction(): """ if amount and keys and 'address' in keys and \ 'publicKey' in keys and recipient_id: tx = { "type": str(TYPE_MONEY_TRANSFER), "amount": amount, "senderPublicKey": keys['publicKey'].decode(), "senderId": keys['address'], "recipientId": recipient_id, "timestamp": self.common_functions.get_timestamp() } else: self.logger.error("Missing values in form transaction.") return False if not self.validator.transaction(tx): return False return self.hexlify_transaction(tx) def generate_transaction_id(self, tx_hex): """ Generates a transaction ID :param tx_hex: :return: Success: tx_id: Transaction ID :rtype: int :return: False: Could not generate transaction id. :rtype: bool """ try: tx_hash = self.crypto.sha256_tx(tx_hex) tx_id = int.from_bytes(tx_hash[:10], byteorder='big') except Exception as e: self.logger.error("Could not generate transaction id. Reason:", e) return False return tx_id @staticmethod def get_transaction_fee(): """ Returns transaction fee. :return: 10 :rtype: int """ return 10 def parse_transaction_hex(self, tx_hex): """ Parse hex to a transaction dict. :param tx_hex: :return: tx """ try: tx = self.unhexlify_transaction(tx_hex) if int(tx['type']) == TYPE_MONEY_TRANSFER: sender_balance = Prisma().db.get_account_balance( tx['senderId']) if tx['amount'] > sender_balance: raise Exception('Money transfer without enough balance.') return tx except Exception as e: self.logger.debug('Could not parse transaction: ' + str(e)) return False def insert_transactions_into_pool(self, tx_list): """ Prepares and inserts transactions into tx pool. They will be inserted into event as soon as it will be created. :param tx_list: list of finalized transactions :type tx_list: list :return: insertion result :rtype: bool """ if tx_list: prepared_tx_list = [] for tx_hex in tx_list: tx = self.parse_transaction_hex(tx_hex) if tx: tx['tx_dict_hex'] = tx_hex self.logger.debug("Prepared for pool tx %s", str(tx)) prepared_tx_list.append(tx) else: self.logger.error( "Skipping inserting malformed transaction %s", str(tx)) continue return Prisma().db.insert_transactions(prepared_tx_list) return False def insert_processed_transaction(self, ev_hash_list, round, self_pub_key): """ Inserts processed tx (the one, that was included in final tx order) by event hash Should be used only in order.py :param ev_hash_list: list of events for which final order was found :type ev_hash_list: list :param round: round of all events, used later to generate state and clean db :type round: int :param private_key_seed: node private key seed needed to create and afterwards to check verify key :type private_key_seed: str :return: """ self.logger.debug( "insert_processed_transaction input ev_hash_list = %s, round = %s, pub_key = %s", str(ev_hash_list), str(round), str(self_pub_key)) tx_list = [] for event_hash in ev_hash_list: self.logger.debug( "insert_processed_transaction for ev with hash %s", str(event_hash)) event = Prisma().db.get_event(event_hash) Prisma().db.set_round_handled({event_hash: round}) self.logger.debug("insert_transaction_by_ev_hash event %s", str(event)) if not event: self.logger.error( "Could not insert tx, event there is no event !") return False if len(event.d) > 0: if event.c != self_pub_key.decode("utf-8"): for tx_hex in event.d: tx = self.parse_transaction_hex(tx_hex) # Money transfer if (tx and 'type' in tx and int(tx['type']) == TYPE_MONEY_TRANSFER and 'amount' in tx and 'senderId' in tx and 'recipientId' in tx): tx['tx_dict_hex'] = tx_hex tx['ev_hash'] = event_hash tx['round'] = round tx_list.append(tx) self.logger.debug("Insert money transfer tx %s", str(tx)) # State signature elif tx and 'type' in tx and int( tx['type']) == TYPE_SIGNED_STATE: # Handle new signatures that was crated by remote node self.logger.debug("Handle remote sign %s", str(tx)) Prisma().state_manager.handle_new_sign(tx) # Error else: self.logger.error( "Skipping malformed transaction data for event hash: %s", str(tx)) else: Prisma().db.set_transaction_round(event_hash, round) return Prisma().db.insert_transactions(tx_list)
class Graph: """ Graph """ def __init__(self): """ Create class instance :returns instance of Graph class :rtype: object """ self.wallet = Wallet() self.keystore = self.wallet.prompt_unlock() if not self.keystore: sys.exit(1) self.logger = logging.getLogger('Graph') self.logger.debug("Node address %s", str(self.keystore['address'])) self.stake = 1 self.tot_stake = 4 # TODO: It should change dynamically self.min_s = int(2 * self.tot_stake / 3 + 1) # min stake amount self.to_sign_count = 10 self.last_signed_state = 0 self.unsent_count = 0 self._cgc = CryptographCommon(graph=self) self.common = Common() self.crypto = Crypto() self.head = None self.round = {} self.tbd = set() self._event = Event(graph=self) self._fame = Fame(graph=self) self._order = Order(graph=self) self._round = Rounds(graph=self) def init_graph(self): self.last_signed_state = Prisma().db.get_consensus_last_signed() self.logger.debug("INIT last_signed_state: %s", str(self.last_signed_state)) is_cg_empty = self.init_events() self.restore_invariants(is_cg_empty) if is_cg_empty: self.sync_genesis() self.unsent_count = len(Prisma().db.get_consensus_greater_than( Prisma().db.get_consensus_last_created_sign())) def init_events(self): """ Verifying events stored in database :return: is cg empty :rtype: bool """ cg = Prisma().db.get_events_many() if cg: self.logger.info("Verifying events stored in database.") for event in cg: if not self._event.is_valid_event(event, cg[event]): self.logger.error("Could not verify event with blake2b hash %s", str(event)) """ Todo: what will we do here if we can not validate an event in database? """ exit() return False else: return True def restore_invariants(self, is_cg_empty): """ Initializes cryptograph if it is empty :param is_cg_empty: is cryptograph empty (this arg func get from init_events) :return: None """ if is_cg_empty: h, ev = self._event.new_event([], ()) self._event.add_event(h, ev) Prisma().db.insert_round({h: 0}) Prisma().db.insert_witness({0: {ev.c: h}}) Prisma().db.insert_can_see({h: {ev.c: h}}) Prisma().db.insert_head(h) else: self.logger.debug("Reconnect") def sync_genesis(self): """ Insert genesis state if it is not exist """ if not Prisma().db.get_state(-1): gen_state = Common().read_genesis_state() self.logger.debug("genesis_state: %s", str(gen_state)) Prisma().db.insert_state(gen_state['state'], gen_state['hash'], gen_state['signed']) def signed_event_response(self): """ Sends the events we know about without actually sending the events. As a response we shall get the events in full which we don't know about. The remote peer will call local_cryptograph_response() and create a subset based on this information. signed_events contains two parents and their heights. :returns: signed events :rtype: dict """ head = Prisma().db.get_head() if head: signed_events = self.crypto.sign_data( json.dumps({c: Prisma().db.get_height(h) for c, h in Prisma().db.get_can_see(head).items()}), self.keystore['privateKeySeed']) if signed_events: return signed_events return None def get_clean_remote_cg(self, remote_cg): """ Removes events from remote cg we have already signed or know. :param: remote cryptograph with events that we might know :type remote_cg: tuple :returns: remote cg without events that we have already sign or know :rtype: dict """ remote_cg = self._event.restore(remote_cg) for event_hash in list(remote_cg): event_round = Prisma().db.get_round(event_hash) if event_round == -1: self.logger.error("Could not clean remote cg: get event round ERROR !") return False if (event_round and event_round <= self.last_signed_state) \ or Prisma().db.get_event(event_hash, as_tuple=False): self.logger.debug("CLEANING DELETE hash = %s", str(event_hash)) del remote_cg[event_hash] self.logger.debug("Clean remote HG %s", str(remote_cg)) return remote_cg def validate_add_event(self, events_sign): """ Gets remote events signature, and validate than delete events that we already know :param events_sign: signature of remote cryptograph :type events_sign: dict :returns: remote cryptograph without events that we already know and remote head :rtype: dict and str """ msg = self.crypto.verify_concatenated(events_sign) remote_head, remote_cg = json.loads(msg.decode('utf-8')) self.logger.debug("VALIDATE remote_cg %s", str(remote_cg)) self.logger.debug("VALIDATE remote_head %s", str(remote_head)) remote_cg = self.get_clean_remote_cg(remote_cg) return remote_cg, remote_head def insert_new_events(self, remote_cg, remote_head, payload): """ Inserts new remote events into db and create new event of that sync :param remote_cg: remote cryptograph :type remote_cg: dict :param remote_head: hash of head of remote cryptograph :type remote_head: str :param payload: data (transaction) that will be inserted into the new event :type payload: dict or tuple :returns: topologicaly sorted sequence of new events to process. :rtype: events: set """ new = tuple(self._cgc.toposort(remote_cg.keys(), lambda u: remote_cg[u].p)) self.logger.debug("remote_cg %s", str(remote_cg)) self.logger.debug("remote_head %s", str(remote_head)) self.logger.debug("payload %s", str(payload)) self.logger.debug("new %s", str(new)) for h in new: ev = remote_cg[h] self.logger.debug("h %s", str(h)) self.logger.debug("ev %s", str(ev)) if self._event.is_valid_event(h, ev): self._event.add_event(h, ev) else: self.logger.debug("Event not valid: %s", str(ev)) if self._event.is_valid_event(remote_head, remote_cg[remote_head]): h, ev = self._event.new_event(payload, (Prisma().db.get_head(), remote_head)) self.logger.debug("new_event_event_part %s", str(ev)) assert self._event.is_valid_event(h, ev) if self._event.add_event(h, ev): Prisma().db.insert_head(h) return new + (h,) return False def local_cryptograph_response(self, signed_event_response): """ Based on the get_event_response and the data generated in signed_event_response() on the remote peer we calculate a subset of events that the asking node does not know about. The two parents and their height are used below. Returns a dict; see crypto.py :param signed_event_response: signed remote events and last remote event time :type signed_event_response: dict :returns: signed local events or False if error :rtype: dict or bool """ head = Prisma().db.get_head() self.logger.debug("signed_event_response %s", str(signed_event_response)) self.logger.debug("head %s", str(head)) # cg is a list of event tuples, it should be a dict of tuples if head: cs = json.loads((self.crypto.verify_concatenated(signed_event_response)).decode('utf-8')) # cs are a dict subset = {h: Prisma().db.get_event(h) for h in self._cgc.bfs((head,), lambda u: (p for p in Prisma().db.get_event(u, clear_parent=True).p if Prisma().db.get_event(p).c not in cs or Prisma().db.get_height(p) > cs[Prisma().db.get_event(p).c]))} response = json.dumps((head, subset)) local_cryptograph_response_res = self.crypto.sign_data(response, self.keystore['privateKeySeed']) self.logger.debug("local_cryptograph_response_res %s", str(local_cryptograph_response_res)) return local_cryptograph_response_res return False
class SignedStateManager(object): """ Signed state manager """ def __init__(self, graph): """ Create class instance :param graph: instance of Graph class :type graph: object :returns instance of SignedStateManager class :rtype: object """ self.graph = graph self.crypto = Crypto() self.transaction = Transaction() self.logger = logging.getLogger('SignedStateManager') def get_ordered_state(self, last_round, prev_hash, balance): """ Gets ordered dict of state :param last_round: last round of state :type last_round: int :param prev_hash: hash of previous state :type prev_hash: str :param balance: sorted balance of all nodes :type balance: sorted dict :return: ordered state :rtype: ordered dict """ state = collections.OrderedDict([('_id', last_round), ('prev_hash', prev_hash), ('balance', balance)]) return state def create_state(self, start_round, last_round): """ Generate state for given round range :param start_round: :param last_round: :return: hash of newly created state :rtype: str """ # Gets Prev_hash prev_hash = Prisma().db.get_last_state()['hash'] # Gets and sort Balance balance_dict = Prisma().db.get_account_balance_many( [start_round, last_round]) balance = collections.OrderedDict(sorted(balance_dict.items())) state = self.get_ordered_state(last_round, prev_hash, balance) state_hash = self.crypto.blake_hash(bytes( dumps(state).encode('utf-8'))) # Result of all old transactions is saved in newly created state, so we can drop tx Prisma().db.delete_money_transfer_transaction_less_than(last_round) Prisma().db.insert_state(state, state_hash) return state_hash def create_state_sign(self): """ Gets unsent state from db or create if it is not exit, then gets its hash, and finally signs hash and last round of this state by nodes secret key :returns: transaction with state signature :rtype: str """ # Get rounds where famousness is fully decided consensus = Prisma().db.get_consensus_greater_than( Prisma().db.get_consensus_last_created_sign(), lim=self.graph.to_sign_count) self.logger.debug("State consensus %s", str(consensus)) if len(consensus) != self.graph.to_sign_count: raise ValueError( "Not enough rounds where famousness is fully decided!") state_db = Prisma().db.get_state(consensus[-1]) if not state_db: state_hash = self.create_state(consensus[0], consensus[-1]) else: # State was generated before state_hash = state_db['hash'] data = {'last_round': consensus[-1], 'hash': state_hash} self.logger.debug("State signature data %s", str(data)) sign_data = self.crypto.sign_data( dumps(data), self.graph.keystore['privateKeySeed']) # Form transaction sign_data['type'] = TYPE_SIGNED_STATE hex_str = self.transaction.hexlify_transaction(sign_data) Prisma().db.set_consensus_last_created_sign(consensus[-1]) # Save our signature in orrder to send it to new node data['sign'] = sign_data Prisma().db.insert_signature(data) return hex_str def try_create_state_signatures(self): """ While there are enough rounds where famousness is fully decided, creates state and than digitally sign this and pushes this signature to list. Afterwards, all created signatures are inserted into the pool. :returns: None """ state_signatures = [] while self.graph.unsent_count >= self.graph.to_sign_count: self.logger.debug("Signed state unsent count %s", str(self.graph.unsent_count)) try: new_signature = self.create_state_sign() except ValueError as e: self.logger.error("Error with get state signature %s", str(e)) break if new_signature: state_signatures.append(new_signature) self.graph.unsent_count -= self.graph.to_sign_count self.logger.debug("State signature was generated %s", str(new_signature)) self.logger.debug("Consensus sign response = %s", str(state_signatures)) self.transaction.insert_transactions_into_pool(state_signatures) def handle_new_sign(self, tx_dict): """ If transaction if valid, stores it as unchecked to db (that means that we have not compared this remote hash with our local), also checks whether there are enough rounds for which all famous events are completely determined in order to create the signature of state On success, sign so many state as we can. :param tx_dict: parsed transaction dict :type tx_dict: dict :returns: None """ if tx_dict: # Validates signature self.logger.debug("tx_dict: %s", str(tx_dict)) sign_data = self.crypto.validate_state_sign(tx_dict) del tx_dict['type'] sign_data['sign'] = tx_dict self.logger.debug("sign_data: %s", str(sign_data)) if (sign_data and sign_data['last_round'] > self.graph.last_signed_state and sign_data['sign']['verify_key'] != self.graph.keystore['publicKey'].decode('utf-8')): # Inserts signature as unchecked Prisma().db.insert_signature_unchecked(sign_data) # Try to sign local state while True: self.logger.debug( "Handle state signatures last_signed_state %s", str(self.graph.last_signed_state)) local_consensus = Prisma().db.get_consensus_greater_than( self.graph.last_signed_state, lim=self.graph.to_sign_count) # if there is enough consensus to sign if (len(local_consensus) != self.graph.to_sign_count or not self.update_state_sign(local_consensus)): break def update_state_sign(self, local_consensus): """ Handles signatures of state stored in db :param local_consensus: local stored rounds where famousness is fully decided :type local_consensus: tuple :return: True if signed state is reached and False in opposite case :rtype: bool """ local_signatures = Prisma().db.get_signature(local_consensus[-1]) self.logger.debug("Local signatures %s", str(local_signatures)) if not local_signatures: self.logger.debug("There is no local signatures") return False # Hash of local state local_hash = Prisma().db.get_state(local_signatures['_id'])['hash'] # Count of successfully checked signatures unchecked_len = 0 ''' Checks if hash of pair is equal to local, on success inserts signature to db as checked and increments sign count. Note: pair consists of hash of consensus and its sign. ''' if 'unchecked_pair' in local_signatures: for pair in local_signatures['unchecked_pair']: for h, sign in pair.items(): self.logger.debug("Local con hash %s", str(local_hash)) self.logger.debug("Remote hash %s", str(h)) if h == local_hash and ('sign' not in local_signatures or sign not in local_signatures['sign']): self.logger.debug("Consensus hash is equal.") data = { 'last_round': local_signatures['_id'], 'sign': sign, 'hash': h } Prisma().db.insert_signature(data) unchecked_len += 1 else: self.logger.error( "Consensus hash is NOT equal or that signature is already saved." ) # All stored unchecked signatures are processed now, so we can delete them Prisma().db.unset_unchecked_signature(local_signatures['_id']) ''' There are no new valid signatures ''' if not unchecked_len: return False ''' Calculates total count of valid signatures ''' sign_count = unchecked_len if 'sign' in local_signatures: sign_count += len(local_signatures['sign']) ''' If there are enough signatures, signs consensus and cleans db ''' if sign_count >= self.graph.min_s: self.logger.debug("Signs consensus") Prisma().db.sign_consensus(self.graph.to_sign_count) self.graph.last_signed_state = local_consensus[-1] self.logger.debug("self.graph.last_signed_state %s", str(self.graph.last_signed_state)) # Start cleaning database self.clean_database(self.graph.last_signed_state) Prisma().db.set_state_signed(local_signatures['_id']) return True else: return False def clean_database(self, last_signed): """ Deletes signed data from db, so there will never be a huge number of data stored :param last_signed: last round for which signed state was reached :type last_signed: int :return: None """ Prisma().db.delete_transaction_less_than(last_signed) Prisma().db.delete_witnesses_less_than(last_signed) # Gets list of signed events hash_list = Prisma().db.get_rounds_hash_list(last_signed) for _hash in hash_list: Prisma().db.delete_event(_hash) Prisma().db.delete_can_see(_hash) Prisma().db.delete_votes(_hash) Prisma().db.delete_famous(_hash) ''' We should clear references after removing documents by hash. In this case we will get much better performance ''' # Delete each link to signed events Prisma().db.delete_references_can_see(hash_list) def handle_received_state(self, state, signatures): """ Validates state received via connection :param state: state to validate :type state: dict :param signatures: list of signatures from more than 1/3 voting power :type signatures: list :return: is handling operation successful """ last_state_hash = Prisma().db.get_last_state()['hash'] if last_state_hash != state['prev_hash']: self.logger.error("Recived state have bad hash of prev state") return False '''balance = collections.OrderedDict(sorted(state['balance'].items())) ordered_state = self.get_ordered_state(state['_id'], state['prev_hash'], balance) self.logger.debug("ordered_state: %s", str(ordered_state)) state_hash = self.crypto.blake_hash(bytes(dumps(ordered_state).encode('utf-8')))''' state_hash = self.crypto.blake_hash(bytes( dumps(state).encode('utf-8'))) # TODO improve signature storing and validation signature_list = [] proof_sign_count = 0 for verify_key in signatures: # Verifies signed data temp_dict = { 'verify_key': verify_key, 'signed': signatures[verify_key] } sign_data = self.crypto.validate_state_sign(temp_dict) if not sign_data: # Incorrect verify_key return False node_addr = Prisma().wallet.addr_from_public_key( bytes(verify_key.encode('utf-8'))) self.logger.debug("node_addr: %s", str(node_addr)) self.logger.debug("node ballance: %s", str(Prisma().db.get_state_balance(node_addr))) self.logger.debug( "sign_data['last_round'] = %s, state['_id'] = %s", str(sign_data['last_round']), str(state['_id'])) self.logger.debug("sign_data['hash'] = %s, state_hash = %s", str(sign_data['hash']), str(state_hash)) if (Prisma().db.get_state_balance(node_addr) and sign_data['last_round'] == state['_id'] and sign_data['hash'] == state_hash): # All is good add signature to valid list and count it as proof sign_data['sign'] = temp_dict signature_list.append(sign_data) proof_sign_count += 1 else: self.logger.error("Recived state have bad signature") # Checks if we get enough signatures if proof_sign_count >= self.graph.min_s: Prisma().db.insert_state(state, state_hash, True) for sign in signature_list: Prisma().db.insert_signature(sign) return True else: self.logger.error("Recived state have not enough proof signatures") return False def handle_received_state_chain(self, chain): """ :param chain: Chain of states and signatures as proof :type chain: list :return: is handling successful """ for stateunit in chain: if not self.handle_received_state(stateunit['state'], stateunit['signatures']): return False return True
class Event(object): """ Event """ def __init__(self, graph): """ Create class instance :param graph: instance of cryptograph class :type graph: object :returns instance of Event class :rtype: object """ self.graph = graph self.crypto = Crypto() self.logger = logging.getLogger('Event') @staticmethod def restore(msg): """ Restore Named tuple Event from tuple :param msg: event dict formatted as tuple(not named) with order that match with order in named Event :type msg: tuple :return: formated Event :rtype: named tuple(Event) """ res = {} for h in msg.keys(): list_event = msg[h] res[h] = Event_(list_event[0], tuple(list_event[1]), list_event[2], list_event[3], list_event[4]) return res def new_event(self, d, p): """ Create a new event (and also return it's hash) Events are vertices in the hash graph containing the info(hashes) about 2 parent graphs that synced/gossiped :param d: data/payload :type d: tuple :param p: event-hash of 2 parents :type p: set :return: hash of new event and event itself :rtype: str and named tuple """ self.logger.debug("NEW_EVENT: %s %s", str(d), str(p)) t = time() s = self.crypto.sign_data( dumps(d, p, t, self.graph.keystore['publicKey'].decode()), self.graph.keystore['privateKeySeed'], True) self.logger.debug("Sign: %s", s['sig_detached']) ev = Event_(d, p, t, s['verify_key'], s['sig_detached']) self.logger.debug("Created event : %s", str(ev)) return self.crypto.blake_hash(bytes(dumps(ev).encode('utf-8'))), ev def is_valid_event(self, blake2hash, ev): """ Validate event :param blake2hash: event hash :type blake2hash: str :param ev: Event = named tuple containing : d p c t s :type ev: named tuple :return: True if event valid and False otherwise :rtype: bool """ if not self.crypto.verify_local_event(ev): return False self.logger.debug("VALID_CHECK %s %s", str(blake2hash), str(ev)) self.logger.debug( "self.hash %s", str(self.crypto.blake_hash(bytes(dumps(ev).encode('utf-8'))))) if self.crypto.blake_hash(bytes( dumps(ev).encode('utf-8'))) == blake2hash: self.logger.debug("Event blake2b hash matches ") else: self.logger.debug("HASHES dont match.") self.logger.debug("Parents: %s", str(ev.p)) if ev.p != (): rnd1 = Prisma().db.get_round(ev.p[0]) rnd2 = Prisma().db.get_round(ev.p[1]) first_parent = Prisma().db.get_event(ev.p[0], as_tuple=False) second_parent = Prisma().db.get_event(ev.p[1], as_tuple=False) if (self.crypto.blake_hash(bytes( dumps(ev).encode('utf-8'))) == blake2hash and (ev.p == () or (len(ev.p) == 2 and ((first_parent and first_parent[ev.p[0]]['c'] == ev.c) or rnd1 <= self.graph.last_signed_state) and ((second_parent and second_parent[ev.p[1]]['c'] != ev.c) or rnd2 <= self.graph.last_signed_state)))): self.logger.debug("Event successfully validated: %s", str(ev)) return True self.logger.debug("Event could not be successfully validated: %s", str(ev)) return False # TODO: check if there is a fork (rly need reverse edges?) # and all(self.cg[x].c != ev.c # for x in self.preds[ev.p[0]])))) def add_event(self, blake2hash, ev): """ Save event to database :param blake2hash: event hash :type blake2hash: str :param ev: Event = named tuple containing : d p c t s :type ev: named tuple :return: True if successfully added and False otherwise :rtype: bool """ try: Prisma().graph.tbd.add(blake2hash) if ev.p == (): if not Prisma().db.insert_height({blake2hash: 0}): self.logger.error( "Could not add root event with blake2b hash %s.", str(blake2hash)) return False else: height_list = [] for p in ev.p: height_list.append(Prisma().db.get_height(p)) if not Prisma().db.insert_height( {blake2hash: max(height_list) + 1}): self.logger.debug( "Could not add new event with blake2b hash %s", str(blake2hash)) return False Prisma().db.insert_event({blake2hash: ev}) except Exception as e: self.logger.error("Could not add new event. Reason:", e) return False return True