diff --git a/period_1/06-transaction_block/601_T01_Tx_SaveLoad/BlockChain.py b/period_1/06-transaction_block/601_T01_Tx_SaveLoad/BlockChain.py new file mode 100644 index 0000000..f6fb2f3 --- /dev/null +++ b/period_1/06-transaction_block/601_T01_Tx_SaveLoad/BlockChain.py @@ -0,0 +1,19 @@ +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() \ No newline at end of file diff --git a/period_1/06-transaction_block/601_T01_Tx_SaveLoad/KeyFile_test_a.py b/period_1/06-transaction_block/601_T01_Tx_SaveLoad/KeyFile_test_a.py new file mode 100644 index 0000000..d9bef3e --- /dev/null +++ b/period_1/06-transaction_block/601_T01_Tx_SaveLoad/KeyFile_test_a.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Transactions -> Ledger (Transaction Blockchain): Tutorial 1 + +The goal of this tutorial is to learn how custom data structures can be de-/serialized +before storage and transference. In addition, you will learn how an application will fail +if serialization is not applied before storing an object in a file. +Any data structure needs to be serialized as a byte stream before storing it +in a file or transferring it through the network. Pickle module supports such an operation. +However, it has limited support for data structures to be serialized automatically. +Custom data structures such as asymmetric keys and transactions are not supported natively. +They need to be serialized before storage. Pickle will throw an exception if it +does not support the data structure. Some third-party modules such as Cryptography provide +such serialization functions. Only some objects are needed for storage or transference +should be serialized. For instance, we might need only to serialize public keys and +leave private keys not serialized. Depending on your implementation scenario, +you might also check JSON serialization functions. + +Many serialization formats support multiple different types of asymmetric keys +and will return an instance of the appropriate type. You should check that +the returned key matches the type your application expects when using these methods. + +In this scenario we generate a asymmetric keys. Use the private to sign a message. +Your task is to de-/serialize public key of alex before storing to and when loading from a file. + +Your task is to: + * locate the TODOs in this file + * complete the missing part from the code + * run the test of this exercise located in same folder. + +To test run 'KeyFile_test_a.py' in your command line + +Notes: + * do not change class structure or method signature to not break unit tests + * visit this url for more information on this topic: + https://cryptography.io/en/latest/hazmat/primitives/asymmetric/serialization/ + https://cryptography.io/en/latest/hazmat/primitives/asymmetric/serialization/#cryptography.hazmat.primitives.serialization.load_pem_public_key +""" +from Signature import * +from Transaction import * +import pickle + +if __name__ == "__main__": + + alex_prv, alex_pbc = generate_keys() + + sample_message = b'a test message' + sig = sign(sample_message, alex_prv) + print(verify(sample_message, sig, alex_pbc)) + + savefile = open("key.dat", "wb") + + # TODO 1: Serialize the public key to bytes + # Use PEM encoding + + pickle.dump(alex_pbc, savefile) + savefile.close() + + + loadfile = open("key.dat", "rb") + new_pbc = pickle.load(loadfile) + + # TODO 2: Deserialize a public key from PEM encoded data + # Load using one of the function that supports asymmetric public key-loading + + + loadfile.close() + print(verify(sample_message, sig, new_pbc)) \ No newline at end of file diff --git a/period_1/06-transaction_block/601_T01_Tx_SaveLoad/Signature.py b/period_1/06-transaction_block/601_T01_Tx_SaveLoad/Signature.py new file mode 100644 index 0000000..a713989 --- /dev/null +++ b/period_1/06-transaction_block/601_T01_Tx_SaveLoad/Signature.py @@ -0,0 +1,35 @@ +from cryptography.exceptions import * +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding + +def generate_keys(): + private_key = rsa.generate_private_key(public_exponent=65537,key_size=2048) + public_key = private_key.public_key() + return private_key, public_key + +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, public_key): + message = bytes(str(message), 'utf-8') + 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/06-transaction_block/601_T01_Tx_SaveLoad/Transaction.py b/period_1/06-transaction_block/601_T01_Tx_SaveLoad/Transaction.py new file mode 100644 index 0000000..fbc2ed5 --- /dev/null +++ b/period_1/06-transaction_block/601_T01_Tx_SaveLoad/Transaction.py @@ -0,0 +1,66 @@ +from Signature import * + +class Tx: + inputs = None + outputs =None + sigs = None + reqd = None + def __init__(self): + 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): + 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 diff --git a/period_1/06-transaction_block/601_T01_Tx_SaveLoad/TxFile_test_a.py b/period_1/06-transaction_block/601_T01_Tx_SaveLoad/TxFile_test_a.py new file mode 100644 index 0000000..e8afddc --- /dev/null +++ b/period_1/06-transaction_block/601_T01_Tx_SaveLoad/TxFile_test_a.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Transactions -> Ledger (Transaction Blockchain): Tutorial 1 + +The goal of this tutorial is to learn how custom data structures can be de-/serialized +before storage and transference. In addition, you will learn how an application will fail +if serialization is not applied before storing an object in a file. +Any data structure needs to be serialized as a byte stream before storing it +in a file or transferring it through the network. Pickle module supports such an operation. +However, it has limited support for data structures to be serialized automatically. +Custom data structures such as asymmetric keys and transactions are not supported natively. +They need to be serialized before storage. Pickle will throw an exception if it +does not support the data structure. Some third-party modules such as Cryptography provide +such serialization functions. Only some objects are needed for storage or transference +should be serialized. For instance, we might need only to serialize public keys and +leave private keys not serialized. Depending on your implementation scenario, +you might also check JSON serialization functions. + +In this scenario we try to store a custom data structure into a file without serialization. +Pickle will fail to store the byte stream into the file as it does not recognize the object. +Your task is to try different Pickle.dump() calls and check the output. + +Your task is to: + * locate the TODOs in this file + * complete the missing part from the code + * run the test of this exercise located in same folder. + +To test run 'TxFile_test_a.py' in your command line + +Notes: + * do not change class structure or method signature to not break unit tests + * visit this url for more information on this topic: + https://cryptography.io/en/latest/hazmat/primitives/asymmetric/serialization/ + https://cryptography.io/en/latest/hazmat/primitives/asymmetric/serialization/#cryptography.hazmat.primitives.serialization.load_pem_public_key +""" +from Signature import * +from Transaction import * +import pickle + +if __name__ == "__main__": + + # Generating asymmetric keys for multiple users + alex_prv, alex_pbc = generate_keys() + mike_prv, mike_pbc = generate_keys() + rose_prv, rose_pbc = generate_keys() + mara_prv, mara_pbc = generate_keys() + + # Creating a transaction from alex to mike and signing it with alex private key + Tx1 = Tx() + Tx1.add_input(alex_pbc, 1) + Tx1.add_output(mike_pbc, 1) + Tx1.sign(alex_prv) + + # Checking the validity of this transaction + if Tx1.is_valid(): + print("Success! Tx is valid") + else: + print("Fail! Tx is invalid") + + # Opening a file to store a transaction data + savefile = open("tx.dat", "wb") + + # TODO 1: Try different dump() calls by uncommenting it + # Make sure you uncomment other dump calls before trying a new one + # Application should stop here as it fails to serialize our custom data structure (transaction) + pickle.dump(Tx1, savefile) + # pickle.dump(Tx1.inputs[0][0], savefile) + # pickle.dump(alex_pbc, savefile) + + savefile.close() + + loadfile = open("tx.dat", "rb") + newTx = pickle.load(loadfile) + + if newTx.is_valid(): + print("Sucess! Loaded tx is valid") + loadfile.close() \ No newline at end of file diff --git a/period_1/06-transaction_block/602_T02_A01_PublicKey_Serialization/Signature.py b/period_1/06-transaction_block/602_T02_A01_PublicKey_Serialization/Signature.py new file mode 100644 index 0000000..21659ab --- /dev/null +++ b/period_1/06-transaction_block/602_T02_A01_PublicKey_Serialization/Signature.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""Transactions -> Ledger (Key Serialization): Tutorial 2 + +The goal of this tutorial is to learn how implement serialization mechanism into +different method to simplify your operation. Due to the scope limitation of this tutorial +we expect de-/serialization calls to be part of methods implementation (generate_keys() and verify()). +In an ideal scenario It would be more convenient to implement it in a more abstract way +as a part of your custom data structure + +In this scenario we serialize only the generate public key of a user when keys are generated. +The private key remain as it is. When verifying the signature it is expected that the given public key +should be deserialized before usage. +It is expected that you rebuild Signature module to work with serialized keys + + +Your task is to: + * locate the TODOs in this file + * complete the missing part from the code + * run the test of this exercise located in same folder. + +To test run 'Signature_t.py' in your command line + +Notes: + * do not change class structure or method signature to not break unit tests + * visit this url for more information on this topic: + https://cryptography.io/en/latest/hazmat/primitives/asymmetric/serialization/ + https://cryptography.io/en/latest/hazmat/primitives/asymmetric/serialization/#cryptography.hazmat.primitives.serialization.load_pem_public_key +""" +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 + +# TODO 1: Generate first a private key, then a public key. As a result return both values. +# Make sure you generate the keys in the correct order. +# Use recommended algorithm values where possible +# Suggested key size 2048 +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 + +# TODO 2: Sign a passed message using the passed private key +# Signing and verifying algorithms must be the same +def sign(message, private_key): + # TODO 2: Sign a passed message using the passed private key +# Signing and verifying algorithms must be the same + 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 + +# TODO 3: Verify a signature for a message with the passed public key +# Signing and verifying algorithms values must be the same +# Make sure to handle exception properly if verification fails +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: + return False \ No newline at end of file diff --git a/period_1/06-transaction_block/602_T02_A01_PublicKey_Serialization/Signature_t.py b/period_1/06-transaction_block/602_T02_A01_PublicKey_Serialization/Signature_t.py new file mode 100644 index 0000000..0ebd287 --- /dev/null +++ b/period_1/06-transaction_block/602_T02_A01_PublicKey_Serialization/Signature_t.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +""" +This test case will verify if the provided solution by a student for Signature.py is correct. +""" +from Signature import * + +if __name__ == '__main__': +# Generating asymmetric keys for both alex and mike + # returned public key of both mike and alex should be serialized + alex_prv, alex_pbc = generate_keys() + mike_prv, mike_pbc = generate_keys() + + alex_message = b'pay 10 euro to bob' + + +# Verification of a signature using public key: + + # It should be correct because Alex is signing it and we verifying using his public key + alex_signature = sign(alex_message, alex_prv) + + verified = verify(alex_message, alex_signature, alex_pbc) + if verified: + print('Success: Correct signature is verified') + else: + print('Error: Correct signature is not verified') + + # It should be correct because mike is signing it and we verifying using alex public key + f_signature = sign(alex_message, mike_prv) + + verified = verify(alex_message, f_signature, alex_pbc) + if not verified: + print('Success: Wrong signature is not verified') + else: + print('Error: Wrong signature is verified') + +# Check originality of message using public key: + # It should be original because alex is signing it and we verifying using alex public key + correct = verify(b'pay 10 euro to bob', alex_signature, alex_pbc) + if correct: + print('Sucsess: Original message is verified') + else: + print('Error: Original message is not verified') + + + # It should be tempered because alex signature of the message is different + t_message = b'pay 100 euro to bob' + correct = verify(t_message, alex_signature, alex_pbc) + if not correct: + print('Sucsess: Tampered message is detected') + else: + print('Error: Tampered message is not detected') \ No newline at end of file diff --git a/period_1/06-transaction_block/602_T02_A01_PublicKey_Serialization/Transaction.py b/period_1/06-transaction_block/602_T02_A01_PublicKey_Serialization/Transaction.py new file mode 100644 index 0000000..fbc2ed5 --- /dev/null +++ b/period_1/06-transaction_block/602_T02_A01_PublicKey_Serialization/Transaction.py @@ -0,0 +1,66 @@ +from Signature import * + +class Tx: + inputs = None + outputs =None + sigs = None + reqd = None + def __init__(self): + 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): + 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 diff --git a/period_1/06-transaction_block/602_T02_A01_PublicKey_Serialization/output.txt b/period_1/06-transaction_block/602_T02_A01_PublicKey_Serialization/output.txt new file mode 100644 index 0000000..ba4db01 --- /dev/null +++ b/period_1/06-transaction_block/602_T02_A01_PublicKey_Serialization/output.txt @@ -0,0 +1,4 @@ +Success: Correct signature is verified +Success: Wrong signature is not verified +Sucsess: Original message is verified +Sucsess: Tampered message is detected \ No newline at end of file diff --git a/period_1/06-transaction_block/603_T03_A02_Transaction_SaveLoad/Signature.py b/period_1/06-transaction_block/603_T03_A02_Transaction_SaveLoad/Signature.py new file mode 100644 index 0000000..740ee1a --- /dev/null +++ b/period_1/06-transaction_block/603_T03_A02_Transaction_SaveLoad/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/06-transaction_block/603_T03_A02_Transaction_SaveLoad/Transaction.py b/period_1/06-transaction_block/603_T03_A02_Transaction_SaveLoad/Transaction.py new file mode 100644 index 0000000..06f5a13 --- /dev/null +++ b/period_1/06-transaction_block/603_T03_A02_Transaction_SaveLoad/Transaction.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""Transactions -> Ledger (Transaction Save and Load): Tutorial 3 + +The goal of this tutorial is to learn how storing and loading a transaction from and to +a file system works. For this purpose you need to check the provided code in both files, Signatue.py and Transaction_t.py. +then, rebuild the transaction module to satisfy our testing scenario. + +Your task is to: + * locate the TODOs in this file + * complete the missing part from the code + * run the test of this exercise located in same folder. + +To test run 'Transaction_t.py' in your command line + +Notes: + * do not change class structure or method signature to not break unit tests + * Check previous tutorials for more information on this topic: +""" +from Signature import * +class Tx: + inputs = None + outputs =None + sigs = None + reqd = None + def __init__(self): + 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): + 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 diff --git a/period_1/06-transaction_block/603_T03_A02_Transaction_SaveLoad/Transaction_t.py b/period_1/06-transaction_block/603_T03_A02_Transaction_SaveLoad/Transaction_t.py new file mode 100644 index 0000000..a8da714 --- /dev/null +++ b/period_1/06-transaction_block/603_T03_A02_Transaction_SaveLoad/Transaction_t.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +""" +This test case will verify if the provided solution by a student for TxBlock.py is correct. +""" +from Signature import generate_keys + +from Transaction import Tx + +import pickle + +if __name__ == "__main__": + + # Generating asymmetric keys for multiple users + alex_prv, alex_pbc = generate_keys() + mike_prv, mike_pbc = generate_keys() + rose_prv, rose_pbc = generate_keys() + mara_prv, mara_pbc = generate_keys() + + # Creating a transaction from alex to mike and signing it with alex private key + Tx1 = Tx() + Tx1.add_input(alex_pbc, 1) + Tx1.add_output(mike_pbc, 1) + Tx1.sign(alex_prv) + + # Checking the validity of this transaction + if Tx1.is_valid(): + print("Success! Tx is valid") + else: + print("Fails!") + + # Storing transaction data in a file + # The operation should succeed this time + savefile = open("tx.dat", "wb") + pickle.dump(Tx1, savefile) + savefile.close() + + # Loading transaction data from a file + # The operation should succeed this time + loadfile = open("tx.dat", "rb") + newTx = pickle.load(loadfile) + + # Verifying the validity of the transaction + if newTx.is_valid(): + print("Sucess! Loaded tx is valid") + else: + print("Fails!") + loadfile.close() diff --git a/period_1/06-transaction_block/603_T03_A02_Transaction_SaveLoad/output.txt b/period_1/06-transaction_block/603_T03_A02_Transaction_SaveLoad/output.txt new file mode 100644 index 0000000..958e45c --- /dev/null +++ b/period_1/06-transaction_block/603_T03_A02_Transaction_SaveLoad/output.txt @@ -0,0 +1,2 @@ +Success! Tx is valid +Sucess! Loaded tx is valid \ No newline at end of file diff --git a/period_1/06-transaction_block/604_T04_A03_TxBlock_TxValidity/BlockChain.py b/period_1/06-transaction_block/604_T04_A03_TxBlock_TxValidity/BlockChain.py new file mode 100644 index 0000000..9d6f5e9 --- /dev/null +++ b/period_1/06-transaction_block/604_T04_A03_TxBlock_TxValidity/BlockChain.py @@ -0,0 +1,19 @@ +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() diff --git a/period_1/06-transaction_block/604_T04_A03_TxBlock_TxValidity/Signature.py b/period_1/06-transaction_block/604_T04_A03_TxBlock_TxValidity/Signature.py new file mode 100644 index 0000000..740ee1a --- /dev/null +++ b/period_1/06-transaction_block/604_T04_A03_TxBlock_TxValidity/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/06-transaction_block/604_T04_A03_TxBlock_TxValidity/Transaction.py b/period_1/06-transaction_block/604_T04_A03_TxBlock_TxValidity/Transaction.py new file mode 100644 index 0000000..fbc2ed5 --- /dev/null +++ b/period_1/06-transaction_block/604_T04_A03_TxBlock_TxValidity/Transaction.py @@ -0,0 +1,66 @@ +from Signature import * + +class Tx: + inputs = None + outputs =None + sigs = None + reqd = None + def __init__(self): + 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): + 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 diff --git a/period_1/06-transaction_block/604_T04_A03_TxBlock_TxValidity/TxBlock.py b/period_1/06-transaction_block/604_T04_A03_TxBlock_TxValidity/TxBlock.py new file mode 100644 index 0000000..1d9701e --- /dev/null +++ b/period_1/06-transaction_block/604_T04_A03_TxBlock_TxValidity/TxBlock.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +"""Transactions -> Ledger (Transaction and Blocks): Tutorial 4 + +The goal of this tutorial is to learn how a blockchain for the transactions is implemented. +In this scenario the implementation of the block is minimal. Each block contains only his +own hash value, transaction data and the hash value of previous block. Check the provided +code in both files, Signature.py, Transaction.py and Blockchain.py. then, +rebuild the Block module to satisfy our testing scenario. + +Your task is to: + * locate the TODOs in this file + * complete the missing part from the code + * run the test of this exercise located in same folder. + +To test run 'TxBlock_t.py' in your command line + +Notes: + * do not change class structure or method signature to not break unit tests + * Check previous tutorials for more information on this topic +""" +from BlockChain import CBlock +from Signature import generate_keys, sign, verify +from Transaction import Tx + +class TxBlock (CBlock): + previousHash = None + previousBlock = None + data = None + # TODO 1: Initialize the block + # Each block contains a list for the data and a hash value to previous block + def __init__(self, previousBlock): + self.previousBlock = previousBlock + self.data = [] + if previousBlock != None: + self.previousHash = previousBlock.computeHash() + # TODO 1: Initialize the block + # Each block contains a list for the data and a hash value to previous block + + + # TODO 2: Append the transaction to the data list + def addTx(self, Tx_in): + self.data.append(Tx_in) + + # TODO 3: Check the validity of each transaction in the data list + # Expected return value is true or false + def is_valid(self): + for t in self.data: + if t.is_valid() == False: + return False + return True \ No newline at end of file diff --git a/period_1/06-transaction_block/604_T04_A03_TxBlock_TxValidity/TxBlock_t.py b/period_1/06-transaction_block/604_T04_A03_TxBlock_TxValidity/TxBlock_t.py new file mode 100644 index 0000000..a93490e --- /dev/null +++ b/period_1/06-transaction_block/604_T04_A03_TxBlock_TxValidity/TxBlock_t.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +""" +This test case will verify if the provided solution by a student for TxBlock.py is correct. +In this test scenario 6 blocks will be created (1 genesis and 5 child blocks). +A total of 8 transactions will be created. 3 of those transactions are invalid. +Every time we add some transactions to a block and add the block +to a chain, we iterate through the chain and check if those transactions are valid. + +""" +from BlockChain import CBlock +from Signature import generate_keys, sign, verify +from Transaction import Tx +import pickle +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.backends import default_backend + +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 Transactions +#################### + # Create 2 valid transactions Tx1 and Tx2 + Tx1 = Tx() + Tx1.add_input(alex_pbc, 1) + Tx1.add_output(rose_pbc, 1) + Tx1.sign(alex_prv) + + Tx2 = Tx() + Tx2.add_input(mike_pbc,1.1) + Tx2.add_output(rose_pbc, 1) + Tx2.sign(mike_prv) + + # Add Tx1 and Tx2 to the first block (genesis block) + root = TxBlock(None) + root.addTx(Tx1) + root.addTx(Tx2) + + # Create 2 more valid transactions Tx3 and Tx4 + Tx3 = Tx() + Tx3.add_input(rose_pbc,1.1) + Tx3.add_output(alex_pbc, 1) + Tx3.sign(rose_prv) + + Tx4 = Tx() + Tx4.add_input(mike_pbc,1) + Tx4.add_output(mara_pbc, 1) + Tx4.add_reqd(rose_pbc) + Tx4.sign(mike_prv) + Tx4.sign(rose_prv) + + # Add Tx3 and Tx4 to the second block (the child of the genesis block) + B1 = TxBlock(root) + B1.addTx(Tx3) + B1.addTx(Tx4) + + for b in [root, B1]: + if b.is_valid(): + print ("Success! Valid block is verified.") + else: + print ("Error! Valid block is not verified.") + + # Create new valid transaction Tx5 + Tx5 = Tx() + Tx5.add_input(rose_pbc, 2.3) + Tx5.add_output(mike_pbc, 2.3) + Tx5.sign(rose_prv) + # Create a new block (a child of the block B1), and the transaction Tx5 to the block + B2 = TxBlock(B1) + B2.addTx(Tx5) + + # Add another valid transaction to B1 + B1.addTx(Tx4) + + for b in [root, B1, B2]: + if b.is_valid(): + print ("Success! Valid block is verified.") + else: + print ("Error! Valid block is not verified.") + + +# Invalid Transactions +###################### + # Create an invalid transaction Tx6 + Tx6 = Tx() + Tx6.add_input(mara_pbc, 2.0) + Tx6.add_output(rose_pbc, 15.3) + Tx6.sign(mara_prv) + B3 = TxBlock(B2) + B3.addTx(Tx6) + + # Creat an invalid transaction Tx7 + Tx7 = Tx() + Tx7.add_input(rose_pbc, 2.3) + Tx7.add_output(mike_pbc, 2.3) + Tx7.sign(mike_prv) + B4 = TxBlock(B3) + B4.addTx(Tx7) + + # Creat an invalid transaction Tx8 + Tx8 = Tx() + Tx8.add_input(mike_pbc, 0.9) + Tx8.add_output(mara_pbc, 0.8) + Tx8.add_reqd(rose_pbc) + Tx8.sign(mike_prv) + B5 = TxBlock(B4) + B5.addTx(Tx8) + + for b in [B3, B4, B5]: + if b.is_valid(): + print ("Error! Invalid block is verified.") + else: + print ("Success! Invalid blocks is detected.") \ No newline at end of file diff --git a/period_1/06-transaction_block/604_T04_A03_TxBlock_TxValidity/output.txt b/period_1/06-transaction_block/604_T04_A03_TxBlock_TxValidity/output.txt new file mode 100644 index 0000000..8a6be96 --- /dev/null +++ b/period_1/06-transaction_block/604_T04_A03_TxBlock_TxValidity/output.txt @@ -0,0 +1,8 @@ +Success! Valid block is verified. +Success! Valid block is verified. +Success! Valid block is verified. +Success! Valid block is verified. +Success! Valid block is verified. +Success! Invalid blocks is detected. +Success! Invalid blocks is detected. +Success! Invalid blocks is detected. \ No newline at end of file