Προγραμματιστική Εργασία
{ Δημιουργία Περιβάλλοντος Blockchain }
Αυγέρης Τσιρώνης – Α.Μ. 00909
atsironis@uth.gr
α. Δημιουργία Ιδιωτικού – Δημόσιου Κλειδιού
β. Υπογραφή
γ. Έλεγχος εγκυρότητας
δ. Δημιουργία αρχιτεκτονικής του Blockchain
α. Δημιουργία Κρυπτονομίσματος
β. Σύνδεση με HTTP Client
Εκτέλεση Εφαρμογής
# CHAPTER 2
Κατά την εκκίνηση εκχωρείται ένα ιδιωτικό κλειδί (χωρίς δυνατότητα κοινής χρήσης) και αυτό είναι το μοναδικό αναγνωριστικό του χρήστη
Από το ιδιωτικό κλειδί δημιουργείται ένα δημόσιο κλειδί (κοινής χρήσης). Χρησιμοποιείται για την αποστολή χρημάτων και για την επαλήθευση της συναλλαγής
Η συναλλαγή περιέχει τον αποστολέα (sender's public key), τον παραλήπτη (receiver’s public key) και το ποσό της συναλλαγής. Το σύνολο των περιεχομένων της συναλλαγής νοείται ως ένα μήνυμα (message).
Η επαλήθευση πραγματοποιείται χρησιμοποιόντας το δημόσιο κλειδί, το μήνυμα και την υπογραφή.
Ιδιωτικό κλειδί και μήνυμα συνδυάζονται για να δημιουργήσουν μία υπογραφή.
Δημιουργία Ιδιωτικού και Δημόσιου Κλειδιού
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
from cryptography.exceptions import InvalidSignature
question = input("Do you have a private key? Y/N\n")
if question == "N" or question == "n":
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048, #bits
backend=default_backend()) #get the default backend
pr = private_key
#pem file creation for future use. The node should ender the app with the same private and public key always. Pem files encode the private and public keys
pem = pr.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption())
with open("private_key.pem", "wb") as f:
f.write(pem)
public_key = private_key.public_key()
pr = pem.decode('utf-8')
pu = public_key
pem2 = pu.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo)
pu = pem2.decode('utf-8')
with open("public_key.pem", "wb") as f:
f.write(pem2)
#if there is already a private key we load the pem files and we decode them
else:
with open("private_key.pem", "rb") as key_file:
private_key = serialization.load_pem_private_key(key_file.read(), password = None)
with open("public_key.pem", "rb") as file:
public_key = serialization.load_pem_public_key(file.read())
pr = private_key
pem = pr.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption())
pr = pem.decode('utf-8')
pu = public_key
with open("private_key2.pem", "wb") as f:
f.write(pem)
pem2 = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo)
with open("public_key2.pem", "wb") as f:
f.write(pem2)
pu = pem2.decode('utf-8')
# OrpheumCoin
#Creating Signature
def sign(message, private_key):
sig = private_key.sign(
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
return sig
# OrheumCoin CODE
def verify (message, sig, public_key):
try:
public_key.verify(
sig,
message,
#we have to pass the same padding - hash
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
#If it completes return true
return True
except InvalidSignature:
print("ERROR!! Invalide sig!!!")
return False
except:
print("Error executing public.verify")
# OrheumCoin CODE
#Creating a file that it will be our mempool
mempool = []
with open('transactions.json', 'w') as jsonfile:
json.dump(mempool, jsonfile)
##A. BUILDING THE ARCHTECTURE OF THE BLOCKCHAIN
#Defining all the components of the blockchain
class Blockchain:
def __init__(self):
#initialize the chain that will contain the blocks.
self.chain = []
# list that will contain transactions
self.transactions = []
#Create the Genesis Block
self.create_block(proof = 1, previous_hash = '0')
#Set containing the nodes
self.nodes = set()
# OrpheumCoin CODE
import random
import datetime
import hashlib
import json
import os
from flask import Flask, jsonify
from flask import request
import requests
from uuid import uuid4
from urllib.parse import urlparse
#Create block function must aplly after mining a block
def create_block(self, proof, previous_hash):
#define the new block by creating a dictionary that will define each block
block = {'index': len(self.chain) + 1,
'timestamp': str(datetime.datetime.now()),
'proof': proof,
'previous_hash': previous_hash,
'transactions': self.transactions} #
self.transactions = []
os.remove('transactions.json')
with open('transactions.json', 'w') as jsonfile:
json.dump(mempool, jsonfile)
#append the block to the list (blockchain)
self.chain.append(block)
#return this block because we're going to display the
#informations of this block in Postman.
return block
# OrpheumCoin CODE
#create a method to get the previous block
def get_previous_block(self):
#last index of the chain
return self.chain[-1]
# OrpheumCoin CODE
#PROOF_of_WORK
def PoW(self, previous_proof):
nonce = random.getrandbits(32)
#Variable that checks if the new proof is the right one
check_proof = False
#Defining the problem that miners have to solve using hash function
while check_proof is False:
#creating a new variable that takes the previous proof and the nonce
# this operation should be non symmetrical
hash_operation = hashlib.sha256(str(nonce**2 -
previous_proof**2).encode()).hexdigest()
#we put it on a str in order to be acceptable from sha256. Encode function will include the str in the right format for sha256 (adds a b before the str)
#define the 2nd part of the problem.
#check if the 5 first char of this has operation are 5 zeros
if hash_operation[:5] == '00000':
check_proof = True
else:
#we add 1 to continue the loop and to check for 5 zeros
nonce += 1
return nonce
# OrpheumCoin CODE
#Creating a function that checks if everything is right in the blockchain
#Checking each block for a correct PoW &
#Check if Prev Hash of each block is equal to the hash of Prev Block
#This function takes a block as input and returns the sha256 cryptographic hash
def hash(self, block):
#we exclude The current hash from hashing
myDict = {}
myDict.update(block)
json.dumps(myDict)
if 'hash' in myDict:
del myDict['hash']
encoded_block = json.dumps(myDict, sort_keys = True).encode()
return hashlib.sha256(encoded_block).hexdigest()
# OrpheumCoin CODE
def is_chain_valid(self, chain):
#1. initialize prev_block as the first block of the chain
previous_block = chain[0]
#2. Initialize the looping variable. Each block has the index
#key in the dictionary which is the number of the block (starts at 1)
block_index = 1
#create a loop to iterate on all the blocks.
#while block index is lower than the length of the chain
while block_index < len(chain):
#check if prev_hash of Block = hash of prev_Block
block = chain[block_index]
if block['previous_hash'] != self.hash(previous_block):
return False
#check proof of each block for validation
#taking the prev_proof and then the current_proof and
#computing the hash operation between prev_proof & current_proof
#and checking if this hash operation starts with 5 leading zeros
previous_proof = previous_block['proof']
proof = block['proof']
#call the hash operation
hash_operation = hashlib.sha256(str(proof**2 -
previous_proof**2).encode()).hexdigest()
#check if starts with 0000
if hash_operation[:5] != '00000':
return False
#Updating Loop Variable Block index & prev block variable
previous_block = block
block_index += 1 # block_index = Block index +1
return True
# OrpheumCoin CODE
##Creating the format of the transactions (sender, receiver, amount of coins)
def add_transaction(self, sender, receiver, amount):
with open('transactions.json') as jsonfile:
pool = json.load(jsonfile)
self.transactions.append(pool)
self.transactions.append({'sender': sender,
'receiver': receiver,
'amount': amount})
#append the transaction into pool
with open('transactions.json', 'w') as jsonfile:
json.dump(self.transactions, jsonfile)
#list of transaction returns to empty
self.transactions=[]
##Returning the index of the (new) block that will
#receive these transactions
previous_block = self.get_previous_block()
## and then returning the index of the new block
#that will enclude the transactions.
return previous_block['index'] + 1
# OrpheumCoin CODE
##Creating consensus algorithm to make sure that all
#the nodes contain the same chain
#1. Adding Nodes
def add_node(self, address):
##parse the address of the node with the url parse function
parsed_url = urlparse(address)
self.nodes.add(parsed_url.netloc)
# OrpheumCoin CODE
##Creating a method that replaces any chain that is shorter
#with the longest one among the nodes of the network
def replace_chain(self):
#the set of nodes
network = self.nodes
#introducing the variable that some point
#will become the chain of the node
longest_chain = None
#the length of the largest chain
max_length = len(self.chain)
##if finding a chain with hιgher length , variable will be updated
##Looping over the node in the network
for node in network:
response = requests.get(f'http://{node}/get_chain')
if response.status_code == 200:
length = response.json()['length']
chain = response.json()['chain']
## if the chain in the node is valid and if the length
#of the other node is greater that the current
if length > max_length and self.is_chain_valid(chain):
##if that is true---> updating the max_length variable
max_length = length
longest_chain = chain
if longest_chain:
self.chain = longest_chain ##replacing the chain
return True
return False #as the chain was not replaced
# OrpheumCoin CODE
# Μέρος 2ο
# CREATING A WEB APP
app = Flask(__name__)
app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False
#contains the address of the rewarder node
node_address = str(uuid4()).replace('-', '')
#CREATING A BLOCKCHAIN
#instance object of our blockchain class
orpheum_blockchain = Blockchain()
Mining
Προσθήκη συναλλαγών
Έλεγχος εγκυρότητας αλυσίδας
# Μέρος 2ο
Επικαιροποίηση αλυσίδας
Σύνδεση κόμβων
# OrpheumCoin CODE
#Mining a new block
@app.route('/mine_block', methods = ['GET'])
def mine_block():
#a. Solving PoW (in order to get that proof,we apply the
#PoW function. But in order to apply thePoW function
#we need the prev proof. We use the get_prev_block function)
previous_block = orpheum_blockchain.get_previous_block()
#b. getting the previous proof to call the Pow
previous_proof = previous_block['proof']
#c.getting the proof of the future new block by calling the PoW method
proof = orpheum_blockchain.PoW(previous_proof)
#Adding the reward for the mining
##define sender,receiver & amount
orpheum_blockchain.add_transaction(sender = node_address,
receiver = pu, amount = 20)
previous_hash = orpheum_blockchain.hash(previous_block)
#append the block to the blockchain
block = orpheum_blockchain.create_block(proof, previous_hash)
#getting the current hash
block_hash = {'hash': orpheum_blockchain.hash(block)}
#d. Displaying it in Postman
response = {'message': 'CONGRATULATIONS!! MINING A BLOCK WAS SUCCESFULL!!',
'index': block['index'],
'timestamp': block['timestamp'],
'proof': block['proof'],
'previous_hash': block['previous_hash'],
'hash': block_hash['hash'],
'transactions': block['transactions']}
#adding current hash to the block
block.update(block_hash)
json.dumps(block)
#we want to return an HTTP code for success (200), everything is OK
return jsonify(response), 200
# OrpheumCoin CODE
#Create another request to get the whole blockchain
@app.route('/get_chain', methods = ['GET'])
#We are calling a new function in order to display the full chain
def get_chain():
response = {'chain': orpheum_blockchain.chain,
'length': len(orpheum_blockchain.chain)}
return jsonify(response), 200
# Checking if the Blockchain is valid
@app.route('/is_valid', methods = ['GET'])
def is_valid():
#calling the class function
is_valid = orpheum_blockchain.is_chain_valid(orpheum_blockchain.chain)
if is_valid:
response = {'message': 'All good. The Blockchain is valid.'}
else:
response = {'message': 'Houston, we have a problem. The Blockchain is not valid.'}
return jsonify(response), 200
# OrpheumCoin CODE
## Adding a new transaction to the Blockchain
@app.route('/add_transaction', methods = ['GET'])
def add_transaction():
#Defining the receiver
transaction_receiver = input("Enter receiver's public key:\n")
#Defining the amount of Orpheum coins
while True:
try:
transaction_amount = float(input("Enter the amount of Orpheum coins you want to send:\n"))
break
except ValueError:
print("ERROR!! Amount must be a number!!!")
#adding transaction to the transaction list. Index is the actual message and will be used to string in bytes the message
index = orpheum_blockchain.add_transaction(sender = pu, receiver = transaction_receiver,
amount = transaction_amount)
#from signature function
message = bytes(str(index), 'utf-8')
###Sign and verify the transaction
if __name__ == '__main__':
sig = sign(message, private_key)
print(sig)
correct = verify(message, sig, public_key)
print(correct)
if correct:
response = {'message': f'Success! Valid Signature! This transaction will be added to Block {index}'}
return jsonify(response)
else:
response = {'message': 'Signature not Valid.Try Again'}
return jsonify(response),200
Είναι απαραίτητο για γίνουν τα εξής:
1. Να συνδεθούν όλοι οι κόμβοι σε αυτό το δίκτυο.
2. Η αλυσίδα να είναι επικαιροποιημένη σε όλους τους κόμβους και να αντικαθιστάται εάν υπάρχει μεγαλύτερη από αυτή του κόμβου.
# Μέρος 2ο
## 1.Connecting new nodes
@app.route('/connect_node', methods = ['POST'])
def connect_node():
## getting the request of posting a new node in the network
json = request.get_json()
##connecting a new node to all other nodes in the network
nodes = json.get('nodes')
##checking if the request is invalid
if nodes is None:
return "No node", 400
##looping over the addresses of the nodes and add them one by one.
for node in nodes:
##use the add_node method in order to take the blockchain object
orpheum_blockchain.add_node(node)
## return the response
response = {'message': 'All nodes are connected. The Orpheum Blockchain
now contains the following nodes:',
'total_nodes': list(orpheum_blockchain.nodes)}
##list here is a function of the object blockchain and the variable node
#returning in json format
return jsonify(response), 201
# OrpheumCoin CODE
## 2.Replacing the chain by the longest chain if needed
@app.route('/replace_chain', methods = ['GET'])
def replace_chain():
##A Boolean is needing to check if the chain needs to be relaced.
#The replace_chain methon not only replace but returns true or false
is_chain_replaced = orpheum_blockchain.replace_chain()
if is_chain_replaced: ##true
response = {'message': 'The nodes had different chains so the
chain was replaced by the longest one.',
'new_chain': orpheum_blockchain.chain}
else:
response = {'message': 'All good. The chain is the largest one.',
'actual_chain': orpheum_blockchain.chain}
return jsonify(response), 200
# OrpheumCoin CODE
#C. RUNNING THE APP
app.run(host = '0.0.0.0', port = 5000)
# Μέρος 3ο