From 1b0a18de005021eecbe35fa6bd18dbbc8296a63e Mon Sep 17 00:00:00 2001 From: spekulaas Date: Mon, 6 Nov 2023 14:35:52 +0100 Subject: [PATCH] added all tutorials from lesson 7 --- .../701_TX1_A01_Mining_Reward/BlockChain.py | 24 +++ .../701_TX1_A01_Mining_Reward/Signature.py | 41 ++++ .../701_TX1_A01_Mining_Reward/Transaction.py | 100 ++++++++++ .../701_TX1_A01_Mining_Reward/TxBlock.py | 19 ++ .../701_TX1_A01_Mining_Reward/TxBlock_t.py | 144 ++++++++++++++ .../07-mining/702_TX2_A02_Nonce/BlockChain.py | 25 +++ .../07-mining/702_TX2_A02_Nonce/Signature.py | 41 ++++ .../702_TX2_A02_Nonce/Transaction.py | 100 ++++++++++ .../07-mining/702_TX2_A02_Nonce/TxBlock.py | 50 +++++ .../07-mining/702_TX2_A02_Nonce/TxBlock_t.py | 165 ++++++++++++++++ .../BlockChain.py | 25 +++ .../Signature.py | 41 ++++ .../Transaction.py | 100 ++++++++++ .../TxBlock.py | 50 +++++ .../TxBlock_t.py | 176 ++++++++++++++++++ 15 files changed, 1101 insertions(+) create mode 100644 period_1/07-mining/701_TX1_A01_Mining_Reward/BlockChain.py create mode 100644 period_1/07-mining/701_TX1_A01_Mining_Reward/Signature.py create mode 100644 period_1/07-mining/701_TX1_A01_Mining_Reward/Transaction.py create mode 100644 period_1/07-mining/701_TX1_A01_Mining_Reward/TxBlock.py create mode 100644 period_1/07-mining/701_TX1_A01_Mining_Reward/TxBlock_t.py create mode 100644 period_1/07-mining/702_TX2_A02_Nonce/BlockChain.py create mode 100644 period_1/07-mining/702_TX2_A02_Nonce/Signature.py create mode 100644 period_1/07-mining/702_TX2_A02_Nonce/Transaction.py create mode 100644 period_1/07-mining/702_TX2_A02_Nonce/TxBlock.py create mode 100644 period_1/07-mining/702_TX2_A02_Nonce/TxBlock_t.py create mode 100644 period_1/07-mining/703_TX2_A02_Nonce - tamper test/BlockChain.py create mode 100644 period_1/07-mining/703_TX2_A02_Nonce - tamper test/Signature.py create mode 100644 period_1/07-mining/703_TX2_A02_Nonce - tamper test/Transaction.py create mode 100644 period_1/07-mining/703_TX2_A02_Nonce - tamper test/TxBlock.py create mode 100644 period_1/07-mining/703_TX2_A02_Nonce - tamper test/TxBlock_t.py diff --git a/period_1/07-mining/701_TX1_A01_Mining_Reward/BlockChain.py b/period_1/07-mining/701_TX1_A01_Mining_Reward/BlockChain.py new file mode 100644 index 0000000..d3879c9 --- /dev/null +++ b/period_1/07-mining/701_TX1_A01_Mining_Reward/BlockChain.py @@ -0,0 +1,24 @@ +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes + +class CBlock: + + data = None + previousHash = None + previousBlock = None + def __init__(self, data, previousBlock): + self.data = data + self.previousBlock = previousBlock + if previousBlock != None: + self.previousHash = previousBlock.computeHash() + + def computeHash(self): + digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) + digest.update(bytes(str(self.data),'utf8')) + digest.update(bytes(str(self.previousHash),'utf8')) + return digest.finalize() + + def is_valid(self): + if self.previousBlock == None: + return True + return self.previousBlock.computeHash() == self.previousHash \ No newline at end of file diff --git a/period_1/07-mining/701_TX1_A01_Mining_Reward/Signature.py b/period_1/07-mining/701_TX1_A01_Mining_Reward/Signature.py new file mode 100644 index 0000000..740ee1a --- /dev/null +++ b/period_1/07-mining/701_TX1_A01_Mining_Reward/Signature.py @@ -0,0 +1,41 @@ +from cryptography.exceptions import * +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 + +def generate_keys(): + private_key = rsa.generate_private_key(public_exponent=65537,key_size=2048) + public_key = private_key.public_key() + + pbc_ser = public_key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo) + return private_key, pbc_ser + +def sign(message, private_key): + message = bytes(str(message), 'utf-8') + signature = private_key.sign( + message, + padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), + hashes.SHA256() + ) + return signature + +def verify(message, signature, pbc_ser): + message = bytes(str(message), 'utf-8') + public_key = serialization.load_pem_public_key(pbc_ser) + try: + public_key.verify( + signature, + message, + padding.PSS(mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH), + hashes.SHA256() + ) + return True + except InvalidSignature: + return False + except: + print("Error executing 'public_key.verify'") + return False \ No newline at end of file diff --git a/period_1/07-mining/701_TX1_A01_Mining_Reward/Transaction.py b/period_1/07-mining/701_TX1_A01_Mining_Reward/Transaction.py new file mode 100644 index 0000000..518ac8f --- /dev/null +++ b/period_1/07-mining/701_TX1_A01_Mining_Reward/Transaction.py @@ -0,0 +1,100 @@ +from gzip import READ +from operator import truediv +from optparse import AmbiguousOptionError + +REWARD_VALUE = 25.0 +NORMAL = 0 +REWARD = 1 + +from Signature import * + +class Tx: + def __init__(self, type = NORMAL): + + self.type = type + self.inputs = [] + self.outputs = [] + self.sigs = [] + self.reqd = [] + + def add_input(self, from_addr, amount): + self.inputs.append((from_addr, amount)) + + def add_output(self, to_addr, amount): + self.outputs.append((to_addr, amount)) + + def add_reqd(self, addr): + self.reqd.append(addr) + + def sign(self, private): + message = self.__gather() + newsig = sign(message, private) + self.sigs.append(newsig) + + def is_valid(self): + + if self.type == REWARD: + if len(self.inputs)!=0 and len(self.outputs)!=1: + return False + return True + + else: + total_in = 0 + total_out = 0 + message = self.__gather() + for addr,amount in self.inputs: + found = False + for s in self.sigs: + if verify(message, s, addr): + found = True + if not found: + # print ("No good sig found for " + str(message)) + return False + if amount < 0: + return False + total_in = total_in + amount + for addr in self.reqd: + found = False + for s in self.sigs: + if verify(message, s, addr): + found = True + if not found: + return False + for addr,amount in self.outputs: + if amount < 0: + return False + total_out = total_out + amount + + if total_out > total_in: + # print("Outputs exceed inputs") + return False + return True + + def __gather(self): + data=[] + data.append(self.inputs) + data.append(self.outputs) + data.append(self.reqd) + return data + + def __repr__(self): + + repr_str = "INPUTS:\n" + for addr, amt in self.inputs: + repr_str = repr_str + str(amt) + "from" + str(addr) + "\n" + + repr_str += "OUTPUTS:\n" + for addr, amt in self.outputs: + repr_str = repr_str + str(amt) + "to" + str(addr) + "\n" + + repr_str += "EXTRA REQUIRED SIGNATURES:\n" + for req_sig in self.reqd: + repr_str = repr_str + str(req_sig) + "\n" + + repr_str += "SIGNATURES:\n" + for sig in self.sigs: + repr_str = repr_str + str(sig) + "\n" + + repr_str += "END\n" + + return repr_str diff --git a/period_1/07-mining/701_TX1_A01_Mining_Reward/TxBlock.py b/period_1/07-mining/701_TX1_A01_Mining_Reward/TxBlock.py new file mode 100644 index 0000000..34b6a48 --- /dev/null +++ b/period_1/07-mining/701_TX1_A01_Mining_Reward/TxBlock.py @@ -0,0 +1,19 @@ +from BlockChain import CBlock +from Signature import generate_keys, sign, verify + + +class TxBlock (CBlock): + + def __init__(self, previousBlock): + super(TxBlock, self).__init__([], previousBlock) + + def addTx(self, Tx_in): + self.data.append(Tx_in) + + def is_valid(self): + if not super(TxBlock, self).is_valid(): + return False + for tx in self.data: + if not tx.is_valid(): + return False + return True \ No newline at end of file diff --git a/period_1/07-mining/701_TX1_A01_Mining_Reward/TxBlock_t.py b/period_1/07-mining/701_TX1_A01_Mining_Reward/TxBlock_t.py new file mode 100644 index 0000000..46f960e --- /dev/null +++ b/period_1/07-mining/701_TX1_A01_Mining_Reward/TxBlock_t.py @@ -0,0 +1,144 @@ +from BlockChain import CBlock +from Signature import generate_keys +import pickle +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.backends import default_backend + + +from Transaction import * +from TxBlock import * + +if __name__ == "__main__": + + alex_prv, alex_pbc = generate_keys() + mike_prv, mike_pbc = generate_keys() + rose_prv, rose_pbc = generate_keys() + mara_prv, mara_pbc = generate_keys() + +# Valid Blocks + + # Valid Transactions + Tx1 = Tx() + Tx1.add_input(alex_pbc, 5) + Tx1.add_output(rose_pbc, 5) + Tx1.sign(alex_prv) + + Tx2 = Tx() + Tx2.add_input(mike_pbc,1) + Tx2.add_output(rose_pbc,0.9) + Tx2.sign(mike_prv) + + # Block "root": Valid + root = TxBlock(None) + root.addTx(Tx1) + root.addTx(Tx2) + + # Valid Transactions + Tx3 = Tx() + Tx3.add_input(rose_pbc,3.1) + Tx3.add_output(alex_pbc, 3) + Tx3.sign(rose_prv) + + Tx4 = Tx() + Tx4.add_input(mike_pbc,2.1) + Tx4.add_output(mara_pbc, 2) + Tx4.add_reqd(rose_pbc) + Tx4.sign(mike_prv) + Tx4.sign(rose_prv) + + # Block "B1": Valid + B1 = TxBlock(root) + B1.addTx(Tx3) + B1.addTx(Tx4) + + + # Save "B1" + savefile = open("block.dat", "wb") + pickle.dump(B1, savefile) + savefile.close() + + # Load "B1" as "load_B1": should be valid + loadfile = open("block.dat" ,"rb") + load_B1 = pickle.load(loadfile) + loadfile.close() + + # validation of valid blocks + for b in [root, B1, load_B1, load_B1.previousBlock]: + if b.is_valid(): + print ("Success! Valid block is verified.") + else: + print ("Error! Valid block is not verified.") + + + +# Invalid Blocks + + # Invalid Transaction + Tx5 = Tx() + Tx5.add_input(rose_pbc, 1) + Tx5.add_output(mike_pbc, 15) + Tx5.sign(rose_prv) + + # Block "B2": Invalid + B2 = TxBlock(B1) + B2.addTx(Tx5) + + # tamper "load_B1": Invalid + load_B1.previousBlock.addTx(Tx4) + + # validation of invalid blocks + for b in [B2, load_B1]: + if b.is_valid(): + print ("Error! Invalid block is verified.") + else: + print ("Success! Invalid block is detected.") + + + # Test mining rewards and tx fees + B3 = TxBlock(B2) + B3.addTx(Tx2) + B3.addTx(Tx3) + B3.addTx(Tx4) + + # reward transaction + Tx6 = Tx(type=REWARD) + Tx6.add_output(mara_pbc, REWARD_VALUE) + B3.addTx(Tx6) + + # validation of valid block, containing tx for mining reward + 3 other valid txs + if B3.is_valid(): + print("Success! Block reward succeeded.") + else: + print("Error! Block reward failed.") + + # validation of valid block, containing tx for mining reward and tx fees + 3 other valid txs + B4 = TxBlock(B3) + B4.addTx(Tx2) + B4.addTx(Tx3) + B4.addTx(Tx4) + + Tx7 = Tx(type=REWARD) + Tx7.add_output(mara_pbc, 25.3) + B4.addTx(Tx7) + + if B4.is_valid(): + print("Success! Transaction fees succeeded.") + else: + print("Error! Transaction fees failed.") + + + #Greedy miner + B5 = TxBlock(B4) + B5.addTx(Tx2) + B5.addTx(Tx3) + B5.addTx(Tx4) + + Tx8 = Tx(type=REWARD) + Tx8.add_output(mara_pbc, REWARD_VALUE + 1 + 0.3) + + # validation of invalid block, containing tx for (invalid) mining reward (26 coins for reward) + B5.addTx(Tx8) + if not B5.is_valid(): + print("Success! Greedy miner is detected.") + else: + print("Error! Greedy miner is not detected") diff --git a/period_1/07-mining/702_TX2_A02_Nonce/BlockChain.py b/period_1/07-mining/702_TX2_A02_Nonce/BlockChain.py new file mode 100644 index 0000000..c20e155 --- /dev/null +++ b/period_1/07-mining/702_TX2_A02_Nonce/BlockChain.py @@ -0,0 +1,25 @@ +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes + +class CBlock: + + data = None + previousHash = None + previousBlock = None + def __init__(self, data, previousBlock): + self.data = data + self.blockHash = None + self.previousBlock = previousBlock + if previousBlock != None: + self.previousHash = previousBlock.computeHash() + + def computeHash(self): + digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) + digest.update(bytes(str(self.data),'utf8')) + digest.update(bytes(str(self.previousHash),'utf8')) + return digest.finalize() + + def is_valid(self): + if self.previousBlock == None: + return True + return self.previousBlock.computeHash() == self.previousHash \ No newline at end of file diff --git a/period_1/07-mining/702_TX2_A02_Nonce/Signature.py b/period_1/07-mining/702_TX2_A02_Nonce/Signature.py new file mode 100644 index 0000000..740ee1a --- /dev/null +++ b/period_1/07-mining/702_TX2_A02_Nonce/Signature.py @@ -0,0 +1,41 @@ +from cryptography.exceptions import * +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 + +def generate_keys(): + private_key = rsa.generate_private_key(public_exponent=65537,key_size=2048) + public_key = private_key.public_key() + + pbc_ser = public_key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo) + return private_key, pbc_ser + +def sign(message, private_key): + message = bytes(str(message), 'utf-8') + signature = private_key.sign( + message, + padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), + hashes.SHA256() + ) + return signature + +def verify(message, signature, pbc_ser): + message = bytes(str(message), 'utf-8') + public_key = serialization.load_pem_public_key(pbc_ser) + try: + public_key.verify( + signature, + message, + padding.PSS(mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH), + hashes.SHA256() + ) + return True + except InvalidSignature: + return False + except: + print("Error executing 'public_key.verify'") + return False \ No newline at end of file diff --git a/period_1/07-mining/702_TX2_A02_Nonce/Transaction.py b/period_1/07-mining/702_TX2_A02_Nonce/Transaction.py new file mode 100644 index 0000000..518ac8f --- /dev/null +++ b/period_1/07-mining/702_TX2_A02_Nonce/Transaction.py @@ -0,0 +1,100 @@ +from gzip import READ +from operator import truediv +from optparse import AmbiguousOptionError + +REWARD_VALUE = 25.0 +NORMAL = 0 +REWARD = 1 + +from Signature import * + +class Tx: + def __init__(self, type = NORMAL): + + self.type = type + self.inputs = [] + self.outputs = [] + self.sigs = [] + self.reqd = [] + + def add_input(self, from_addr, amount): + self.inputs.append((from_addr, amount)) + + def add_output(self, to_addr, amount): + self.outputs.append((to_addr, amount)) + + def add_reqd(self, addr): + self.reqd.append(addr) + + def sign(self, private): + message = self.__gather() + newsig = sign(message, private) + self.sigs.append(newsig) + + def is_valid(self): + + if self.type == REWARD: + if len(self.inputs)!=0 and len(self.outputs)!=1: + return False + return True + + else: + total_in = 0 + total_out = 0 + message = self.__gather() + for addr,amount in self.inputs: + found = False + for s in self.sigs: + if verify(message, s, addr): + found = True + if not found: + # print ("No good sig found for " + str(message)) + return False + if amount < 0: + return False + total_in = total_in + amount + for addr in self.reqd: + found = False + for s in self.sigs: + if verify(message, s, addr): + found = True + if not found: + return False + for addr,amount in self.outputs: + if amount < 0: + return False + total_out = total_out + amount + + if total_out > total_in: + # print("Outputs exceed inputs") + return False + return True + + def __gather(self): + data=[] + data.append(self.inputs) + data.append(self.outputs) + data.append(self.reqd) + return data + + def __repr__(self): + + repr_str = "INPUTS:\n" + for addr, amt in self.inputs: + repr_str = repr_str + str(amt) + "from" + str(addr) + "\n" + + repr_str += "OUTPUTS:\n" + for addr, amt in self.outputs: + repr_str = repr_str + str(amt) + "to" + str(addr) + "\n" + + repr_str += "EXTRA REQUIRED SIGNATURES:\n" + for req_sig in self.reqd: + repr_str = repr_str + str(req_sig) + "\n" + + repr_str += "SIGNATURES:\n" + for sig in self.sigs: + repr_str = repr_str + str(sig) + "\n" + + repr_str += "END\n" + + return repr_str diff --git a/period_1/07-mining/702_TX2_A02_Nonce/TxBlock.py b/period_1/07-mining/702_TX2_A02_Nonce/TxBlock.py new file mode 100644 index 0000000..97a1747 --- /dev/null +++ b/period_1/07-mining/702_TX2_A02_Nonce/TxBlock.py @@ -0,0 +1,50 @@ +from BlockChain import CBlock +from Signature import generate_keys, sign, verify +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.backends import default_backend + +import random + +REWARD_VALUE = 25.0 +leading_zeros = 2 +next_char_limit = 20 + +class TxBlock (CBlock): + + def __init__(self, previousBlock): + self.nonce = "A random nonce" + super(TxBlock, self).__init__([], previousBlock) + + def addTx(self, Tx_in): + self.data.append(Tx_in) + + def __count_totals(self): + total_in = 0 + total_out = 0 + for tx in self.data: + for addr, amt in tx.inputs: + total_in = total_in + amt + for addr, amt in tx.outputs: + total_out = total_out + amt + return total_in, total_out + + def is_valid(self): + if not super(TxBlock, self).is_valid(): + return False + for tx in self.data: + if not tx.is_valid(): + return False + + total_in, total_out = self.__count_totals() + + Tx_Balance = round(total_out - total_in, 10) + + if Tx_Balance > REWARD_VALUE: + return False + return True + + def good_nonce(self): + return False + + def find_nonce(self): + return None diff --git a/period_1/07-mining/702_TX2_A02_Nonce/TxBlock_t.py b/period_1/07-mining/702_TX2_A02_Nonce/TxBlock_t.py new file mode 100644 index 0000000..bf66c33 --- /dev/null +++ b/period_1/07-mining/702_TX2_A02_Nonce/TxBlock_t.py @@ -0,0 +1,165 @@ +from BlockChain import CBlock +from Signature import generate_keys, sign, verify +import pickle +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.backends import default_backend +import time + +from Transaction import * + +from TxBlock import * + +MIN_MINING_TIME = 30 +MAX_MINING_TIME = 60 + +if __name__ == "__main__": + + alex_prv, alex_pbc = generate_keys() + mike_prv, mike_pbc = generate_keys() + rose_prv, rose_pbc = generate_keys() + mara_prv, mara_pbc = generate_keys() + +# Valid Blocks + + # Valid Transactions + Tx1 = Tx() + Tx1.add_input(alex_pbc, 5) + Tx1.add_output(rose_pbc, 5) + Tx1.sign(alex_prv) + + Tx2 = Tx() + Tx2.add_input(mike_pbc,1) + Tx2.add_output(rose_pbc,0.9) + Tx2.sign(mike_prv) + + # Block "root": Valid + root = TxBlock(None) + root.addTx(Tx1) + root.addTx(Tx2) + + # Valid Transactions + Tx3 = Tx() + Tx3.add_input(rose_pbc,3.1) + Tx3.add_output(alex_pbc, 3) + Tx3.sign(rose_prv) + + Tx4 = Tx() + Tx4.add_input(mike_pbc,2.1) + Tx4.add_output(mara_pbc, 2) + Tx4.add_reqd(rose_pbc) + Tx4.sign(mike_prv) + Tx4.sign(rose_prv) + + # Block "B1": Valid + B1 = TxBlock(root) + B1.addTx(Tx3) + B1.addTx(Tx4) + +# ------------------------------------------------- + start = time.time() + nonce = B1.find_nonce() + elapsed = time.time() - start + + if B1.good_nonce(): + print("Success! Nonce is good!") + print(f'Accepted Nonce = {str(nonce)}') + + print("elapsed time: " + str(elapsed) + " s.") + if elapsed < MIN_MINING_TIME: + print("Alarm! Mining is too fast") + elif elapsed > MAX_MINING_TIME: + print("Alarm! Mining is too Slow") + + else: + print("ERROR! Bad nonce") + +# ------------------------------------------------- + + + savefile = open("block.dat", "wb") + pickle.dump(B1, savefile) + savefile.close() + + loadfile = open("block.dat" ,"rb") + load_B1 = pickle.load(loadfile) + loadfile.close() + + for b in [root, B1, load_B1, load_B1.previousBlock]: + if b.is_valid(): + print ("Success! Valid block is verified.") + else: + print ("Error! Valid block is not verified.") + +# ---------------------------------------------------------------------- + if load_B1.good_nonce(): # ------ + print("Success! Nonce is good after save and load!") # ------ + else: # ------ + print("ERROR! Bad nonce after load") # ------ +# ---------------------------------------------------------------------- + + + Tx5 = Tx() + Tx5.add_input(rose_pbc, 1) + Tx5.add_output(mike_pbc, 10) + Tx5.sign(rose_prv) + + B2 = TxBlock(B1) + B2.addTx(Tx5) + + load_B1.previousBlock.addTx(Tx4) + + for b in [B2, load_B1]: + if b.is_valid(): + print ("Error! Invalid block is verified.") + else: + print ("Success! Invalid blocks is detected.") + + + # Test mining rewards and tx fees + B3 = TxBlock(B2) + B3.addTx(Tx2) + B3.addTx(Tx3) + B3.addTx(Tx4) + + # reward transaction + Tx6 = Tx(type=REWARD) + Tx6.add_output(mara_pbc, REWARD_VALUE) + B3.addTx(Tx6) + + # validation of valid block, containing tx for mining reward + 3 other valid txs + if B3.is_valid(): + print("Success! Block reward succeeded.") + else: + print("Error! Block reward failed.") + + # validation of valid block, containing tx for mining reward and tx fees + 3 other valid txs + B4 = TxBlock(B3) + B4.addTx(Tx2) + B4.addTx(Tx3) + B4.addTx(Tx4) + + Tx7 = Tx(type=REWARD) + Tx7.add_output(mara_pbc, 25.3) + B4.addTx(Tx7) + + if B4.is_valid(): + print("Success! Transaction fees succeeded.") + else: + print("Error! Transaction fees failed.") + + + #Greedy miner + B5 = TxBlock(B4) + B5.addTx(Tx2) + B5.addTx(Tx3) + B5.addTx(Tx4) + + Tx8 = Tx() + Tx8.add_output(mara_pbc, REWARD_VALUE + 1 + 0.3) + + # validation of invalid block, containing tx for (invalid) mining reward (26 coins for reward) + B5.addTx(Tx8) + if not B5.is_valid(): + print("Success! Greedy miner is detected.") + else: + print("Error! Greedy miner is not detected") \ No newline at end of file diff --git a/period_1/07-mining/703_TX2_A02_Nonce - tamper test/BlockChain.py b/period_1/07-mining/703_TX2_A02_Nonce - tamper test/BlockChain.py new file mode 100644 index 0000000..c20e155 --- /dev/null +++ b/period_1/07-mining/703_TX2_A02_Nonce - tamper test/BlockChain.py @@ -0,0 +1,25 @@ +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes + +class CBlock: + + data = None + previousHash = None + previousBlock = None + def __init__(self, data, previousBlock): + self.data = data + self.blockHash = None + self.previousBlock = previousBlock + if previousBlock != None: + self.previousHash = previousBlock.computeHash() + + def computeHash(self): + digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) + digest.update(bytes(str(self.data),'utf8')) + digest.update(bytes(str(self.previousHash),'utf8')) + return digest.finalize() + + def is_valid(self): + if self.previousBlock == None: + return True + return self.previousBlock.computeHash() == self.previousHash \ No newline at end of file diff --git a/period_1/07-mining/703_TX2_A02_Nonce - tamper test/Signature.py b/period_1/07-mining/703_TX2_A02_Nonce - tamper test/Signature.py new file mode 100644 index 0000000..740ee1a --- /dev/null +++ b/period_1/07-mining/703_TX2_A02_Nonce - tamper test/Signature.py @@ -0,0 +1,41 @@ +from cryptography.exceptions import * +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 + +def generate_keys(): + private_key = rsa.generate_private_key(public_exponent=65537,key_size=2048) + public_key = private_key.public_key() + + pbc_ser = public_key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo) + return private_key, pbc_ser + +def sign(message, private_key): + message = bytes(str(message), 'utf-8') + signature = private_key.sign( + message, + padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), + hashes.SHA256() + ) + return signature + +def verify(message, signature, pbc_ser): + message = bytes(str(message), 'utf-8') + public_key = serialization.load_pem_public_key(pbc_ser) + try: + public_key.verify( + signature, + message, + padding.PSS(mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH), + hashes.SHA256() + ) + return True + except InvalidSignature: + return False + except: + print("Error executing 'public_key.verify'") + return False \ No newline at end of file diff --git a/period_1/07-mining/703_TX2_A02_Nonce - tamper test/Transaction.py b/period_1/07-mining/703_TX2_A02_Nonce - tamper test/Transaction.py new file mode 100644 index 0000000..518ac8f --- /dev/null +++ b/period_1/07-mining/703_TX2_A02_Nonce - tamper test/Transaction.py @@ -0,0 +1,100 @@ +from gzip import READ +from operator import truediv +from optparse import AmbiguousOptionError + +REWARD_VALUE = 25.0 +NORMAL = 0 +REWARD = 1 + +from Signature import * + +class Tx: + def __init__(self, type = NORMAL): + + self.type = type + self.inputs = [] + self.outputs = [] + self.sigs = [] + self.reqd = [] + + def add_input(self, from_addr, amount): + self.inputs.append((from_addr, amount)) + + def add_output(self, to_addr, amount): + self.outputs.append((to_addr, amount)) + + def add_reqd(self, addr): + self.reqd.append(addr) + + def sign(self, private): + message = self.__gather() + newsig = sign(message, private) + self.sigs.append(newsig) + + def is_valid(self): + + if self.type == REWARD: + if len(self.inputs)!=0 and len(self.outputs)!=1: + return False + return True + + else: + total_in = 0 + total_out = 0 + message = self.__gather() + for addr,amount in self.inputs: + found = False + for s in self.sigs: + if verify(message, s, addr): + found = True + if not found: + # print ("No good sig found for " + str(message)) + return False + if amount < 0: + return False + total_in = total_in + amount + for addr in self.reqd: + found = False + for s in self.sigs: + if verify(message, s, addr): + found = True + if not found: + return False + for addr,amount in self.outputs: + if amount < 0: + return False + total_out = total_out + amount + + if total_out > total_in: + # print("Outputs exceed inputs") + return False + return True + + def __gather(self): + data=[] + data.append(self.inputs) + data.append(self.outputs) + data.append(self.reqd) + return data + + def __repr__(self): + + repr_str = "INPUTS:\n" + for addr, amt in self.inputs: + repr_str = repr_str + str(amt) + "from" + str(addr) + "\n" + + repr_str += "OUTPUTS:\n" + for addr, amt in self.outputs: + repr_str = repr_str + str(amt) + "to" + str(addr) + "\n" + + repr_str += "EXTRA REQUIRED SIGNATURES:\n" + for req_sig in self.reqd: + repr_str = repr_str + str(req_sig) + "\n" + + repr_str += "SIGNATURES:\n" + for sig in self.sigs: + repr_str = repr_str + str(sig) + "\n" + + repr_str += "END\n" + + return repr_str diff --git a/period_1/07-mining/703_TX2_A02_Nonce - tamper test/TxBlock.py b/period_1/07-mining/703_TX2_A02_Nonce - tamper test/TxBlock.py new file mode 100644 index 0000000..97a1747 --- /dev/null +++ b/period_1/07-mining/703_TX2_A02_Nonce - tamper test/TxBlock.py @@ -0,0 +1,50 @@ +from BlockChain import CBlock +from Signature import generate_keys, sign, verify +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.backends import default_backend + +import random + +REWARD_VALUE = 25.0 +leading_zeros = 2 +next_char_limit = 20 + +class TxBlock (CBlock): + + def __init__(self, previousBlock): + self.nonce = "A random nonce" + super(TxBlock, self).__init__([], previousBlock) + + def addTx(self, Tx_in): + self.data.append(Tx_in) + + def __count_totals(self): + total_in = 0 + total_out = 0 + for tx in self.data: + for addr, amt in tx.inputs: + total_in = total_in + amt + for addr, amt in tx.outputs: + total_out = total_out + amt + return total_in, total_out + + def is_valid(self): + if not super(TxBlock, self).is_valid(): + return False + for tx in self.data: + if not tx.is_valid(): + return False + + total_in, total_out = self.__count_totals() + + Tx_Balance = round(total_out - total_in, 10) + + if Tx_Balance > REWARD_VALUE: + return False + return True + + def good_nonce(self): + return False + + def find_nonce(self): + return None diff --git a/period_1/07-mining/703_TX2_A02_Nonce - tamper test/TxBlock_t.py b/period_1/07-mining/703_TX2_A02_Nonce - tamper test/TxBlock_t.py new file mode 100644 index 0000000..2886200 --- /dev/null +++ b/period_1/07-mining/703_TX2_A02_Nonce - tamper test/TxBlock_t.py @@ -0,0 +1,176 @@ +from BlockChain import CBlock +from Signature import generate_keys, sign, verify +import pickle +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.backends import default_backend +import time + +from Transaction import * + +from TxBlock import * + +MIN_MINING_TIME = 30 +MAX_MINING_TIME = 60 + +if __name__ == "__main__": + + alex_prv, alex_pbc = generate_keys() + mike_prv, mike_pbc = generate_keys() + rose_prv, rose_pbc = generate_keys() + mara_prv, mara_pbc = generate_keys() + +# Valid Blocks + + # Valid Transactions + Tx1 = Tx() + Tx1.add_input(alex_pbc, 5) + Tx1.add_output(rose_pbc, 5) + Tx1.sign(alex_prv) + + Tx2 = Tx() + Tx2.add_input(mike_pbc,1) + Tx2.add_output(rose_pbc,0.9) + Tx2.sign(mike_prv) + + # Block "root": Valid + root = TxBlock(None) + root.addTx(Tx1) + root.addTx(Tx2) + + # Valid Transactions + Tx3 = Tx() + Tx3.add_input(rose_pbc,3.1) + Tx3.add_output(alex_pbc, 3) + Tx3.sign(rose_prv) + + Tx4 = Tx() + Tx4.add_input(mike_pbc,2.1) + Tx4.add_output(mara_pbc, 2) + Tx4.add_reqd(rose_pbc) + Tx4.sign(mike_prv) + Tx4.sign(rose_prv) + + # Block "B1": Valid + B1 = TxBlock(root) + B1.addTx(Tx3) + B1.addTx(Tx4) + +# ------------------------------------------------- + start = time.time() + nonce = B1.find_nonce() + elapsed = time.time() - start + + if B1.good_nonce(): + print("Success! Nonce is good!") + print(f'Accepted Nonce = {str(nonce)}') + + print("elapsed time: " + str(elapsed) + " s.") + if elapsed < MIN_MINING_TIME: + print("Alarm! Mining is too fast") + elif elapsed > MAX_MINING_TIME: + print("Alarm! Mining is too Slow") + + else: + print("ERROR! Bad nonce") + +# ------------------------------------------------- + + + savefile = open("block.dat", "wb") + pickle.dump(B1, savefile) + savefile.close() + + loadfile = open("block.dat" ,"rb") + load_B1 = pickle.load(loadfile) + loadfile.close() + + for b in [root, B1, load_B1, load_B1.previousBlock]: + if b.is_valid(): + print ("Success! Valid block is verified.") + else: + print ("Error! Valid block is not verified.") + +# ---------------------------------------------------------------------- + if load_B1.good_nonce(): # ------ + print("Success! Nonce is good after save and load!") # ------ + else: # ------ + print("ERROR! Bad nonce after load") # ------ +# ---------------------------------------------------------------------- + + + Tx5 = Tx() + Tx5.add_input(rose_pbc, 1) + Tx5.add_output(mike_pbc, 10) + Tx5.sign(rose_prv) + + B2 = TxBlock(B1) + B2.addTx(Tx5) + + load_B1.previousBlock.addTx(Tx4) + + for b in [B2, load_B1]: + if b.is_valid(): + print ("Error! Invalid block is verified.") + else: + print ("Success! Invalid blocks is detected.") + + + # Test mining rewards and tx fees + B3 = TxBlock(B2) + B3.addTx(Tx2) + B3.addTx(Tx3) + B3.addTx(Tx4) + + # reward transaction + Tx6 = Tx(type=REWARD) + Tx6.add_output(mara_pbc, REWARD_VALUE) + B3.addTx(Tx6) + + # validation of valid block, containing tx for mining reward + 3 other valid txs + if B3.is_valid(): + print("Success! Block reward succeeded.") + else: + print("Error! Block reward failed.") + + # validation of valid block, containing tx for mining reward and tx fees + 3 other valid txs + B4 = TxBlock(B3) + B4.addTx(Tx2) + B4.addTx(Tx3) + B4.addTx(Tx4) + + Tx7 = Tx(type=REWARD) + Tx7.add_output(mara_pbc, 25.3) + B4.addTx(Tx7) + + if B4.is_valid(): + print("Success! Transaction fees succeeded.") + else: + print("Error! Transaction fees failed.") + + + #Greedy miner + B5 = TxBlock(B4) + B5.addTx(Tx2) + B5.addTx(Tx3) + B5.addTx(Tx4) + + Tx8 = Tx() + Tx8.add_output(mara_pbc, REWARD_VALUE + 1 + 0.3) + + # validation of invalid block, containing tx for (invalid) mining reward (26 coins for reward) + B5.addTx(Tx8) + if not B5.is_valid(): + print("Success! Greedy miner is detected.") + else: + print("Error! Greedy miner is not detected") + + + + # Check tamper + Tx9 = Tx(type=REWARD) + Tx9.add_output(alex_pbc, 25.0) + B1.addTx(Tx9) + if B1.is_valid(): + print ("Error! Tampered block is verified.") + else: + print ("Success! Tampered block is detected.") \ No newline at end of file