├── .vscode └── settings.json ├── API_calls_screenshots ├── GET Request.png ├── POST Request.png ├── adding_peer_node.png ├── fetch_balance.png ├── generating_a_wallet.png ├── make_transaction.png ├── make_transaction_to_peer_node.png ├── mining_a_block.png └── peer_node_removed.png ├── Legacy Blockchain Files ├── .vscode │ ├── launch.json │ └── settings.json ├── __pycache__ │ └── hash_util.cpython-36.pyc ├── blockchain.p ├── blockchain.py ├── blockchain.txt └── hash_util.py ├── README.md ├── __pycache__ ├── block.cpython-37.pyc ├── blockchain.cpython-37.pyc ├── transaction.cpython-37.pyc └── wallet.cpython-37.pyc ├── block.py ├── blockchain.py ├── cover.png ├── node.py ├── node_console.py ├── transaction.py ├── utility ├── .DS_Store ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ ├── hash_util.cpython-37.pyc │ ├── printable.cpython-37.pyc │ └── verification.cpython-37.pyc ├── hash_util.py ├── printable.py └── verification.py └── wallet.py /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "C:\\Users\\Aditya\\AppData\\Local\\Continuum\\anaconda3\\python.exe" 3 | } -------------------------------------------------------------------------------- /API_calls_screenshots/GET Request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/GET Request.png -------------------------------------------------------------------------------- /API_calls_screenshots/POST Request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/POST Request.png -------------------------------------------------------------------------------- /API_calls_screenshots/adding_peer_node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/adding_peer_node.png -------------------------------------------------------------------------------- /API_calls_screenshots/fetch_balance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/fetch_balance.png -------------------------------------------------------------------------------- /API_calls_screenshots/generating_a_wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/generating_a_wallet.png -------------------------------------------------------------------------------- /API_calls_screenshots/make_transaction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/make_transaction.png -------------------------------------------------------------------------------- /API_calls_screenshots/make_transaction_to_peer_node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/make_transaction_to_peer_node.png -------------------------------------------------------------------------------- /API_calls_screenshots/mining_a_block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/mining_a_block.png -------------------------------------------------------------------------------- /API_calls_screenshots/peer_node_removed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/peer_node_removed.png -------------------------------------------------------------------------------- /Legacy Blockchain Files/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python: Current File", 9 | "type": "python", 10 | "request": "launch", 11 | "program": "${file}", 12 | "console": "integratedTerminal" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /Legacy Blockchain Files/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true, 3 | "python.pythonPath": "C:\\Users\\Aditya\\AppData\\Local\\Programs\\Python\\Python36-32\\python.exe" 4 | } -------------------------------------------------------------------------------- /Legacy Blockchain Files/__pycache__/hash_util.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/Legacy Blockchain Files/__pycache__/hash_util.cpython-36.pyc -------------------------------------------------------------------------------- /Legacy Blockchain Files/blockchain.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/Legacy Blockchain Files/blockchain.p -------------------------------------------------------------------------------- /Legacy Blockchain Files/blockchain.py: -------------------------------------------------------------------------------- 1 | ##### Phase - 1: Importing all necessary files and Initializing the blockchain ##### 2 | 3 | # Importing 4 | from functools import reduce 5 | import hashlib as hl 6 | from collections import OrderedDict 7 | import json 8 | import pickle 9 | from hash_util import hash_string_256, hash_block 10 | 11 | # The reward we give to miners for creating a new block 12 | mining_reward = 10 13 | 14 | # Initializing our empty blockchain list 15 | blockchain = [] 16 | 17 | # Unhandled or open transactions that are yet to be added to a new block 18 | open_transactions = [] 19 | 20 | # We are the owner of this blockchain node, hence this is our identifier (e.g. for sending coins) 21 | owner = 'Aditya' 22 | 23 | # Registered participants: Us + other people sending/ receiving coins 24 | participants = {'Aditya'} 25 | 26 | 27 | 28 | 29 | ##### Phase - 2: Saving & Loading the Blockchain Data ##### 30 | 31 | # Saving the transactions and other details of our blockchain 32 | def save_data(): 33 | try: 34 | with open('blockchain.txt', mode='w') as f: 35 | f.write(json.dumps(blockchain)) 36 | f.write('\n') 37 | f.write(json.dumps(open_transactions)) 38 | # Uncomment below block of code to save data using pickle and comment out json block of code 39 | # save_data = { 40 | # 'chain': blockchain, 41 | # 'ot': open_transactions 42 | # } 43 | # f.write(pickle.dumps(save_data)) 44 | except IOError: 45 | print('Saving failed!') 46 | 47 | # Loading the transactions and other details of our blockchain 48 | def load_data(): 49 | global blockchain 50 | global open_transactions 51 | try: 52 | with open('blockchain.txt', mode='r') as f: 53 | # Uncomment below block of code to load data from pickle file and comment out json code 54 | # file_content = pickle.loads(f.read()) 55 | # blockchain = file_content['chain'] 56 | # open_transactions = file_content['ot'] 57 | file_content = f.readlines() 58 | blockchain = json.loads(file_content[0][:-1]) 59 | # We need to first convert the data loaded from the file because Transactions should use OrderedDict 60 | updated_blockchain = [] 61 | for block in blockchain: 62 | updated_block = { 63 | 'previous_hash': block['previous_hash'], 64 | 'index': block['index'], 65 | 'proof': block['proof'], 66 | 'transactions': [OrderedDict( 67 | [('sender', tx['sender']), ('recipient', tx['recipient']), ('amount', tx['amount'])]) for tx in block['transactions']] 68 | } 69 | updated_blockchain.append(updated_block) 70 | blockchain = updated_blockchain 71 | open_transactions = json.loads(file_content[1]) 72 | # We need to convert the loaded data because Transactions should use OrderedDict 73 | updated_transactions = [] 74 | for tx in open_transactions: 75 | updated_transaction = OrderedDict( 76 | [('sender', tx['sender']), ('recipient', tx['recipient']), ('amount', tx['amount'])]) 77 | updated_transactions.append(updated_transaction) 78 | open_transactions = updated_transactions 79 | except IOError: 80 | # Our starting block for the blockchain 81 | genesis_block = { 82 | 'previous_hash': '', 83 | 'index': 0, 84 | 'transactions': [], 85 | 'proof': 100 86 | } 87 | # Initializing our (empty) blockchain list 88 | blockchain = [genesis_block] 89 | # Unhandled or Unprocessed transactions that are yet to be a part of a block 90 | open_transactions = [] 91 | finally: 92 | print('Cleaning Up!') 93 | 94 | # Load the data from the file 95 | load_data() 96 | 97 | 98 | 99 | 100 | ##### Phase - 3: Verifying & Authenticating the Blockchain ##### 101 | 102 | # Proof of Work 103 | def valid_proof(transactions, last_hash, proof): 104 | guess = (str(transactions) + str(last_hash) + str(proof)).encode() 105 | # This hashing is used for Proof of work Algo 106 | guess_hash = hash_string_256(guess) 107 | # Only a hash (which is based on the above inputs) which starts with two 0s is treated as valid 108 | return guess_hash[0:2] == '00' 109 | 110 | def proof_of_work(): 111 | """Generate a proof of work for the open transactions, the hash of the previous block and a random number (which is guessed until it fits).""" 112 | last_block = blockchain[-1] 113 | last_hash = hash_block(last_block) 114 | proof = 0 115 | # Try different PoW numbers and return the first valid one 116 | while not valid_proof(open_transactions, last_hash, proof): 117 | proof += 1 118 | return proof 119 | 120 | # Calculate and return the balance of a participant 121 | def get_balance(participant): 122 | # Fetch a list of all sent coin amounts for the given person (empty lists are returned if the person was NOT the sender) 123 | # This fetches sent amounts of transactions that were already included in blocks of the blockchain 124 | tx_sender = [[tx['amount'] for tx in block['transactions'] 125 | if tx['sender'] == participant] for block in blockchain] 126 | 127 | # Fetch a list of all sent coin amounts for the given person (empty lists are returned if the person was NOT the sender) 128 | # This fetches sent amounts of open transactions (to avoid double spending) 129 | open_tx_sender = [tx['amount'] 130 | for tx in open_transactions if tx['sender'] == participant] 131 | tx_sender.append(open_tx_sender) 132 | print(tx_sender) 133 | amount_sent = reduce(lambda tx_sum, tx_amt: tx_sum + sum(tx_amt) 134 | if len(tx_amt) > 0 else tx_sum + 0, tx_sender, 0) 135 | 136 | # This was the previous logic used for calculating the amount of coins sent 137 | """ 138 | amount_sent = 0 139 | # Calculate the total amount of coins sent 140 | for tx in tx_sender: 141 | if len(tx) > 0: 142 | amount_sent += tx[0] 143 | """ 144 | 145 | # This fetches received coin amounts of transactions that were already included in blocks of the blockchain 146 | # We ignore open transactions here because you shouldn't be able to spend coins before the transaction was confirmed + included in a block 147 | tx_recipient = [[tx['amount'] for tx in block['transactions'] 148 | if tx['recipient'] == participant] for block in blockchain] 149 | amount_received = reduce(lambda tx_sum, tx_amt: tx_sum + sum(tx_amt) 150 | if len(tx_amt) > 0 else tx_sum + 0, tx_recipient, 0) 151 | 152 | # Return the total balance 153 | return amount_received - amount_sent 154 | 155 | # Returns the last value of the current blockchain 156 | def get_last_blockchain_value(): 157 | if len(blockchain) < 1: 158 | return None 159 | return blockchain[-1] 160 | 161 | # Verifies a transaction by checking whether the sender has sufficient amount of coins 162 | def verify_transaction(transaction): 163 | sender_balance = get_balance(transaction['sender']) 164 | return sender_balance >= transaction['amount'] 165 | 166 | # Creating a Chain of Data( Append a new value as well as the last blockchain value to the blockchain ) 167 | def add_transaction(recipient, sender=owner, amount=1.0): 168 | # transaction = { 169 | # 'sender': sender, 170 | # 'recipient': recipient, 171 | # 'amount': amount 172 | # } 173 | transaction = OrderedDict( 174 | [('sender', sender), ('recipient', recipient), ('amount', amount)]) 175 | if verify_transaction(transaction): 176 | open_transactions.append(transaction) 177 | participants.add(sender) 178 | participants.add(recipient) 179 | save_data() 180 | return True 181 | return False 182 | 183 | 184 | 185 | 186 | ##### Phase - 4: Mining a new block in the Blockchain ##### 187 | 188 | # Mine a new block in the Blockchain ( Create a new block and add open transactions to it ) 189 | def mine_block(): 190 | # Fetch the current last block of the blockchain 191 | last_block = blockchain[-1] 192 | # Hash the last block. So, we can compare it to the stored hash value 193 | hashed_block = hash_block(last_block) 194 | proof = proof_of_work() 195 | # For rewarding miners, Mining Reward was created 196 | # reward_transaction = { 197 | # 'sender': 'mining_system', 198 | # 'recipient': owner, 199 | # 'amount': mining_reward 200 | # } 201 | reward_transaction = OrderedDict( 202 | [('sender', 'mining_system'), ('recipient', owner), ('amount', mining_reward)]) 203 | # Copy transaction instead of manipulating the original open_transactions list 204 | # This ensures that if for some reason the mining should fail, we don't have the reward transaction stored in the open transactions 205 | copied_transactions = open_transactions[:] 206 | copied_transactions.append(reward_transaction) 207 | block = { 208 | 'previous_hash': hashed_block, 209 | 'index': len(blockchain), 210 | 'transactions': copied_transactions, 211 | 'proof': proof 212 | } 213 | blockchain.append(block) 214 | return True 215 | 216 | # Get the user input, transform it from a string to a float and store it in user_input 217 | def get_transaction_value(): 218 | tx_recipient = input('Enter the recipient of the transaction: ') 219 | tx_amount = float(input('Your transaction amount please: ')) 220 | return tx_recipient, tx_amount 221 | 222 | # Prompts the user for its choice and return it 223 | def get_user_choice(): 224 | user_input = input('Your choice: ') 225 | return user_input 226 | 227 | # Output the blockchain list to the console 228 | def print_blockchain_elements(): 229 | for block in blockchain: 230 | print('Outputting Block') 231 | print(block) 232 | else: 233 | print('-' * 20) 234 | 235 | # Analyze and Verify the Blockchain 236 | # We skip checking the first block as there's no previous block with which it can be compared 237 | # If first element of current block is equal to the previous entire block, chain is valid 238 | def verify_chain(): 239 | # Basic Implementation 240 | """ is_valid = True 241 | for block_index in range(len(blockchain)): 242 | if block_index == 0: 243 | continue 244 | elif blockchain[block_index][0] == blockchain[block_index - 1]: 245 | is_valid = True 246 | else: 247 | is_valid = False 248 | return is_valid """ 249 | 250 | # Improved Implementation 251 | """ Verify the current blockchain and return True if it's valid, False otherwise.""" 252 | for (index, block) in enumerate(blockchain): 253 | if index == 0: 254 | continue 255 | if block['previous_hash'] != hash_block(blockchain[index - 1]): 256 | return False 257 | if not valid_proof(block['transactions'][:-1], block['previous_hash'], block['proof']): 258 | print('Proof of work is invalid') 259 | return False 260 | return True 261 | 262 | # Verifies all open & unprocessed transactions 263 | def verify_transactions(): 264 | return all([verify_transaction(tx) for tx in open_transactions]) 265 | 266 | 267 | 268 | 269 | ##### Phase - 5: User Interface ##### 270 | 271 | waiting_for_input = True 272 | 273 | # User Input Interface 274 | while waiting_for_input: 275 | print('Please choose') 276 | print('1: Add a new transaction value') 277 | print('2: Mine a new block') 278 | print('3: Output the blockchain blocks') 279 | print('4: Output participants') 280 | print('5: Check transaction validity') 281 | print('h: Manipulate the chain') 282 | print('q: Quit') 283 | user_choice = get_user_choice() 284 | if user_choice == '1': 285 | tx_data = get_transaction_value() 286 | recipient, amount = tx_data 287 | # Add the transaction amount to the blockchain 288 | if add_transaction(recipient, amount=amount): 289 | print('Added transaction!') 290 | else: 291 | print('Transaction failed!') 292 | print(open_transactions) 293 | elif user_choice == '2': 294 | if mine_block(): 295 | open_transactions = [] 296 | save_data() 297 | elif user_choice == '3': 298 | print_blockchain_elements() 299 | elif user_choice == '4': 300 | print(participants) 301 | elif user_choice == '5': 302 | if verify_transactions(): 303 | print('All transactions are valid') 304 | else: 305 | print('There are invalid transactions') 306 | elif user_choice == 'h': 307 | # Make sure that you don't try to manipulate the blockchain if it's empty 308 | if len(blockchain) >= 1: 309 | blockchain[0] = { 310 | 'previous_hash': '', 311 | 'index': 0, 312 | 'transactions': [{'sender': 'Chris', 'recipient': 'Aditya', 'amount': 100.0}] 313 | } 314 | elif user_choice == 'q': 315 | # This will lead to the loop to exist because it's running condition becomes False 316 | waiting_for_input = False 317 | else: 318 | print('Input was invalid, please pick a value from the list!') 319 | if not verify_chain(): 320 | print_blockchain_elements() 321 | print('Invalid blockchain!') 322 | # Break out of the loop 323 | break 324 | print('Balance of {}: {:6.2f}'.format('Aditya', get_balance('Aditya'))) 325 | else: 326 | print('User left!') 327 | 328 | 329 | print('Done!') 330 | -------------------------------------------------------------------------------- /Legacy Blockchain Files/blockchain.txt: -------------------------------------------------------------------------------- 1 | [{"previous_hash": "", "index": 0, "proof": 100, "transactions": []}, {"previous_hash": "68da5267793e1fc7bd98d9cc01cf91e38daa67cbbd7b646bcd99b4dc3df93eb1", "index": 1, "proof": 119, "transactions": [{"sender": "MINING", "recipient": "Aditya", "amount": 10}]}, {"previous_hash": "8c097d52167ae2a038ff8c1b12d079fcd54f739ab945178a1dd3d1dc1c02d96b", "index": 2, "proof": 18, "transactions": [{"sender": "MINING", "recipient": "Aditya", "amount": 10}]}, {"previous_hash": "a69cb435f1b58c193d5eed3fdf9a769b1d685e49f5b33f2aa7c18cd1f3a78d56", "index": 3, "proof": 66, "transactions": [{"sender": "Aditya", "recipient": "Sam", "amount": 5.0}, {"sender": "Aditya", "recipient": "Rob", "amount": 9.5}, {"sender": "Aditya", "recipient": "Jane", "amount": 2.0}, {"sender": "mining_system", "recipient": "Aditya", "amount": 10}]}, {"previous_hash": "25933fa334929513ace937d1509c2c2c9c1f88992702a527785e69e292693122", "index": 4, "transactions": [{"sender": "Aditya", "recipient": "Robert", "amount": 3.5}, {"sender": "mining_system", "recipient": "Aditya", "amount": 10}], "proof": 0}] 2 | [] -------------------------------------------------------------------------------- /Legacy Blockchain Files/hash_util.py: -------------------------------------------------------------------------------- 1 | import hashlib as hl 2 | import json 3 | 4 | # Create a SHA256 hash for a given input string 5 | def hash_string_256(string): 6 | return hl.sha256(string).hexdigest() 7 | 8 | # Hashes a block and returns a string representation of it 9 | def hash_block(block): 10 | return hash_string_256(json.dumps(block, sort_keys=True).encode()) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blockchain Project 2 | 3 | This project is an implementation of blockchain and it's concepts in Python & Flask with Postman used for API development 4 | 5 | ## Getting Started 6 | 7 | 1. [Download Anaconda](https://www.anaconda.com/distribution/) 8 | 2. [Download Visual Studio Code](https://code.visualstudio.com/) 9 | 3. [Download Postman](https://www.getpostman.com/) 10 | 4. Clone the directory and open it in **vs code** 11 | 5. In terminal, enter **activate** to activate the **base** environment of Anaconda 12 | > *Install the below dependencies to avoid any errors incase they're not already installed* 13 | 6. Install Flask 14 | 15 | `pip install flask` 16 | 17 | 7. Install Flask-Cors 18 | 19 | `pip install -U flask-cors` 20 | 21 | 8. Install requests Lib 22 | 23 | `pip install requests` 24 | 25 | 9. Install pycrypto (collection of hash functions) 26 | 27 | `pip install pycrypto` 28 | 29 | 10. In the terminal, run the following command 30 | 31 | `python node.py` 32 | 33 | > *or to run multiple nodes, in a new terminal* 34 | 35 | `python node.py -p` 36 | 37 | 11. Open Postman Client, Test the below API Calls 38 | 39 | ## API Calls 40 | Below is a list of API calls and short description about what they do, these api calls can conveniently be executed in Postman 41 | Localhost & Port have been set in node.py file, by default I've set them as 0.0.0.0 with port=5000 42 | 43 | ### Blockchain 44 | Creates a blockchain.txt file that contains information regarding the transactions and other information related to the blockchain 45 | 46 | * [POST] Mine a Block: 47 | 48 | `localhost:5000/mine` 49 | 50 | * [GET] Get snapshot of current blockchain: 51 | 52 | `localhost:5000/chain` 53 | 54 | ### Transaction 55 | 56 | * [POST] Add a New Transaction: 57 | 58 | `localhost:5000/transaction` 59 | 60 | Before executing the above api call, in Postman, 61 | Go to "Body" > "Raw" > Select "Json" in the current window and enter the following data: 62 | 63 | `eg. { "recipient": "NameofRecipient", "amount": 5 }` 64 | 65 | * [GET] Get a List of all Open Transactions: 66 | 67 | `localhost:5000/transactions` 68 | 69 | ### Wallet 70 | creates a wallet.txt file with your port number that contains information regarding your wallet such as public key, private key 71 | 72 | * [POST] Generate Wallet: 73 | 74 | `localhost:5000/wallet` 75 | 76 | * [GET] Fetch Wallet: 77 | 78 | `localhost:5000/wallet` 79 | 80 | * [GET] Fetch Wallet Balance: 81 | 82 | `localhost:5000/balance` 83 | 84 | ### Node/Server 85 | You can run multiple nodes and test communication between the two by opening two terminals inside **vs code** and executing the **python node.py** file with arguments **-p 5001** or **--port 5001**. So, it will look like **python node.py -port 5001** 86 | 87 | You can create as many nodes/servers you want by using the open ports and appending them to the **python node.py --port ** command 88 | 89 | * [POST] Add Peer Node to Your Chain Network: 90 | 91 | `localhost:5000/node` 92 | 93 | Before executing the above api call, in Postman, 94 | Go to "Body" > "Raw" > Select "Json" in the current window and enter the following data: 95 | 96 | `e.g. { "node": "localhost:5001" }` 97 | 98 | * [DELETE] Delete Peer Node: 99 | 100 | `e.g. localhost:5000/node/localhost:5001` 101 | 102 | * [GET] Get all the Nodes in your Network: 103 | 104 | `localhost:5000/nodes` 105 | 106 | ## File Contents 107 | * block.py - represents single block of our blockchain 108 | * blockchain.py - represents the blockchain 109 | * node.py - contains all the routes for API calls 110 | * node_console.py - provides a user interface using while loop to interact with blockchain from within the vs code terminal 111 | * transaction.py - represents open transactions 112 | * wallet.py - Implements functionality to generate wallet, public & private key and other security related algorithm 113 | * API_calls_screenshot Folder - contains screenshots of successful API calls and their output 114 | * Utility > hash_util.py - Implements functionality to hash a block 115 | * Utility > printable.py - Converts binary data to string for printing in console 116 | * Utility > verification.py - Implements proof of work and verification of chain and transactions 117 | 118 | * Legacy_blockchain_files - contains old files related to project 119 | 120 | ## Shoutout 121 | Huge credits to Dapp university & howCode for explaining the concepts and how to implement them in python. 122 | 1. [Dapp University Video Link](https://www.youtube.com/watch?v=pZSegEXtgAE) 123 | 2. [howCode Video Link](https://www.youtube.com/watch?v=b81Ib_oYbFk) 124 | -------------------------------------------------------------------------------- /__pycache__/block.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/__pycache__/block.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/blockchain.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/__pycache__/blockchain.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/transaction.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/__pycache__/transaction.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/wallet.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/__pycache__/wallet.cpython-37.pyc -------------------------------------------------------------------------------- /block.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | from utility.printable import Printable 4 | 5 | # Block class represents a single block of our blockchain 6 | # index: The index of this block 7 | # previous_hash: The hash of the previous block in the blockchain 8 | # timestamp: The timestamp of the block (automatically generated by default) 9 | # transactions: A list of transaction which are included in the block 10 | # proof: The proof of work number that yielded this block 11 | class Block(Printable): 12 | def __init__(self, index, previous_hash, transactions, proof, time=time()): 13 | self.index = index 14 | self.previous_hash = previous_hash 15 | self.timestamp = time 16 | self.transactions = transactions 17 | self.proof = proof 18 | 19 | 20 | -------------------------------------------------------------------------------- /blockchain.py: -------------------------------------------------------------------------------- 1 | # Importing 2 | from functools import reduce 3 | import hashlib as hl 4 | 5 | import json 6 | import pickle 7 | import requests 8 | 9 | from utility.hash_util import hash_block 10 | from utility.verification import Verification 11 | from block import Block 12 | from transaction import Transaction 13 | from wallet import Wallet 14 | 15 | # The reward we give to miners for creating a new block 16 | MINING_REWARD = 10 17 | 18 | #__name__ is basically used here to identify whether the file is executed as main program or 19 | # if it is being imported from another module/file and then being executed 20 | print(__name__) 21 | 22 | # This class manages the chain of blocks, open transactions and the node on which it's running 23 | # chain: The list of blocks 24 | # open_transactions (private): The list of open transactions 25 | # hosting_node: The connected node (which runs the blockchain) 26 | class Blockchain: 27 | def __init__(self, public_key, node_id): 28 | # Genesis block is the very first block in our blockchain 29 | genesis_block = Block(0, '', [], 100, 0) 30 | # Initializing our blockchain list 31 | self.chain = [genesis_block] 32 | # Unhandled or Open transactions which are yet to be included in a block 33 | self.__open_transactions = [] 34 | self.public_key = public_key 35 | self.__peer_nodes = set() 36 | self.node_id = node_id 37 | self.load_data() 38 | 39 | # This turns the chain attribute into a property with a getter (the method below) and a setter (@chain.setter) 40 | # chain[:] returns a copy so we only get a copy of the reference of the objects, so we can't directly change the value 41 | @property 42 | def chain(self): 43 | return self.__chain[:] 44 | 45 | # The setter for the chain property 46 | @chain.setter 47 | def chain(self, val): 48 | self.__chain = val 49 | 50 | # Returns a copy of the open transactions list that are yet to be mined 51 | def get_open_transactions(self): 52 | return self.__open_transactions[:] 53 | 54 | # Initialize blockchain + open transactions data from a file 55 | def load_data(self): 56 | try: 57 | with open('blockchain-{}.txt'.format(self.node_id), mode='r') as f: 58 | # file_content = pickle.loads(f.read()) 59 | file_content = f.readlines() 60 | # blockchain = file_content['chain'] 61 | # open_transactions = file_content['ot'] 62 | blockchain = json.loads(file_content[0][:-1]) 63 | # We need to convert the loaded data because Transactions should use OrderedDict 64 | updated_blockchain = [] 65 | for block in blockchain: 66 | converted_tx = [Transaction( 67 | tx['sender'], tx['recipient'], tx['signature'], tx['amount']) for tx in block['transactions']] 68 | updated_block = Block( 69 | block['index'], block['previous_hash'], converted_tx, block['proof'], block['timestamp']) 70 | updated_blockchain.append(updated_block) 71 | self.chain = updated_blockchain 72 | open_transactions = json.loads(file_content[1][:-1]) 73 | # We need to convert the loaded data because Transactions should use OrderedDict 74 | updated_transactions = [] 75 | for tx in open_transactions: 76 | updated_transaction = Transaction( 77 | tx['sender'], tx['recipient'], tx['signature'], tx['amount']) 78 | updated_transactions.append(updated_transaction) 79 | self.__open_transactions = updated_transactions 80 | peer_nodes = json.loads(file_content[2]) 81 | self.__peer_nodes = set(peer_nodes) 82 | except (IOError, IndexError): 83 | pass 84 | finally: 85 | print('Cleanup!') 86 | 87 | # Save blockchain + open transactions to a file 88 | def save_data(self): 89 | try: 90 | with open('blockchain-{}.txt'.format(self.node_id), mode='w') as f: 91 | saveable_chain = [block.__dict__ for block in [Block(block_el.index, block_el.previous_hash, [ 92 | tx.__dict__ for tx in block_el.transactions], block_el.proof, block_el.timestamp) for block_el in self.__chain]] 93 | f.write(json.dumps(saveable_chain)) 94 | f.write('\n') 95 | saveable_tx = [tx.__dict__ for tx in self.__open_transactions] 96 | f.write(json.dumps(saveable_tx)) 97 | f.write('\n') 98 | f.write(json.dumps(list(self.__peer_nodes))) 99 | # save_data = { 100 | # 'chain': blockchain, 101 | # 'ot': open_transactions 102 | # } 103 | # f.write(pickle.dumps(save_data)) 104 | except IOError: 105 | print('Saving failed!') 106 | 107 | # Generate a proof of work for open transactions, hash of previous block and a random number(Which is guessed until it fits) 108 | def proof_of_work(self): 109 | last_block = self.__chain[-1] 110 | last_hash = hash_block(last_block) 111 | proof = 0 112 | # Try different PoW numbers and return the first valid one 113 | while not Verification.valid_proof(self.__open_transactions, last_hash, proof): 114 | proof += 1 115 | return proof 116 | 117 | # Calculate and return the balance of the user 118 | def get_balance(self, sender=None): 119 | if sender == None: 120 | if self.public_key == None: 121 | return None 122 | participant = self.public_key 123 | else: 124 | participant = sender 125 | # Fetch a list of all sent coin amounts for the given person (empty lists are returned if the person was NOT the sender) 126 | # This fetches sent amounts of transactions that were already included in blocks of the blockchain 127 | tx_sender = [[tx.amount for tx in block.transactions 128 | if tx.sender == participant] for block in self.__chain] 129 | # Fetch a list of all sent coin amounts for the given person (empty lists are returned if the person was NOT the sender) 130 | # This fetches sent amounts of open transactions (to avoid double spending) 131 | open_tx_sender = [tx.amount 132 | for tx in self.__open_transactions if tx.sender == participant] 133 | tx_sender.append(open_tx_sender) 134 | print(tx_sender) 135 | amount_sent = reduce(lambda tx_sum, tx_amt: tx_sum + sum(tx_amt) 136 | if len(tx_amt) > 0 else tx_sum + 0, tx_sender, 0) 137 | # This fetches received coin amounts of transactions that were already included in blocks of the blockchain 138 | # We ignore open transactions here because you shouldn't be able to spend coins before the transaction was confirmed + included in a block 139 | tx_recipient = [[tx.amount for tx in block.transactions 140 | if tx.recipient == participant] for block in self.__chain] 141 | amount_received = reduce(lambda tx_sum, tx_amt: tx_sum + sum(tx_amt) 142 | if len(tx_amt) > 0 else tx_sum + 0, tx_recipient, 0) 143 | # Return the total balance 144 | return amount_received - amount_sent 145 | 146 | # Returns the last value of the current blockchain 147 | def get_last_blockchain_value(self): 148 | if len(self.__chain) < 1: 149 | return None 150 | return self.__chain[-1] 151 | 152 | # Creating a Chain of Data( Append a new value as well as the last blockchain value to the blockchain ) 153 | def add_transaction(self, recipient, sender, signature, amount=1.0, is_receiving=False): 154 | # transaction = { 155 | # 'sender': sender, 156 | # 'recipient': recipient, 157 | # 'amount': amount 158 | # } 159 | # if self.public_key == None: 160 | # return False 161 | transaction = Transaction(sender, recipient, signature, amount) 162 | if Verification.verify_transaction(transaction, self.get_balance): 163 | self.__open_transactions.append(transaction) 164 | self.save_data() 165 | if not is_receiving: 166 | for node in self.__peer_nodes: 167 | url = 'http://{}/broadcast-transaction'.format(node) 168 | try: 169 | response = requests.post(url, json={ 170 | 'sender': sender, 'recipient': recipient, 'amount': amount, 'signature': signature}) 171 | if response.status_code == 400 or response.status_code == 500: 172 | print('Transaction declined, needs resolving') 173 | return False 174 | except requests.exceptions.ConnectionError: 175 | continue 176 | return True 177 | return False 178 | 179 | # Mine a new block in the Blockchain ( Create a new block and add open transactions to it ) 180 | def mine_block(self): 181 | if self.public_key == None: 182 | return None 183 | last_block = self.__chain[-1] 184 | # Hash the last block. So, we can compare it to the stored hash value 185 | hashed_block = hash_block(last_block) 186 | proof = self.proof_of_work() 187 | # reward_transaction is created because miners should be added for mining a new block 188 | reward_transaction = Transaction( 189 | 'MINING', self.public_key, '', MINING_REWARD) 190 | # Copy transaction instead of manipulating the original open_transactions list 191 | # This ensures that if for some reason the mining should fail, we don't have the reward transaction stored in the open transactions 192 | copied_transactions = self.__open_transactions[:] 193 | for tx in copied_transactions: 194 | if not Wallet.verify_transaction(tx): 195 | return None 196 | copied_transactions.append(reward_transaction) 197 | block = Block(len(self.__chain), hashed_block, 198 | copied_transactions, proof) 199 | self.__chain.append(block) 200 | self.__open_transactions = [] 201 | self.save_data() 202 | for node in self.__peer_nodes: 203 | url = 'http://{}/broadcast-block'.format(node) 204 | converted_block = block.__dict__.copy() 205 | converted_block['transactions'] = [ 206 | tx.__dict__ for tx in converted_block['transactions']] 207 | try: 208 | response = requests.post(url, json={'block': converted_block}) 209 | if response.status_code == 400 or response.status_code == 500: 210 | print('Block declined, needs resolving') 211 | except requests.exceptions.ConnectionError: 212 | continue 213 | return block 214 | 215 | def add_block(self, block): 216 | transactions = [Transaction( 217 | tx['sender'], tx['recipient'], tx['signature'], tx['amount']) for tx in block['transactions']] 218 | proof_is_valid = Verification.valid_proof( 219 | transactions[:-1], block['previous_hash'], block['proof']) 220 | hashes_match = hash_block(self.chain[-1]) == block['previous_hash'] 221 | if not proof_is_valid or not hashes_match: 222 | return False 223 | converted_block = Block( 224 | block['index'], block['previous_hash'], transactions, block['proof'], block['timestamp']) 225 | self.__chain.append(converted_block) 226 | stored_transactions = self.__open_transactions[:] 227 | for itx in block['transactions']: 228 | for opentx in stored_transactions: 229 | if opentx.sender == itx['sender'] and opentx.recipient == itx['recipient'] and opentx.amount == itx['amount'] and opentx.signature == itx['signature']: 230 | try: 231 | self.__open_transactions.remove(opentx) 232 | except ValueError: 233 | print('Item was already removed') 234 | self.save_data() 235 | return True 236 | 237 | # add a new node to the network of peer nodes 238 | # node: The node URL which should be added 239 | def add_peer_node(self, node): 240 | self.__peer_nodes.add(node) 241 | self.save_data() 242 | 243 | # remove a new node to the network of peer nodes 244 | # node: The node URL which should be removed 245 | def remove_peer_node(self, node): 246 | self.__peer_nodes.discard(node) 247 | self.save_data() 248 | 249 | def get_peer_nodes(self): 250 | return list(self.__peer_nodes) 251 | -------------------------------------------------------------------------------- /cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/cover.png -------------------------------------------------------------------------------- /node.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify, request, send_from_directory 2 | from flask_cors import CORS 3 | 4 | from wallet import Wallet 5 | from blockchain import Blockchain 6 | 7 | app = Flask(__name__) 8 | CORS(app) 9 | 10 | 11 | # POST - Wallet 12 | @app.route('/wallet', methods=['POST']) 13 | def create_keys(): 14 | wallet.create_keys() 15 | if wallet.save_keys(): 16 | # We reinitialise blockchain since we're using a POST request and also 17 | # instruct function to use global blockchain variable 18 | global blockchain 19 | blockchain = Blockchain(wallet.public_key, port) 20 | response = { 21 | 'public_key': wallet.public_key, 22 | 'private_key': wallet.private_key, 23 | 'funds': blockchain.get_balance() 24 | } 25 | return jsonify(response), 201 26 | else: 27 | response = { 28 | 'message': 'Saving keys failed.' 29 | } 30 | return jsonify(response), 500 31 | 32 | 33 | # GET - Wallet 34 | @app.route('/wallet', methods=['GET']) 35 | def load_keys(): 36 | if wallet.load_keys(): 37 | global blockchain 38 | blockchain = Blockchain(wallet.public_key, port) 39 | response = { 40 | 'public_key': wallet.public_key, 41 | 'private_key': wallet.private_key, 42 | 'funds': blockchain.get_balance() 43 | } 44 | return jsonify(response), 201 45 | else: 46 | response = { 47 | 'message': 'Loading the keys failed.' 48 | } 49 | return jsonify(response), 500 50 | 51 | 52 | # GET - Balance 53 | @app.route('/balance', methods=['GET']) 54 | def get_balance(): 55 | balance = blockchain.get_balance() 56 | if balance != None: 57 | response = { 58 | 'message': 'Fetched balance successfully.', 59 | 'funds': balance 60 | } 61 | return jsonify(response), 200 62 | else: 63 | response = { 64 | 'messsage': 'Loading balance failed.', 65 | 'wallet_set_up': wallet.public_key != None 66 | } 67 | return jsonify(response), 500 68 | 69 | 70 | # POST - Broadcast Transaction Information to Peer Nodes 71 | @app.route('/broadcast-transaction', methods=['POST']) 72 | def broadcast_transaction(): 73 | values = request.get_json() 74 | if not values: 75 | response = {'message': 'No data found.'} 76 | return jsonify(response), 400 77 | required = ['sender', 'recipient', 'amount', 'signature'] 78 | if not all(key in values for key in required): 79 | response = {'message': 'Some data is missing.'} 80 | return jsonify(response), 400 81 | success = blockchain.add_transaction( 82 | values['recipient'], values['sender'], values['signature'], values['amount'], is_receiving=True) 83 | if success: 84 | response = { 85 | 'message': 'Successfully added transaction.', 86 | 'transaction': { 87 | 'sender': values['sender'], 88 | 'recipient': values['recipient'], 89 | 'amount': values['amount'], 90 | 'signature': values['signature'] 91 | } 92 | } 93 | return jsonify(response), 201 94 | else: 95 | response = { 96 | 'message': 'Creating a transaction failed.' 97 | } 98 | return jsonify(response), 500 99 | 100 | 101 | # POST - Broadcast Mined Block Information to Peer Nodes 102 | @app.route('/broadcast-block', methods=['POST']) 103 | def broadcast_block(): 104 | values = request.get_json() 105 | if not values: 106 | response = {'message': 'No data found.'} 107 | return jsonify(response), 400 108 | if 'block' not in values: 109 | response = {'message': 'Some data is missing.'} 110 | return jsonify(response), 400 111 | block = values['block'] 112 | if block['index'] == blockchain.chain[-1].index + 1: 113 | if blockchain.add_block(block): 114 | response = {'message': 'Block added'} 115 | return jsonify(response), 201 116 | else: 117 | response = {'message': 'Block seems invalid.'} 118 | return jsonify(response), 500 119 | elif block['index'] > blockchain.chain[-1].index: 120 | pass 121 | else: 122 | response = {'message': 'Blockchain seems to be shorter, block not added'} 123 | return jsonify(response), 409 124 | 125 | # POST - Make a transaction 126 | @app.route('/transaction', methods=['POST']) 127 | def add_transaction(): 128 | if wallet.public_key == None: 129 | response = { 130 | 'message': 'No wallet set up.' 131 | } 132 | return jsonify(response), 400 133 | values = request.get_json() 134 | if not values: 135 | response = { 136 | 'message': 'No data found.' 137 | } 138 | return jsonify(response), 400 139 | required_fields = ['recipient', 'amount'] 140 | if not all(field in values for field in required_fields): 141 | response = { 142 | 'message': 'Required data is missing.' 143 | } 144 | return jsonify(response), 400 145 | recipient = values['recipient'] 146 | amount = values['amount'] 147 | signature = wallet.sign_transaction(wallet.public_key, recipient, amount) 148 | success = blockchain.add_transaction( 149 | recipient, wallet.public_key, signature, amount) 150 | if success: 151 | response = { 152 | 'message': 'Successfully added transaction.', 153 | 'transaction': { 154 | 'sender': wallet.public_key, 155 | 'recipient': recipient, 156 | 'amount': amount, 157 | 'signature': signature 158 | }, 159 | 'funds': blockchain.get_balance() 160 | } 161 | return jsonify(response), 201 162 | else: 163 | response = { 164 | 'message': 'Creating a transaction failed.' 165 | } 166 | return jsonify(response), 500 167 | 168 | 169 | # POST - Mine a block (add a new block to the blockchain) 170 | @app.route('/mine', methods=['POST']) 171 | def mine(): 172 | block = blockchain.mine_block() 173 | if block != None: 174 | dict_block = block.__dict__.copy() 175 | dict_block['transactions'] = [ 176 | tx.__dict__ for tx in dict_block['transactions']] 177 | response = { 178 | 'message': 'Block added successfully.', 179 | 'block': dict_block, 180 | 'funds': blockchain.get_balance() 181 | } 182 | return jsonify(response), 201 183 | else: 184 | response = { 185 | 'message': 'Adding a block failed.', 186 | 'wallet_set_up': wallet.public_key != None 187 | } 188 | return jsonify(response), 500 189 | 190 | 191 | # GET - Get History of Transactions 192 | @app.route('/transactions', methods=['GET']) 193 | def get_open_transaction(): 194 | transactions = blockchain.get_open_transactions() 195 | dict_transactions = [tx.__dict__ for tx in transactions] 196 | return jsonify(dict_transactions), 200 197 | 198 | # GET - Get a Snapshot of the Current chain 199 | # Json is used in below route as we want to return some data, not render a webpage 200 | @app.route('/chain', methods=['GET']) 201 | def get_chain(): 202 | chain_snapshot = blockchain.chain 203 | # we use .copy() to prevent side effects of manipulating of the block for future requests 204 | dict_chain = [block.__dict__.copy() for block in chain_snapshot] 205 | for dict_block in dict_chain: 206 | dict_block['transactions'] = [ 207 | tx.__dict__ for tx in dict_block['transactions']] 208 | return jsonify(dict_chain), 200 209 | 210 | 211 | # POST - Add a Peer Node to the Network 212 | @app.route('/node', methods=['POST']) 213 | def add_node(): 214 | values = request.get_json() 215 | if not values: 216 | response = { 217 | 'message': 'No data attached.' 218 | } 219 | return jsonify(response), 400 220 | if 'node' not in values: 221 | response = { 222 | 'message': 'No node data found.' 223 | } 224 | return jsonify(response), 400 225 | node = values['node'] 226 | blockchain.add_peer_node(node) 227 | response = { 228 | 'message': 'Node added successfully.', 229 | 'all_nodes': blockchain.get_peer_nodes() 230 | } 231 | return jsonify(response), 201 232 | 233 | 234 | # DELETE - Delete a Peer Node 235 | @app.route('/node/', methods=['DELETE']) 236 | def remove_node(node_url): 237 | if node_url == '' or node_url == None: 238 | response = { 239 | 'message': 'No node found.' 240 | } 241 | return jsonify(response), 400 242 | blockchain.remove_peer_node(node_url) 243 | response = { 244 | 'message': 'Node removed', 245 | 'all_nodes': blockchain.get_peer_nodes() 246 | } 247 | return jsonify(response), 200 248 | 249 | 250 | # GET - Get List of Peer Nodes 251 | @app.route('/nodes', methods=['GET']) 252 | def get_nodes(): 253 | nodes = blockchain.get_peer_nodes() 254 | response = { 255 | 'all_nodes': nodes 256 | } 257 | return jsonify(response), 200 258 | 259 | 260 | if __name__ == '__main__': 261 | from argparse import ArgumentParser 262 | parser = ArgumentParser() 263 | # # We add additional arguments which can be used during execution to perform 264 | # addition funtionality such as create a new port 265 | parser.add_argument('-p', '--port', type=int, default=5000) 266 | # Getting a list of arguments using parser 267 | args = parser.parse_args() 268 | port = args.port 269 | wallet = Wallet(port) 270 | blockchain = Blockchain(wallet.public_key, port) 271 | app.run(host='0.0.0.0', port=port) 272 | -------------------------------------------------------------------------------- /node_console.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid4 2 | 3 | from blockchain import Blockchain 4 | from utility.verification import Verification 5 | from wallet import Wallet 6 | 7 | # This node class runs the local blockchain instance (id: Id of the node, blockchain: blockchain which is run by node) 8 | class Node: 9 | def __init__(self): 10 | # self.id = str(uuid4()) 11 | self.wallet = Wallet() 12 | self.wallet.create_keys() 13 | self.blockchain = Blockchain(self.wallet.public_key) 14 | 15 | # Get the user input, transform it from a string to a float and store it in user_input 16 | def get_transaction_value(self): 17 | tx_recipient = input('Enter the recipient of the transaction: ') 18 | tx_amount = float(input('Your transaction amount please: ')) 19 | return tx_recipient, tx_amount 20 | 21 | # Prompts the user for its choice and return it 22 | def get_user_choice(self): 23 | user_input = input('Your choice: ') 24 | return user_input 25 | 26 | # Output the blockchain list to the console 27 | def print_blockchain_elements(self): 28 | for block in self.blockchain.chain: 29 | print('Outputting Block') 30 | print(block) 31 | else: 32 | print('-' * 20) 33 | 34 | # Starts the node and waits for user input 35 | def listen_for_input(self): 36 | waiting_for_input = True 37 | 38 | # User Input Interface 39 | while waiting_for_input: 40 | print('Please choose') 41 | print('1: Add a new transaction value') 42 | print('2: Mine a new block') 43 | print('3: Output the blockchain blocks') 44 | print('4: Check transaction validity') 45 | print('5: Create wallet') 46 | print('6: Load wallet') 47 | print('7: Save keys') 48 | print('q: Quit') 49 | user_choice = self.get_user_choice() 50 | if user_choice == '1': 51 | tx_data = self.get_transaction_value() 52 | recipient, amount = tx_data 53 | signature = self.wallet.sign_transaction(self.wallet.public_key, recipient, amount) 54 | if self.blockchain.add_transaction(recipient, self.wallet.public_key, signature, amount=amount): 55 | print('Added transaction!') 56 | else: 57 | print('Transaction failed!') 58 | print(self.blockchain.get_open_transactions()) 59 | elif user_choice == '2': 60 | if not self.blockchain.mine_block(): 61 | print('Mining failed. Got no wallet?') 62 | elif user_choice == '3': 63 | self.print_blockchain_elements() 64 | elif user_choice == '4': 65 | if Verification.verify_transactions(self.blockchain.get_open_transactions(), self.blockchain.get_balance): 66 | print('All transactions are valid') 67 | else: 68 | print('There are invalid transactions') 69 | elif user_choice == '5': 70 | self.wallet.create_keys() 71 | self.blockchain = Blockchain(self.wallet.public_key) 72 | elif user_choice == '6': 73 | self.wallet.load_keys() 74 | self.blockchain = Blockchain(self.wallet.public_key) 75 | elif user_choice == '7': 76 | self.wallet.save_keys() 77 | elif user_choice == 'q': 78 | waiting_for_input = False 79 | else: 80 | print('Input was invalid, please pick a value from the list!') 81 | if not Verification.verify_chain(self.blockchain.chain): 82 | self.print_blockchain_elements() 83 | print('Invalid blockchain!') 84 | # Break out of the loop 85 | break 86 | print('Balance of {}: {:6.2f}'.format(self.wallet.public_key, self.blockchain.get_balance())) 87 | else: 88 | print('User left!') 89 | 90 | print('Done!') 91 | 92 | if __name__ == '__main__': 93 | node = Node() 94 | node.listen_for_input() 95 | -------------------------------------------------------------------------------- /transaction.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from utility.printable import Printable 4 | 5 | # Transaction class represents an open transaction which can be added to a block to become processed transaction 6 | class Transaction(Printable): 7 | def __init__(self, sender, recipient, signature, amount): 8 | self.sender = sender 9 | self.recipient = recipient 10 | self.amount = amount 11 | self.signature = signature 12 | 13 | # Converts the transaction into a hashable OrderedDict 14 | def to_ordered_dict(self): 15 | return OrderedDict([('sender', self.sender), ('recipient', self.recipient), ('amount', self.amount)]) 16 | -------------------------------------------------------------------------------- /utility/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/utility/.DS_Store -------------------------------------------------------------------------------- /utility/__init__.py: -------------------------------------------------------------------------------- 1 | from utility.hash_util import hash_string_256 2 | 3 | __all__ = ['hash_string_256'] -------------------------------------------------------------------------------- /utility/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/utility/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /utility/__pycache__/hash_util.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/utility/__pycache__/hash_util.cpython-37.pyc -------------------------------------------------------------------------------- /utility/__pycache__/printable.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/utility/__pycache__/printable.cpython-37.pyc -------------------------------------------------------------------------------- /utility/__pycache__/verification.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/utility/__pycache__/verification.cpython-37.pyc -------------------------------------------------------------------------------- /utility/hash_util.py: -------------------------------------------------------------------------------- 1 | import hashlib as hl 2 | import json 3 | 4 | # __all__ = ['hash_string_256', 'hash_block'] 5 | 6 | # Uses SHA256 hashing algorithm to hash a given input string 7 | def hash_string_256(string): 8 | return hl.sha256(string).hexdigest() 9 | 10 | # Hashes a block and returns a str representation of it 11 | def hash_block(block): 12 | hashable_block = block.__dict__.copy() 13 | hashable_block['transactions'] = [tx.to_ordered_dict() for tx in hashable_block['transactions']] 14 | return hash_string_256(json.dumps(hashable_block, sort_keys=True).encode()) -------------------------------------------------------------------------------- /utility/printable.py: -------------------------------------------------------------------------------- 1 | """ 2 | Printable class represents a base class which implements 3 | the printing to console functionality. We convert everything 4 | to string as in our wallet functionality, output is in binary 5 | and so to maintain the readability of data, we use printable class 6 | """ 7 | class Printable: 8 | def __repr__(self): 9 | return str(self.__dict__) 10 | -------------------------------------------------------------------------------- /utility/verification.py: -------------------------------------------------------------------------------- 1 | # Provides methods for implementing the verification functionality 2 | 3 | from utility.hash_util import hash_string_256, hash_block 4 | from wallet import Wallet 5 | 6 | # Verification class in our use case is a helper class which offers static & class-based verification & validation methods 7 | class Verification: 8 | @staticmethod 9 | # Validate a proof of work number and see if it solves the POW algorithm (two leading 0s, set by us) 10 | # Arguments: 11 | # transactions: The transactions of the block for which the proof is created. 12 | # last_hash: The previous block's hash which will be stored in the current block. 13 | # proof: The proof number we're testing. 14 | def valid_proof(transactions, last_hash, proof): 15 | # Create a string with all the hash inputs 16 | guess = (str([tx.to_ordered_dict() for tx in transactions]) + str(last_hash) + str(proof)).encode() 17 | # Hash the string; This hash is used for POW Algorithm and is not the same as stored in previous_hash 18 | guess_hash = hash_string_256(guess) 19 | # Only a hash (which is based on the above inputs) which starts with two 0s is treated as valid 20 | # If 10 0's are used instead of '00', this allows to control the speed at which new blocks are created 21 | # so more 0's mean more time will be required to create a new block 22 | return guess_hash[0:2] == '00' 23 | 24 | # # Analyze and Verify the Blockchain, return True if it's valid else False 25 | @classmethod 26 | def verify_chain(cls, blockchain): 27 | for (index, block) in enumerate(blockchain): 28 | if index == 0: 29 | continue 30 | if block.previous_hash != hash_block(blockchain[index - 1]): 31 | return False 32 | if not cls.valid_proof(block.transactions[:-1], block.previous_hash, block.proof): 33 | print('Proof of work is invalid') 34 | return False 35 | return True 36 | 37 | # # Verifies by checking whether sender has sufficient coins or not 38 | @staticmethod 39 | def verify_transaction(transaction, get_balance, check_funds=True): 40 | if check_funds: 41 | sender_balance = get_balance(transaction.sender) 42 | return sender_balance >= transaction.amount and Wallet.verify_transaction(transaction) 43 | else: 44 | return Wallet.verify_transaction(transaction) 45 | 46 | # Verifies all open & unprocessed transactions 47 | @classmethod 48 | def verify_transactions(cls, open_transactions, get_balance): 49 | return all([cls.verify_transaction(tx, get_balance, False) for tx in open_transactions]) -------------------------------------------------------------------------------- /wallet.py: -------------------------------------------------------------------------------- 1 | from Crypto.PublicKey import RSA 2 | from Crypto.Signature import PKCS1_v1_5 3 | from Crypto.Hash import SHA256 4 | import Crypto.Random 5 | import binascii 6 | 7 | # Wallet class generates a wallet for us which contains our public and private keys & manages transaction signing & verification 8 | class Wallet: 9 | 10 | def __init__(self, node_id): 11 | self.private_key = None 12 | self.public_key = None 13 | self.node_id = node_id 14 | 15 | # Create a new pair of private and public keys 16 | def create_keys(self): 17 | private_key, public_key = self.generate_keys() 18 | self.private_key = private_key 19 | self.public_key = public_key 20 | 21 | # Saves the keys to a file (wallet.txt) 22 | def save_keys(self): 23 | if self.public_key != None and self.private_key != None: 24 | try: 25 | with open('wallet-{}.txt'.format(self.node_id), mode='w') as f: 26 | f.write(self.public_key) 27 | f.write('\n') 28 | f.write(self.private_key) 29 | return True 30 | except (IOError, IndexError): 31 | print('Saving wallet failed...') 32 | return False 33 | 34 | # Loads the keys from the wallet.txt 35 | def load_keys(self): 36 | try: 37 | with open('wallet-{}.txt'.format(self.node_id), mode='r') as f: 38 | keys = f.readlines() 39 | public_key = keys[0][:-1] 40 | private_key = keys[1] 41 | self.public_key = public_key 42 | self.private_key = private_key 43 | return True 44 | except (IOError, IndexError): 45 | print('Loading wallet failed...') 46 | return False 47 | 48 | # Generate a new pair of private and public key 49 | def generate_keys(self): 50 | private_key = RSA.generate(1024, Crypto.Random.new().read) 51 | public_key = private_key.publickey() 52 | return (binascii.hexlify(private_key.exportKey(format='DER')).decode('ascii'), binascii.hexlify(public_key.exportKey(format='DER')).decode('ascii')) 53 | 54 | # Sign a transaction and return the signature 55 | # RSA is a cryptography algorithm 56 | # binascii.hexlify is used to convert binary data to hexadecimal representation 57 | def sign_transaction(self, sender, recipient, amount): 58 | signer = PKCS1_v1_5.new(RSA.importKey(binascii.unhexlify(self.private_key))) 59 | h = SHA256.new((str(sender) + str(recipient) + str(amount)).encode('utf8')) 60 | signature = signer.sign(h) 61 | return binascii.hexlify(signature).decode('ascii') 62 | 63 | # Verify signature of transaction 64 | @staticmethod 65 | def verify_transaction(transaction): 66 | public_key = RSA.importKey(binascii.unhexlify(transaction.sender)) 67 | verifier = PKCS1_v1_5.new(public_key) 68 | h = SHA256.new((str(transaction.sender) + str(transaction.recipient) + str(transaction.amount)).encode('utf8')) 69 | return verifier.verify(h, binascii.unhexlify(transaction.signature)) --------------------------------------------------------------------------------