Created
April 25, 2017 03:57
-
-
Save Lambdanaut/779da7825d70b9db85dcdbaf05d7c6f5 to your computer and use it in GitHub Desktop.
A cryptocoin wip from scratch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import json | |
| from Crypto.Random import random | |
| from Crypto.PublicKey import RSA | |
| from Crypto.Hash import SHA | |
| import constants | |
| from utils import rando | |
| class Block(object): | |
| def __init__(self, hash, transactions, target, prev_block=None, | |
| next_block=None): | |
| self.hash = hash | |
| self.transactions = transactions | |
| self.target = target | |
| self.prev_block = prev_block | |
| self.next_block = next_block | |
| # Puzzle is a combination of all of the transaction's hashes together | |
| self.puzzle = ''.join([transaction.hash | |
| for transaction in self.transactions]) | |
| def transaction_in_block(self, transaction): | |
| return transaction.hash in [t.hash for t in self.transactions] | |
| def verify(self, nonce): | |
| """ | |
| Generates a hash based on the transaction text, given an nonce, and | |
| checks whether the first `target` characters of the generated hash | |
| are "0"s | |
| The higher the target value, the more 0s required, and the more | |
| unlikely it is to verify. | |
| :param nonce: str | |
| :return Bool: | |
| """ | |
| text = self.puzzle + nonce | |
| h = SHA.new(text).hexdigest() | |
| return h[:self.target] == '0' * self.target | |
| class Transaction(object): | |
| def __init__(self, recipients, value, hash=None, version=None): | |
| self.recipients = recipients | |
| self.value = value | |
| self.hash = hash or SHA.new(str(rando())).hexdigest() | |
| self.version = version or constants.version | |
| def sign(self, key): | |
| """Adds signature to the Transaction""" | |
| self.key = key # Public key of sender | |
| self.sig = self.key.sign(self.hash, rando()) # Signature of sender | |
| def to_dict(self): | |
| doc = { | |
| 'version': self.version, # sillycoin protocol version | |
| 'hash': '123', # hash of the remainder of the transaction | |
| 'size': 123, # size in bytes of transaction | |
| 'in': [ | |
| { | |
| 'hash': 'abc', | |
| 'n': 0 | |
| }, | |
| ], | |
| 'out': { | |
| 'value': 0.1, | |
| 'hash': 'qwrt' | |
| }, | |
| } | |
| return doc | |
| def transaction_from_dict(doc): | |
| recipients = doc.get('out') | |
| version = doc.get('version') | |
| hash = doc.get('hash') | |
| return Transaction(recipients, 0.01, hash, version) | |
| class BroadcastNode(object): | |
| """Abstract class to implement remote nodes to broadcast data to""" | |
| def broadcast(self, data): | |
| raise NotImplementedError() | |
| class LocalBroadcastNode(BroadcastNode): | |
| """ | |
| Broadcast node for testing broadcasting between clients within a single | |
| Python application | |
| """ | |
| def __init__(self, client): | |
| self.client = client | |
| def broadcast(self, data): | |
| self.client.receive_broadcast(data) | |
| class Client(object): | |
| def __init__(self, blockchain=None, broadcast_nodes=None): | |
| self.key = RSA.generate(1024) | |
| self.addresses = [self.generate_address()] | |
| self.blockchain = blockchain | |
| self.broadcast_nodes = broadcast_nodes or [] | |
| # List of recently received transaction broadcasts | |
| self.new_transaction_broadcasts = set() | |
| def generate_address(self): | |
| return SHA.new(str(rando())).hexdigest() | |
| def mine(self, block, start=0, end=2000): | |
| for x in range(start, end): | |
| if block.verify(str(x)): | |
| return str(x) | |
| def broadcast_transaction(self, transaction): | |
| transaction.sign(self.key) | |
| doc = transaction.to_dict() | |
| doc = self.package_for_broadcast('transaction', doc) | |
| data = json.dumps(doc) | |
| return self.broadcast(data) | |
| def broadcast_block_solution(self): | |
| pass | |
| def broadcast(self, data): | |
| results = [node.broadcast(data) for node in self.broadcast_nodes] | |
| return results | |
| def package_for_broadcast(self, type, data): | |
| """Given dict, wraps in broadcast dict""" | |
| packaged = { | |
| 'type': type, | |
| 'package': data, | |
| } | |
| return packaged | |
| def receive_broadcast(self, data): | |
| """Callback for when a broadcast is received from another node""" | |
| # Parse broadcast | |
| doc = json.loads(data) | |
| b_type = doc.get('type') | |
| if b_type == 'transaction': | |
| transaction_doc = doc.get('package') | |
| transaction = transaction_from_dict(transaction_doc) | |
| # Propogate the transaction if it's new | |
| if transaction.hash not in self.new_transaction_broadcasts: | |
| self.new_transaction_broadcasts.add(transaction.hash) | |
| self.broadcast(data) | |
| else: | |
| return | |
| if __name__ == '__main__': | |
| block = Block(0, [], 1) | |
| print 'Generated block: "{}"'.format(block.hash) | |
| client1 = Client(blockchain=block) | |
| broadcast_node_1 = LocalBroadcastNode(client1) | |
| client2 = Client(blockchain=block, broadcast_nodes=[broadcast_node_1]) | |
| broadcast_node_2 = LocalBroadcastNode(client2) | |
| client1.broadcast_nodes.append(broadcast_node_2) | |
| print 'Generated client with key: "{}"'.format(client1.key.exportKey()) | |
| transaction = Transaction([client2.addresses[0]], 0.1) | |
| print 'Created transaction {}'.format(transaction.hash) | |
| print 'Broadcasting a transaction...' | |
| client1.broadcast_transaction(transaction) | |
| print 'Mining the block...' | |
| solution = client2.mine(block) | |
| print 'Block mined! Puzzle solution: "{}"'.format(solution) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment