def select_witness(self, transaction, i): def gte(a, b): if a == b: return True for i in range(0, 31): if a[i] > b[i]: return True if a[i] < b[i]: return False return True hash = nacl.hash.sha256(transaction + i.to_bytes(4, byteorder='little', signed=True), encoder=nacl.encoding.RawEncoder) node_list = list(self.addressList.keys()) node_list.append(self.verify_key) tx = trustchain_pb2.Transaction() tx.ParseFromString(transaction) for ID in tx.ID: try: node_list.remove(bytes(ID)) except KeyError: pass node_list.sort() witness = node_list[0] for node in node_list: if gte(node, hash): return witness if gte(node, witness): witness = node return witness
def witness_transaction(self, TransactionProposal): reply = b"\x00" # Default to nack request = trustchain_pb2.SignatureRequest() request.ParseFromString(TransactionProposal) transaction = trustchain_pb2.Transaction() transaction.ParseFromString(request.transaction) logging.info("received witness request") if self.select_witness(request.transaction, request.witnessCount, ) != self.verify_key: logging.error("prescribed witness ID does not match my ID") return reply # do this check for every party associated with the transaction try: for j in range(0, len(transaction.ID)): self.validate_transaction(transaction.ID[j], transaction, request.chains[j].blocks[0]) except ValueError as e: logging.exception("A problematic transaction was detected during witnessing \n") return reply signature = trustchain_pb2.Approval() signature.ID = self.verify_key signature.i = request.witnessCount signature.signature = self.signing_key.sign(request.transaction).signature reply = signature.SerializeToString() logging.info("Transaction ") return reply
def transactions(): start = request.args.get('from') if not start or start == "": next = client.chain[-1] else: next = base64.urlsafe_b64decode(start.encode('UTF-8')) length = request.args.get('length') if not length: length = 25 else: length = int(length) length = min(length, len(client.chain)-3) transactions = [] for entries in range(length): block_bytes = client.chain[next][:-32] block = trustchain_pb2.Block() block.ParseFromString(block_bytes) transaction = trustchain_pb2.Transaction() transaction.ParseFromString(block.transaction) myID = None for i in range(len(transaction.ID)): if transaction.ID[i] == bytes(client.verify_key): myID = i if myID == 1: value = transaction.value * -1 else: value = transaction.value next = transaction.previousHash[myID] transactions.append(((time.strftime("%b %d %Y %H:%M:%S", time.gmtime(transaction.timestamp))), client.friendlyNameList[transaction.ID[1-myID]], "EUR {0:.2f}".format(value), base64.urlsafe_b64encode(next).decode('UTF-8'))) if next == b'\x00': break return jsonify({"from": base64.urlsafe_b64encode(next).decode('UTF-8'), "transactions": transactions}), 200, {'Access-Control-Allow-Origin': '*'}
def validate_transaction(self, ID, transaction, block): # A function that checks if the current transaction is a valid successor of the block for this specific node hash = block[-32:] # Find the index of node in current transaction index = -1 for i in range(0, len(transaction.ID)): if transaction.ID[i] == ID: index = i break if index == -1: raise ValueError("Node is not owner of this transaction") block, x = self.trustchain.verify_block(block) temp_transaction = trustchain_pb2.Transaction() temp_transaction.ParseFromString(block.transaction) if transaction.previousHash[index] != hash: raise ValueError("PreviousHash does not point to actual previous hash") # determine old balance for i in range(0, len(temp_transaction.ID)): if temp_transaction.ID[i] == transaction.ID[index]: previous_balance = temp_transaction.balance[i] for i in range(0, len(transaction.ID)): if transaction.balance[i] < 0: logging.error(transaction) raise ValueError("Negative balance detected:") # If index = 0, node is on the receiving side if index == 0: if transaction.balance[index] != previous_balance + transaction.value: logging.error(transaction) raise ValueError("Previous and current value do not match with new balance") # if index == 1, node is on giving side elif index == 1: if transaction.balance[index] != previous_balance - transaction.value: logging.error(transaction) raise ValueError("Previous and current value do not match with new balance")
def verify_block(block_bytes): hash = block_bytes[-32:] block_bytes = block_bytes[:-32] if hash != nacl.hash.sha256(block_bytes, encoder=nacl.encoding.RawEncoder): raise ValueError("Hashes do not match") block = trustchain_pb2.Block() block.ParseFromString(block_bytes) transaction = trustchain_pb2.Transaction() transaction.ParseFromString(block.transaction) if len(transaction.ID) != len(block.signatures): print(transaction) print(block) raise ValueError( "Number of signatures don't match with the number of ID's") for i in range(0, len(transaction.ID)): try: verify_key = nacl.signing.VerifyKey(transaction.ID[i]) verify_key.verify(block.transaction, block.signatures[i]) except nacl.exceptions.BadSignatureError as e: raise ValueError(e) if round(time.time()) < transaction.timestamp: raise ValueError( "Block claims to be created in the future. current time {}, creation time {}" .format( time.strftime("%Y-%m-%d %H:%M:%S", (time.gmtime())), time.strftime("%Y-%m-%d %H:%M:%S", (time.gmtime(transaction.timestamp))))) if transaction.value == 0: # Transaction is genesis block pass return block, hash
def create_genesis_block(self): ''' Genesis block: | 0x00 | Timestamp | creator ID | Signature | Hash | 1 + 4 bytes + 32 bytes + 64 bytes + 32 = 133 ''' ID = bytes(self.signing_key.verify_key) block = trustchain_pb2.Block() # create empty transaction transaction = trustchain_pb2.Transaction() transaction.timestamp = 0 transaction.value = 0 transaction.ID.append(ID) transaction.previousHash.append(b'\0') transaction.sequence_no.append(0) transaction.balance.append(1000) block.transaction = transaction.SerializeToString() block.signatures.append( self.signing_key.sign(block.transaction).signature) serial_block = block.SerializeToString() hash = nacl.hash.sha256(serial_block, encoder=nacl.encoding.RawEncoder) return serial_block + hash
def fsm(self, message): # # Statemachine implementation (See notes.pptx for diagram) # States for initiator node reply = b'\x00' # Always default to nack if self.state == 0: if message[:1] == b'\x01': trade_request = trustchain_pb2.TradeRequest() trade_request.ParseFromString(message[1:]) self.counter_party = trade_request.publicKey self.trade_value = trade_request.value if self.counter_party not in self.addressList: logging.warning("Incoming transaction from unknown id {}".format(self.counter_party)) return reply logging.info("Incoming transaction from {}".format(self.friendlyNameList[self.counter_party])) try: counter_block, self.counter_party_prev_hash = self.trustchain.verify_block(trade_request.blocks[0]) except ValueError: logging.exception("Found invalid block in trade request") return reply counter_transaction = trustchain_pb2.Transaction() counter_transaction.ParseFromString(counter_block.transaction) block_is_valid = False for i in range(0, len(counter_transaction.ID)): if counter_transaction.ID[i] == self.counter_party: self.counter_balance = counter_transaction.balance[i] self.counter_party_sequenceNo = counter_transaction.sequence_no[i] block_is_valid = True break # Todo: re-enable balance check for normal usage if self.counter_balance < self.trade_value: logging.warning("Value is more than balance! value is {}, received last block is: \n {} \n THIS WILL BE INGORED FOR DEMONSTRATION PURPOSES".format(self.trade_value ,str(counter_transaction))) # return reply if block_is_valid: transaction = trustchain_pb2.Transaction() transaction.timestamp = round(time.time()) transaction.previousHash.append(self.chain[-1]) transaction.previousHash.append(self.counter_party_prev_hash) transaction.sequence_no.append(self.sequenceNo + 1) transaction.sequence_no.append(self.counter_party_sequenceNo + 1) transaction.ID.append(bytes(self.signing_key.verify_key)) transaction.ID.append(self.counter_party) transaction.value = self.trade_value transaction.balance.append(self.balance + self.trade_value) transaction.balance.append(self.counter_balance - self.trade_value) # Create a block for the counter party to sign block = trustchain_pb2.Block() block.transaction = transaction.SerializeToString() block.signatures.append(self.signing_key.sign(block.transaction).signature) self.proposed_block = block trade_reply = trustchain_pb2.TradeReply() trade_reply.proposedBlock = block.SerializeToString() trade_reply.blocks.append(self.chain[self.chain[-1]]) # trade_ack, + last block reply = b'\02' + trade_reply.SerializeToString() self.state = 2 return reply # Wait for trade reply if self.state == 1: if message[:1] == b"\x02": # 2 is trade_reply trade_reply = trustchain_pb2.TradeReply() trade_reply.ParseFromString(message[1:]) # Verify block try: counter_block, self.counter_party_prev_hash = self.trustchain.verify_block(trade_reply.blocks[0]) except ValueError: return reply # todo: make the following code work for multiple blocks chain = trustchain_pb2.Chain() chain.blocks.append(self.chain[self.chain[-1]]) counter_chain = trustchain_pb2.Chain() counter_chain.blocks.append(trade_reply.blocks[0]) counter_transaction = trustchain_pb2.Transaction() counter_transaction.ParseFromString(counter_block.transaction) block_is_valid = False for i in range(0, len(counter_transaction.ID)): if counter_transaction.ID[i] == self.counter_party: self.counter_balance = counter_transaction.balance[i] self.counter_party_sequenceNo = counter_transaction.sequence_no[i] block_is_valid = True break # shit down is copied from previous itteration try: block = trustchain_pb2.Block() block.ParseFromString(trade_reply.proposedBlock) transaction = trustchain_pb2.Transaction() transaction.ParseFromString(block.transaction) except ValueError as e: print(e) return reply if len(block.signatures) != 1: print(transaction) print(block) raise ValueError("WTF, dit block is helemaal niet getekend f****r") # Check if the values are actually the values you agreed uppon for i in range(0, len(transaction.ID)): if self.trade_value != transaction.value: raise ValueError("trade agreed on {}".format(self.trade_value)) # Checks for Counter party if transaction.ID[i] == self.counter_party: if self.counter_balance + self.trade_value != transaction.balance[i]: block_is_valid = False print(transaction) print("Counter balance don't match old {}".format(self.counter_balance)) if self.counter_party_prev_hash != transaction.previousHash[i]: block_is_valid = False print(transaction) print("Counter hash don't match {}".format(self.counter_party_prev_hash)) if self.counter_party_sequenceNo + 1!= transaction.sequence_no[i]: block_is_valid = False print(transaction) print(" Counter sequence NO don't match {}".format(self.counter_party_sequenceNo)) # Checks for mine if transaction.ID[i] == bytes(self.signing_key.verify_key): if self.balance - self.trade_value != transaction.balance[i]: block_is_valid = False print(transaction) print("my balance don't match old {}".format(self.balance)) if self.chain[-1][-32:] != transaction.previousHash[i]: block_is_valid = False print(transaction) print("my hash don't match {}".format(self.chain[-1][-32:])) if self.sequenceNo + 1 != transaction.sequence_no[i]: block_is_valid = False print(transaction) print("My sequence NO don't match {}".format(self.sequenceNo)) if block_is_valid: block.signatures.append(self.signing_key.sign(block.transaction).signature) else: return reply self.proposed_block = block if self.required_Witnesses > 0: request = trustchain_pb2.SignatureRequest() request.transaction = transaction.SerializeToString() request.chains.add().CopyFrom(counter_chain) request.chains.add().CopyFrom(chain) gevent.spawn(self.collect_signatures, request, self.required_Witnesses, self.fwsp_reply) try: sigs = self.fwsp_reply.get(block=True, timeout=0.5) if len(sigs) == 0: logging.error("Transaction failed, not enough valid witnesses") # print("Aaawh FWSP failed") return reply except gevent.timeout.Timeout: logging.info("Sending keep alive message: not enough valid witness replies.") reply = b'\x03' + self.fwsp_no_sigs.to_bytes(4,byteorder='big',signed=False) self.state = 3 return reply self.proposed_block.witnesses.extend(sigs) logging.info("Transaction successful with {}/{} valid witness replies".format(len(sigs), self.required_Witnesses)) block_bytes = self.proposed_block .SerializeToString() block = block_bytes + nacl.hash.sha256(block_bytes, encoder=nacl.encoding.RawEncoder) self.chain[block[-32:]] = block self.chain[-1] = block[-32:] self.balance = self.balance - self.trade_value self.sequenceNo = self.sequenceNo + 1 reply = b'\x05' + block return reply # Had to send keep alive message if self.state == 3: if message[:1] == b"\x04": try: sigs = self.fwsp_reply.get(block=True, timeout=0.5) if len(sigs) == 0: logging.error("Transaction failed, not enough valid witnesses") return reply except gevent.timeout.Timeout: logging.info("Sending keep alive message: not enough valid witness replies.") reply = b'\x03' + self.fwsp_no_sigs.to_bytes(4,byteorder='big',signed=False) return reply self.proposed_block.witnesses.extend(sigs) logging.info("Transaction successful with {}/{} valid witness replies".format(len(sigs), self.required_Witnesses)) block_bytes = self.proposed_block.SerializeToString() block = block_bytes + nacl.hash.sha256(block_bytes, encoder=nacl.encoding.RawEncoder) self.chain[block[-32:]] = block self.chain[-1] = block[-32:] self.balance = self.balance - self.trade_value self.sequenceNo = self.sequenceNo + 1 reply = b'\x05' + block return reply if self.state == 2: if message[:1] == b"\x05": block = message[1:] try: received_block, hash = TrustChain.verify_block(block) except ValueError: logging.exception("Malformed block received in final stage\n") return reply if self.proposed_block.transaction != received_block.transaction: logging.warning("Block does not contain transaction earlier agreed uppon") return reply valid_witnesses = 0 for aproval in received_block.witnesses: try: TrustChain.verify_witness_reply(received_block.transaction, aproval) valid_witnesses += 1 except ValueError: logging.exception("Error occured during the verification of witness replies in teh block") logging.info("Transaction successful with {}/{} witness".format(valid_witnesses, self.required_Witnesses)) self.chain[block[-32:]] = block self.chain[-1] = block[-32:] self.balance = self.balance + self.trade_value self.sequenceNo = self.sequenceNo + 1 reply = None elif message[:1] == b'\x03': logging.info("Received keep alive while waiting on signatures") reply = b"\x04" return reply