Malte Kraus vor 8 Jahren
Commit
ccb294f3f5
8 geänderte Dateien mit 281 neuen und 0 gelöschten Zeilen
  1. 56 0
      block.py
  2. 27 0
      blockchain.py
  3. 44 0
      chainbuilder.py
  4. 18 0
      crypto.py
  5. 58 0
      merkle.py
  6. 9 0
      mining_strategy.py
  7. 14 0
      test_merkle.py
  8. 55 0
      transaction.py

+ 56 - 0
block.py

@@ -0,0 +1,56 @@
+from merkle import merkle_tree
+from crypto import get_hasher
+
+GENESIS_BLOCK_HASH = b"" # TODO
+
+class Block:
+    """ A block. """
+
+    def __init__(self, hash_val, prev_block_hash, time, nonce, height, received_time, difficulty, merkle_root_hash=None, transactions=None):
+        self.hash = hash_val
+        self.prev_block_hash = prev_block_hash
+        self.merkle_root_hash = merkle_root_hash
+        self.time = time
+        self.nonce = nonce
+        self.height = height
+        self.received_time = received_time
+        self.difficulty = difficulty
+        self.transactions = transactions
+
+    def verify_merkle(self):
+        """ Verify that the merkle root hash is correct for the transactions in this block. """
+        return merkle_tree(self.transactions).get_hash() == self.merkle_root_hash
+
+    def get_hash(self):
+        """ Compute the hash of the header data. This is not necessarily the received hash value for this block! """
+        hasher = get_hasher()
+        hasher.update(self.prev_block_hash)
+        hasher.update(self.merkle_root_hash)
+        hasher.update(self.time)
+        hasher.update(self.difficulty)
+        hasher.update(self.nonce) # for mining we want to get a copy of hasher here
+        return hasher.digest()
+
+    def verify_difficulty(self):
+        """ Verify that the hash value is correct and fulfills its difficulty promise. """
+        # TODO: move this some better place
+        if self.hash != self.get_hash():
+            return False
+        return int.from_bytes(self.hash, byteorder='little', signed=False) > self.difficulty
+
+    def verify_prev_block(self, chain):
+        """ Verify the previous block pointer points to a valid block in the given block chain. """
+        return chain.get_block_by_hash(chain) is not None
+
+    def verify_transactions(self, chain):
+        """ Verify all transaction are valid in the given block chain. """
+        for t in self.transactions:
+            if not t.verify(chain):
+                return False
+        return True
+
+    def verify(self, chain):
+        """ Verifies this block contains only valid data consistent with the given block chain. """
+        if self.height == 0:
+            return self.hash == GENESIS_BLOCK_HASH
+        return self.verify_difficulty() and self.verify_merkle() and self.verify_prev_block(chain) and self.verify_transactions(chain)

+ 27 - 0
blockchain.py

@@ -0,0 +1,27 @@
+
+
+class Blockchain:
+    def get_transaction_by_hash(self, hash_val):
+        """
+        Returns a transaction from its hash, or None.
+        """
+        pass
+
+    def is_coin_still_valid(self, transaction_hash_val, output_idx):
+        """
+        Validates that the coins that were sent in the transaction identified
+        by `transaction_hash_val` to the nth receiver (n=output_idx) have not been
+        spent yet.
+        """
+        pass
+
+    def get_block_by_hash(self, hash_val):
+        """
+        Returns a block by its hash value, or None if it cannot be found.
+        """
+        pass
+
+
+    @getter
+    def head(self):
+        return self.blocks[-1]

+ 44 - 0
chainbuilder.py

@@ -0,0 +1,44 @@
+class ChainBuilder:
+    confirmed_block_chain = None
+    unconfirmed_block_chain = []
+
+    block_cache = {}
+    unconfirmed_transactions = {}
+
+
+    def new_transaction_received(self, transaction):
+        hash_val = transaction.get_hash()
+        if block_chain.get_transaction_by_hash(hash_val) is None:
+           unconfirmed_transactions[hash_val] = transaction
+
+    def get_next_unconfirmed_block(self):
+        """  """
+        unc = self.unconfirmed_block_chain
+        while unc[-1].prev_block_hash in self.block_cache:
+            unc.append(self.block_cache[unc[-1].prev_block_hash])
+
+        if unc[-1].height == 0:
+            # TODO: create and verify this block chain
+            self.unconfirmed_block_chain = []
+        else:
+            # TODO: download next block
+
+    def new_block_received(self, block):
+        if not block.verify_difficulty() or block.hash in self.block_cache:
+            return
+        self.block_cache[block.hash] = block
+
+        if self.unconfirmed_block_chain:
+            if self.unconfirmed_block_chain[-1].prev_block_hash == block.hash:
+                self.unconfirmed_block_chain.append(block)
+                self.get_next_unconfirmed_block()
+            else if self.unconfirmed_block_chain[0].hash == block.prev_block_hash:
+                self.unconfirmed_block_chain.insert(0, block)
+
+            if block.height > self.unconfirmed_block_chain[0].height and block.height > self.confirmed_block_chain.get_height():
+                self.unconfirmed_block_chain = [block]
+                self.get_next_unconfirmed_block()
+        elif block.height > self.confirmed_block_chain.get_height():
+            self.unconfirmed_block_chain = [block]
+            self.get_next_unconfirmed_block()
+

+ 18 - 0
crypto.py

@@ -0,0 +1,18 @@
+""" Cryptographic primitives. """
+
+from Crypto.Signature import PKCS1_v1_5
+from Crypto.Hash import SHA512
+
+def get_hasher():
+    """ Returns a object that you can use for hashing. Currently SHA512, swap it out for something if you feel like it! """
+    return SHA512.new()
+
+def verify_sign(hashed_value, signature, pub_key):
+    """ Verify a signature for a already hashed value and a public key. """
+    ver = PKCS1_v1_5.new(pub_key)
+    return ver.verify(hashed_value, signature)
+
+def sign(hashed_value, priv_key):
+    """ Sign a hashed value with a private key. """
+    signer = PKCS1_v1_5.new(priv_key)
+    return signer.sign(hashed_value)

+ 58 - 0
merkle.py

@@ -0,0 +1,58 @@
+from crypto import get_hasher
+import json
+from treelib import Node, Tree
+
+class MerkleNode:
+    """ A hash tree node, pointing to a leaf value or another node. """
+
+    def __init__(self, v1, v2):
+        self.v1 = v1
+        self.v1_hash = b'' if v1 is None else v1.get_hash()
+        self.v2 = v2
+        self.v2_hash = b'' if v2 is None else v2.get_hash()
+
+    def get_hash(self):
+        """ Compute the hash of this node. """
+        hasher = get_hasher()
+        hasher.update(self.v1_hash)
+        hasher.update(self.v2_hash)
+        return hasher.digest()
+
+    def _get_tree(self, tree, parent):
+        """ Recursively build a treelib tree for nice pretty printing. """
+        tree.create_node(self.get_hash()[:10], self, parent)
+        if isinstance(self.v1, MerkleNode):
+            self.v1._get_tree(tree, self)
+        elif self.v1 is not None:
+            tree.create_node(str(self.v1), str(self.v1), self)
+        if isinstance(self.v2, MerkleNode):
+            self.v2._get_tree(tree, self)
+        elif self.v2 is not None:
+            tree.create_node(str(self.v2), str(self.v2), self)
+
+    def __str__(self):
+        tree = Tree()
+        self._get_tree(tree, None)
+        return str(tree)
+
+def merkle_tree(values):
+    """
+    Constructs a merkle tree from a list of values.
+
+    All values need to support a method get_hash().
+    """
+
+    if not values:
+        return MerkleNode(None, None)
+
+    while len(values) > 1:
+        nodes = []
+        for (v1, v2) in zip(values[0::2], values[1::2]):
+            nodes.append(MerkleNode(v1, v2))
+
+        if len(values) % 2:
+            nodes.append(MerkleNode(values[-1], None))
+
+        values = nodes
+
+    return values[0]

+ 9 - 0
mining_strategy.py

@@ -0,0 +1,9 @@
+from block import Block
+from datetime import datetime
+from merkle import merkle_tree
+
+def create_block(blockchain, unconfirmed_transactions, difficulty):
+    tree = merkle_tree(unconfirmed_transactions)
+    head = blockchain.head
+    return Block(None, head.hash, datetime.now(), 0, head.height + difficulty,
+                 None, difficulty, tree.get_hash(), unconfirmed_transactions)

+ 14 - 0
test_merkle.py

@@ -0,0 +1,14 @@
+from merkle import merkle_tree
+
+class Val:
+    def __init__(self, hash_val):
+        self.hash_val = hash_val
+
+    def get_hash(self):
+        return self.hash_val
+
+    def __str__(self):
+        return str(self.hash_val)
+
+if __name__ == '__main__':
+    print(merkle_tree([Val(bytes([i])) for i in range(10)]))

+ 55 - 0
transaction.py

@@ -0,0 +1,55 @@
+from collections import namedtuple
+from crypto import get_hasher, sign, verify_sign
+
+TransactionTarget = namedtuple("TransactionTarget", ["recipient_pk", "amount"])
+TransactionInput = namedtuple("TransactionInput", ["transaction_hash", "output_idx"])
+
+
+
+class Transaction:
+    def __init__(self, inputs, targets, signatures=None):
+        self.inputs = inputs
+        self.targets = targets
+        self.signatures = signatures or []
+
+    def get_hash(self):
+        """ Hash this transaction. Returns raw bytes. """
+        h = get_hasher()
+        for target in self.targets:
+            h.update(target.amount)
+            h.update(target.recipient_pk)
+        for inp in self.inputs:
+            h.update(inp)
+        return h.digest()
+
+    def sign(self, private_key):
+        """ Sign this transaction with a private key. You need to call this in the same order as the inputs. """
+        self.signatures.append(sign(self.get_hash(), private_key))
+
+    def _verify_signatures(self, chain):
+        """ Verify that all inputs are signed and the signatures are valid. """
+        if len(self.signatures) != len(self.inputs)
+            return False
+
+        for (s, i) in zip(self.signatures, self.inputs):
+            if not self._verify_single_sig(s, i, chain):
+                return False
+        return True
+
+    def _verify_single_sig(self, sig, inp, chain):
+        """ Verifies the signature on a single input. """
+        trans = chain.get_transaction_by_hash(inp.transaction_hash)
+        sender_pk = trans.targets[inp.output_idx]
+        return verify_sign(self.get_hash(), sig)
+
+
+    def _verify_single_spend(self, chain):
+        """ Verifies that all inputs have not been spent yet. """
+        for i in self.inputs:
+            if not chain.is_coin_still_valid(i.transaction_hash, i.output_idx):
+                return False
+        return True
+
+    def verify(self, chain):
+        """ Verifies that this transaction is completely valid. """
+        return self._verify_single_spend(chain) and self._verify_signatures(chain)